1327 lines
36 KiB
C
1327 lines
36 KiB
C
/*
|
|
* Sensor Hub driver for NVIDIA's Tegra186 AON Sensor Processing Engine.
|
|
*
|
|
* Copyright (c) 2017-2018, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* NVS = NVidia Sensor framework
|
|
* See nvs_iio.c and nvs.h for nvs documentation
|
|
* Here is the flow of this driver. Failure in any step causes the driver
|
|
* probe to fail :
|
|
* 1. On boot, the kernel driver fetches the sensor count and all the
|
|
* sensor chips supported by the sensor hub.
|
|
* 2. It uses the fetched sensor chips from sensor hub to validate the
|
|
* device tree nodes which represent a sensor chip and respective
|
|
* attributes.
|
|
* 3. It then fetches the I2C controller info from the DT and validates
|
|
* the controller id. It creates a setup request comprising the sensor
|
|
* chip attributes such as the I2C controller it is connected as well
|
|
* as the I2C address of the chip, GPIO connected to the chip and sends
|
|
* it to the sensor hub.
|
|
* 4. Upon validating the sensor chip nodes, it uses the I2C controllers
|
|
* info (controller id + clock rate) and sends an I2C init request to
|
|
* the sensor hub. Upon successful initialization of the I2C controller,
|
|
* the driver sends each sensor chip init request. This request also
|
|
* includes the init request of the slave chip connected over the
|
|
* auxiliary I2C bus of the chip if it supports one.
|
|
* 5. Upon successful initialization of the I2C controllers and the sensor
|
|
* chips, the driver fetches the sensor config list supported by each
|
|
* sensor chip and builds a local copy of it. The driver does not fetch
|
|
* the entire list present on the sensor hub rather only fetches the
|
|
* configs supported by the chips passed in the DT node.
|
|
* 6. Upon building the local sensor config table, the driver calls
|
|
* nvs_probe which sets up all the nvs iio attributes for each sensor
|
|
* config and creates the iio sysfs nodes for the NVS HAL to communicate.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/string.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/tegra-aon.h>
|
|
#include <linux/mailbox_client.h>
|
|
#include <linux/time.h>
|
|
#include <linux/time64.h>
|
|
#include <linux/timekeeping.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/trace_imu.h>
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/arch_timer.h>
|
|
|
|
#include "aon-shub-messages.h"
|
|
|
|
#define READJUST_TS_SAMPLES (100)
|
|
|
|
#define AXIS_N 3
|
|
/* block period in ms */
|
|
#define TX_BLOCK_PERIOD 200
|
|
#define INIT_TOUT_COUNT 15
|
|
#define IVC_TIMEOUT 200
|
|
|
|
#define SENSOR_TYPE_UNKNOWN 0
|
|
|
|
enum I2C_IDS {
|
|
I2CID_MIN = 1,
|
|
I2C2 = 1,
|
|
I2C8 = 2,
|
|
I2CID_MAX = 2,
|
|
};
|
|
|
|
enum GPIO_CTLR_IDS {
|
|
GPIO_CHIP_ID_MIN,
|
|
AON_GPIO_CHIP_ID = GPIO_CHIP_ID_MIN,
|
|
MAIN_GPIO_CHIP_ID,
|
|
GPIO_CHIP_ID_MAX = MAIN_GPIO_CHIP_ID,
|
|
};
|
|
|
|
struct aon_shub_sensor {
|
|
char name[32];
|
|
char part[32];
|
|
char vendor[32];
|
|
void *nvs_st;
|
|
struct sensor_cfg cfg;
|
|
int type;
|
|
bool genable; /* global enable */
|
|
};
|
|
|
|
struct shub_chip_node {
|
|
struct device_node *dn;
|
|
u8 chip_id;
|
|
};
|
|
|
|
struct tegra_aon_shub {
|
|
struct device *dev;
|
|
struct completion *wait_on;
|
|
struct mbox_client cl;
|
|
struct mbox_chan *mbox;
|
|
struct aon_shub_request *shub_req;
|
|
struct aon_shub_response *shub_resp;
|
|
/* Interface to the NVS framework */
|
|
struct nvs_fn_if *nvs;
|
|
struct aon_shub_sensor **snsrs;
|
|
struct mutex shub_mutex;
|
|
struct snsr_chip *chips;
|
|
struct shub_chip_node *chip_nodes;
|
|
unsigned int snsr_cnt;
|
|
u32 nchips;
|
|
u32 i2c_clk_rates[I2CID_MAX];
|
|
u32 chip_id_mask;
|
|
u32 active_snsr_msk;
|
|
u32 adjust_ts_counter;
|
|
u64 ts_res_ns;
|
|
s64 ts_adjustment;
|
|
bool last_tx_done;
|
|
};
|
|
|
|
static const char *const snsr_types[] = {
|
|
[SENSOR_TYPE_UNKNOWN] = "generic_sensor",
|
|
[SENSOR_TYPE_ACCELEROMETER] = "accelerometer",
|
|
[SENSOR_TYPE_MAGNETIC_FIELD] = "magnetic_field",
|
|
[SENSOR_TYPE_ORIENTATION] = "orientation",
|
|
[SENSOR_TYPE_GYROSCOPE] = "gyroscope",
|
|
};
|
|
|
|
/* This has to be a multiple of the cache line size */
|
|
static inline int ivc_min_frame_size(void)
|
|
{
|
|
return cache_line_size();
|
|
}
|
|
|
|
static inline int get_snsr_type(const char *snsr_name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(snsr_types); i++) {
|
|
if (!strcmp(snsr_types[i], snsr_name))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Android sensor service expects sensor events to have timestamps in
|
|
* Monotonic time base. However, the sensor events on the sensor hub
|
|
* are timestamped at the TKE timebase. This API provides a conversion
|
|
* from the TKE timebase to the monotonic timebase.
|
|
*/
|
|
static inline s64 get_ts_adjustment(u64 tsc_res)
|
|
{
|
|
s64 tsc = 0;
|
|
struct timespec64 ts;
|
|
s64 delta = 0;
|
|
s64 mono_time = 0;
|
|
unsigned long flags;
|
|
DEFINE_RAW_SPINLOCK(slock);
|
|
|
|
raw_spin_lock_irqsave(&slock, flags);
|
|
tsc = (s64)(arch_counter_get_cntvct() * tsc_res);
|
|
|
|
ktime_get_ts64(&ts);
|
|
mono_time = timespec_to_ns(&ts);
|
|
|
|
delta = mono_time - tsc;
|
|
raw_spin_unlock_irqrestore(&slock, flags);
|
|
|
|
return delta;
|
|
}
|
|
|
|
static void tegra_aon_shub_mbox_rcv_msg(struct mbox_client *cl, void *rx_msg)
|
|
{
|
|
struct tegra_aon_mbox_msg *msg = rx_msg;
|
|
struct tegra_aon_shub *shub = dev_get_drvdata(cl->dev);
|
|
struct aon_shub_response *shub_resp;
|
|
int snsr_id;
|
|
u32 i;
|
|
s64 ts;
|
|
int cookie;
|
|
|
|
shub_resp = (struct aon_shub_response *)msg->data;
|
|
if (shub_resp->resp_type == AON_SHUB_REQUEST_PAYLOAD) {
|
|
i = shub_resp->data.payload.count;
|
|
if (i > ARRAY_SIZE(shub_resp->data.payload.data) || i == 0) {
|
|
dev_err(shub->dev,
|
|
"Invalid payload count\n");
|
|
return;
|
|
}
|
|
if (shub->adjust_ts_counter == READJUST_TS_SAMPLES) {
|
|
shub->ts_adjustment =
|
|
get_ts_adjustment(shub->ts_res_ns);
|
|
shub->adjust_ts_counter = 0;
|
|
}
|
|
shub->adjust_ts_counter++;
|
|
while (i--) {
|
|
snsr_id = shub_resp->data.payload.data[i].snsr_id;
|
|
ts = (s64)shub_resp->data.payload.data[i].ts;
|
|
ts += shub->ts_adjustment;
|
|
shub_resp->data.payload.data[i].ts = (u64)ts;
|
|
cookie = COOKIE(shub->snsrs[snsr_id]->type, ts);
|
|
trace_async_atrace_begin(__func__, TRACE_SENSOR_ID, cookie);
|
|
shub->nvs->handler(shub->snsrs[snsr_id]->nvs_st,
|
|
&shub_resp->data.payload.data[i].x,
|
|
shub_resp->data.payload.data[i].ts);
|
|
trace_async_atrace_end(__func__, TRACE_SENSOR_ID, cookie);
|
|
}
|
|
} else {
|
|
memcpy(shub->shub_resp, msg->data, sizeof(*shub->shub_resp));
|
|
complete(shub->wait_on);
|
|
}
|
|
}
|
|
|
|
static void tegra_aon_shub_mbox_tx_done(struct mbox_client *cl, void *tx_msg,
|
|
int r)
|
|
{
|
|
struct tegra_aon_shub *shub = dev_get_drvdata(cl->dev);
|
|
|
|
shub->last_tx_done = !r;
|
|
}
|
|
|
|
static int tegra_aon_shub_ivc_msg_send(struct tegra_aon_shub *shub, int len, int timeout)
|
|
{
|
|
int status;
|
|
struct tegra_aon_mbox_msg msg;
|
|
|
|
shub->last_tx_done = false;
|
|
msg.length = len;
|
|
msg.data = (void *)shub->shub_req;
|
|
status = mbox_send_message(shub->mbox, (void *)&msg);
|
|
if (status < 0) {
|
|
dev_err(shub->dev, "mbox_send_message() failed with %d\n",
|
|
status);
|
|
} else {
|
|
status = wait_for_completion_timeout(shub->wait_on,
|
|
msecs_to_jiffies(timeout));
|
|
if (status == 0) {
|
|
dev_err(shub->dev, "Timeout: failed on IVC %s\n",
|
|
shub->last_tx_done ? "RX" : "TX");
|
|
return -ETIMEDOUT;
|
|
}
|
|
status = shub->shub_resp->status;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static int tegra_aon_shub_batch_read(void *client, int snsr_id,
|
|
unsigned int *period_us,
|
|
unsigned int *timeout_us)
|
|
{
|
|
struct tegra_aon_shub *shub = (struct tegra_aon_shub *)client;
|
|
int ret;
|
|
|
|
mutex_lock(&shub->shub_mutex);
|
|
shub->shub_req->req_type = AON_SHUB_REQUEST_BATCH_RD;
|
|
shub->shub_req->data.batch_rd.snsr_id = snsr_id;
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
IVC_TIMEOUT);
|
|
if (ret)
|
|
dev_err(shub->dev, "batch_read ERR: snsr_id: %d!\n", snsr_id);
|
|
|
|
if (timeout_us)
|
|
*timeout_us = shub->shub_resp->data.batch_rd.timeout_us;
|
|
if (period_us)
|
|
*period_us = shub->shub_resp->data.batch_rd.period_us;
|
|
mutex_unlock(&shub->shub_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_aon_shub_batch(void *client, int snsr_id, int flags,
|
|
unsigned int period, unsigned int timeout)
|
|
{
|
|
struct tegra_aon_shub *shub = (struct tegra_aon_shub *)client;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&shub->shub_mutex);
|
|
shub->shub_req->req_type = AON_SHUB_REQUEST_BATCH;
|
|
shub->shub_req->data.batch.snsr_id = snsr_id;
|
|
shub->shub_req->data.batch.flags = flags;
|
|
if (period < shub->snsrs[snsr_id]->cfg.delay_us_min)
|
|
period = shub->snsrs[snsr_id]->cfg.delay_us_min;
|
|
else if (period > shub->snsrs[snsr_id]->cfg.delay_us_max)
|
|
period = shub->snsrs[snsr_id]->cfg.delay_us_max;
|
|
shub->shub_req->data.batch.period = period;
|
|
shub->shub_req->data.batch.timeout = timeout;
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
IVC_TIMEOUT);
|
|
if (ret)
|
|
dev_err(shub->dev,
|
|
"batch ERR: snsr_id: %d period: %u timeout: %u!\n",
|
|
snsr_id, period, timeout);
|
|
mutex_unlock(&shub->shub_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_aon_shub_enable(void *client, int snsr_id, int enable)
|
|
{
|
|
struct tegra_aon_shub *shub = (struct tegra_aon_shub *)client;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&shub->shub_mutex);
|
|
shub->shub_req->req_type = AON_SHUB_REQUEST_ENABLE;
|
|
shub->shub_req->data.enable.snsr_id = snsr_id;
|
|
shub->shub_req->data.enable.enable = enable;
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
IVC_TIMEOUT);
|
|
if (ret) {
|
|
dev_err(shub->dev,
|
|
"enable ERR: snsr_id: %d enable: %d!\n",
|
|
snsr_id, enable);
|
|
} else {
|
|
ret = shub->shub_resp->data.enable.enable;
|
|
}
|
|
mutex_unlock(&shub->shub_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_aon_shub_max_range(void *client, int snsr_id, int max_range)
|
|
{
|
|
struct tegra_aon_shub *shub = (struct tegra_aon_shub *)client;
|
|
int ret = 0, i;
|
|
int len;
|
|
|
|
if (max_range < 0)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&shub->shub_mutex);
|
|
shub->shub_req->req_type = AON_SHUB_REQUEST_RANGE;
|
|
shub->shub_req->data.range.snsr_id = snsr_id;
|
|
shub->shub_req->data.range.setting = max_range;
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
IVC_TIMEOUT);
|
|
if (ret) {
|
|
dev_err(shub->dev,
|
|
"range ERR: snsr_id: %d setting: %d!\n",
|
|
snsr_id, max_range);
|
|
goto exit;
|
|
}
|
|
ret = shub->shub_resp->data.range.err;
|
|
switch (ret) {
|
|
case AON_SHUB_NO_ERR:
|
|
memcpy(&shub->snsrs[snsr_id]->cfg.max_range,
|
|
&shub->shub_resp->data.range.max_range,
|
|
sizeof(struct nvs_float));
|
|
memcpy(&shub->snsrs[snsr_id]->cfg.resolution,
|
|
&shub->shub_resp->data.range.resolution,
|
|
sizeof(struct nvs_float));
|
|
/* AXIS sensors need resolution to be put in the scales */
|
|
len = shub->snsrs[snsr_id]->cfg.ch_n_max;
|
|
if (len) {
|
|
for (i = 0; i < len; i++) {
|
|
memcpy(&shub->snsrs[snsr_id]->cfg.scales[i],
|
|
&shub->shub_resp->data.range.resolution,
|
|
sizeof(struct nvs_float));
|
|
}
|
|
}
|
|
break;
|
|
case AON_SHUB_ENODEV:
|
|
/* Invalid snsr_id passed for range setting */
|
|
ret = -ENODEV;
|
|
break;
|
|
case AON_SHUB_EACCES:
|
|
/* can't change the setting on the fly while the device is
|
|
* active. Disable the device first.
|
|
*/
|
|
ret = -EACCES;
|
|
break;
|
|
case AON_SHUB_EINVAL:
|
|
/* Range setting exceeds max setting */
|
|
ret = -EINVAL;
|
|
break;
|
|
case AON_SHUB_EPERM:
|
|
/* No provision to modify the range for this device */
|
|
ret = -EPERM;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (ret)
|
|
dev_err(shub->dev, "range ERR: %d\n", ret);
|
|
|
|
exit:
|
|
mutex_unlock(&shub->shub_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_aon_shub_thresh(struct tegra_aon_shub *shub, int snsr_id,
|
|
int thresh, bool high)
|
|
{
|
|
int ret;
|
|
|
|
mutex_lock(&shub->shub_mutex);
|
|
shub->shub_req->req_type = high ? AON_SHUB_REQUEST_THRESH_HI :
|
|
AON_SHUB_REQUEST_THRESH_LO;
|
|
shub->shub_req->data.thresh.snsr_id = snsr_id;
|
|
shub->shub_req->data.thresh.setting = thresh;
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
IVC_TIMEOUT);
|
|
if (ret) {
|
|
dev_err(shub->dev,
|
|
"thresh_%s ERR: snsr_id: %d setting: %d!\n",
|
|
high ? "hi" : "lo", snsr_id, thresh);
|
|
goto exit;
|
|
}
|
|
ret = shub->shub_resp->data.thresh.err;
|
|
switch (ret) {
|
|
case AON_SHUB_NO_ERR:
|
|
if (high)
|
|
shub->snsrs[snsr_id]->cfg.thresh_hi = thresh;
|
|
else
|
|
shub->snsrs[snsr_id]->cfg.thresh_lo = thresh;
|
|
break;
|
|
case AON_SHUB_ENODEV:
|
|
/* Invalid snsr_id passed for threshold setting */
|
|
ret = -ENODEV;
|
|
break;
|
|
case AON_SHUB_EACCES:
|
|
/* can't change the setting on the fly while the device is
|
|
* active. Disable the device first.
|
|
*/
|
|
ret = -EACCES;
|
|
break;
|
|
case AON_SHUB_EINVAL:
|
|
/* Invalid threshold passed */
|
|
ret = -EINVAL;
|
|
break;
|
|
case AON_SHUB_EPERM:
|
|
/* No provision to modify the thresh for this device */
|
|
ret = -EPERM;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (ret)
|
|
dev_err(shub->dev, "thresh_%s ERR: %d\n",
|
|
high ? "hi" : "lo", ret);
|
|
|
|
exit:
|
|
mutex_unlock(&shub->shub_mutex);
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_aon_shub_thresh_lo(void *client, int snsr_id, int thresh_lo)
|
|
{
|
|
struct tegra_aon_shub *shub = (struct tegra_aon_shub *)client;
|
|
|
|
return tegra_aon_shub_thresh(shub, snsr_id, thresh_lo, false);
|
|
}
|
|
|
|
static int tegra_aon_shub_thresh_hi(void *client, int snsr_id, int thresh_hi)
|
|
{
|
|
struct tegra_aon_shub *shub = (struct tegra_aon_shub *)client;
|
|
|
|
return tegra_aon_shub_thresh(shub, snsr_id, thresh_hi, true);
|
|
}
|
|
|
|
static struct nvs_fn_dev aon_shub_nvs_fn = {
|
|
.enable = tegra_aon_shub_enable,
|
|
.batch = tegra_aon_shub_batch,
|
|
.batch_read = tegra_aon_shub_batch_read,
|
|
.max_range = tegra_aon_shub_max_range,
|
|
.thresh_lo = tegra_aon_shub_thresh_lo,
|
|
.thresh_hi = tegra_aon_shub_thresh_hi,
|
|
};
|
|
|
|
#ifdef TEGRA_AON_SHUB_DBG_ENABLE
|
|
static void tegra_aon_shub_dbg_cfg(struct device *dev, struct sensor_cfg *cfg)
|
|
{
|
|
unsigned int i;
|
|
|
|
dev_dbg(dev, "name=%s\n", cfg->name);
|
|
dev_dbg(dev, "name=%s\n", cfg->part);
|
|
dev_dbg(dev, "name=%s\n", cfg->vendor);
|
|
dev_dbg(dev, "snsr_id=%d\n", cfg->snsr_id);
|
|
dev_dbg(dev, "timestamp_sz=%d\n", cfg->timestamp_sz);
|
|
dev_dbg(dev, "snsr_data_n=%d\n", cfg->snsr_data_n);
|
|
dev_dbg(dev, "kbuf_sz=%d\n", cfg->kbuf_sz);
|
|
dev_dbg(dev, "ch_n=%u\n", cfg->ch_n);
|
|
dev_dbg(dev, "ch_n_max=%u\n", cfg->ch_n_max);
|
|
dev_dbg(dev, "ch_sz=%d\n", cfg->ch_sz);
|
|
dev_dbg(dev, "delay_us_min=%u\n", cfg->delay_us_min);
|
|
dev_dbg(dev, "delay_us_max=%u\n", cfg->delay_us_max);
|
|
dev_dbg(dev, "matrix: ");
|
|
for (i = 0; i < 9; i++)
|
|
dev_dbg(dev, "%hhd ", cfg->matrix[i]);
|
|
dev_dbg(dev, "\nScales:\n");
|
|
for (i = 0; i < 3; i++) {
|
|
dev_dbg(dev, " %d : ival: %u\n", i, cfg->scales[i].ival);
|
|
dev_dbg(dev, " %d : fval: %u\n", i, cfg->scales[i].fval);
|
|
}
|
|
dev_dbg(dev, "maxrange:\n");
|
|
dev_dbg(dev, " ival: %u ", cfg->max_range.ival);
|
|
dev_dbg(dev, " fval: %u\n", cfg->max_range.fval);
|
|
dev_dbg(dev, "resolution:\n");
|
|
dev_dbg(dev, " ival: %u ", cfg->resolution.ival);
|
|
dev_dbg(dev, " fval: %u\n", cfg->resolution.fval);
|
|
dev_dbg(dev, "milliamp:\n");
|
|
dev_dbg(dev, " ival: %u ", cfg->milliamp.ival);
|
|
dev_dbg(dev, " fval: %u\n", cfg->milliamp.fval);
|
|
dev_dbg(dev, "uncal_lo=%d\n", cfg->uncal_lo);
|
|
dev_dbg(dev, "uncal_hi=%d\n", cfg->uncal_hi);
|
|
dev_dbg(dev, "cal_lo=%d\n", cfg->cal_lo);
|
|
dev_dbg(dev, "cal_hi=%d\n", cfg->cal_hi);
|
|
dev_dbg(dev, "thresh_lo=%d\n", cfg->thresh_lo);
|
|
dev_dbg(dev, "thresh_hi=%d\n", cfg->thresh_hi);
|
|
dev_dbg(dev, "report_n=%d\n", cfg->report_n);
|
|
dev_dbg(dev, "float_significance=%s\n",
|
|
nvs_float_significances[cfg->float_significance]);
|
|
}
|
|
#endif
|
|
|
|
static void tegra_aon_shub_copy_cfg(struct tegra_aon_shub *shub,
|
|
struct aon_shub_sensor *sensor)
|
|
{
|
|
int len;
|
|
int i;
|
|
|
|
/* TODO: Should we rearrange sensor_cfg in nvs.h so that we
|
|
* can call memcpy() rather than individual field assignment.
|
|
*/
|
|
len = ARRAY_SIZE(sensor->name);
|
|
strncpy(sensor->name,
|
|
(char *)shub->shub_resp->data.cfg.name,
|
|
len);
|
|
sensor->name[len - 1] = '\0';
|
|
len = ARRAY_SIZE(sensor->part);
|
|
strncpy(sensor->part,
|
|
(char *)shub->shub_resp->data.cfg.part,
|
|
len);
|
|
sensor->part[len - 1] = '\0';
|
|
len = ARRAY_SIZE(sensor->vendor);
|
|
strncpy(sensor->vendor,
|
|
(char *)shub->shub_resp->data.cfg.vendor,
|
|
len);
|
|
sensor->vendor[len - 1] = '\0';
|
|
sensor->type = get_snsr_type(sensor->name);
|
|
sensor->cfg.name = sensor->name;
|
|
sensor->cfg.part = sensor->part;
|
|
sensor->cfg.vendor = sensor->vendor;
|
|
sensor->cfg.version = shub->shub_resp->data.cfg.version;
|
|
sensor->cfg.snsr_id = shub->shub_resp->data.cfg.snsr_id;
|
|
sensor->cfg.kbuf_sz = shub->shub_resp->data.cfg.kbuf_sz;
|
|
sensor->cfg.timestamp_sz =
|
|
shub->shub_resp->data.cfg.timestamp_sz;
|
|
sensor->cfg.snsr_data_n =
|
|
shub->shub_resp->data.cfg.snsr_data_n;
|
|
sensor->cfg.ch_n = shub->shub_resp->data.cfg.ch_n;
|
|
sensor->cfg.ch_n_max = shub->shub_resp->data.cfg.ch_n_max;
|
|
sensor->cfg.ch_sz = shub->shub_resp->data.cfg.ch_sz;
|
|
sensor->cfg.max_range.ival =
|
|
shub->shub_resp->data.cfg.max_range.ival;
|
|
sensor->cfg.max_range.fval =
|
|
shub->shub_resp->data.cfg.max_range.fval;
|
|
sensor->cfg.resolution.ival =
|
|
shub->shub_resp->data.cfg.resolution.ival;
|
|
sensor->cfg.resolution.fval =
|
|
shub->shub_resp->data.cfg.resolution.fval;
|
|
sensor->cfg.milliamp.ival =
|
|
shub->shub_resp->data.cfg.milliamp.ival;
|
|
sensor->cfg.milliamp.fval =
|
|
shub->shub_resp->data.cfg.milliamp.fval;
|
|
sensor->cfg.delay_us_min =
|
|
shub->shub_resp->data.cfg.delay_us_min;
|
|
sensor->cfg.delay_us_max =
|
|
shub->shub_resp->data.cfg.delay_us_max;
|
|
sensor->cfg.fifo_rsrv_evnt_cnt =
|
|
shub->shub_resp->data.cfg.fifo_rsrv_evnt_cnt;
|
|
sensor->cfg.fifo_max_evnt_cnt =
|
|
shub->shub_resp->data.cfg.fifo_max_evnt_cnt;
|
|
sensor->cfg.flags = shub->shub_resp->data.cfg.flags;
|
|
sensor->cfg.uncal_lo = shub->shub_resp->data.cfg.uncal_lo;
|
|
sensor->cfg.uncal_hi = shub->shub_resp->data.cfg.uncal_hi;
|
|
sensor->cfg.cal_lo = shub->shub_resp->data.cfg.cal_lo;
|
|
sensor->cfg.cal_hi = shub->shub_resp->data.cfg.cal_hi;
|
|
sensor->cfg.thresh_lo =
|
|
shub->shub_resp->data.cfg.thresh_lo;
|
|
sensor->cfg.thresh_hi =
|
|
shub->shub_resp->data.cfg.thresh_hi;
|
|
sensor->cfg.float_significance =
|
|
shub->shub_resp->data.cfg.float_significance;
|
|
sensor->cfg.scale.ival =
|
|
shub->shub_resp->data.cfg.scale.ival;
|
|
sensor->cfg.scale.fval =
|
|
shub->shub_resp->data.cfg.scale.fval;
|
|
sensor->cfg.offset.ival =
|
|
shub->shub_resp->data.cfg.offset.ival;
|
|
sensor->cfg.offset.fval =
|
|
shub->shub_resp->data.cfg.offset.fval;
|
|
for (i = 0; i < 3; i++) {
|
|
sensor->cfg.scales[i].ival =
|
|
shub->shub_resp->data.cfg.resolution.ival;
|
|
sensor->cfg.scales[i].fval =
|
|
shub->shub_resp->data.cfg.resolution.fval;
|
|
}
|
|
for (i = 0; i < 9; i++) {
|
|
sensor->cfg.matrix[i] =
|
|
shub->shub_resp->data.cfg.matrix[i];
|
|
}
|
|
}
|
|
|
|
static int tegra_aon_shub_get_cfg(struct tegra_aon_shub *shub, int remote_id)
|
|
{
|
|
int ret;
|
|
struct aon_shub_sensor *sensor;
|
|
|
|
if (remote_id >= shub->snsr_cnt) {
|
|
dev_err(shub->dev,
|
|
"Invalid sensor config index : %d\n", remote_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
shub->shub_req->req_type = AON_SHUB_REQUEST_SNSR_CFG;
|
|
shub->shub_req->data.cfg.index = remote_id;
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
IVC_TIMEOUT);
|
|
if (ret) {
|
|
dev_err(shub->dev, " %s No response from AON SHUB..!\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
|
|
sensor = devm_kzalloc(shub->dev, sizeof(struct aon_shub_sensor),
|
|
GFP_KERNEL);
|
|
if (!sensor)
|
|
return -ENOMEM;
|
|
|
|
shub->snsrs[remote_id] = sensor;
|
|
tegra_aon_shub_copy_cfg(shub, sensor);
|
|
#ifdef TEGRA_AON_SHUB_DBG_ENABLE
|
|
tegra_aon_shub_dbg_cfg(shub->dev, &sensor->cfg);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline bool is_chipid_valid(struct tegra_aon_shub *shub,
|
|
u8 chip_id,
|
|
const char *name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < shub->nchips; i++)
|
|
if (shub->chips[i].chip_id == chip_id)
|
|
return !strcmp(name, shub->chips[i].name);
|
|
|
|
return false;
|
|
}
|
|
|
|
static inline bool is_i2cid_valid(u8 i2c_id)
|
|
{
|
|
return (i2c_id >= I2CID_MIN && i2c_id <= I2CID_MAX);
|
|
}
|
|
|
|
static inline bool is_gpio_ctlr_id_valid(u8 ctlr_id)
|
|
{
|
|
return (ctlr_id >= GPIO_CHIP_ID_MIN && ctlr_id <= GPIO_CHIP_ID_MAX);
|
|
}
|
|
|
|
static int tegra_aon_shub_setup(struct tegra_aon_shub *shub,
|
|
struct device_node *np)
|
|
{
|
|
int ret;
|
|
struct device_node *cn;
|
|
struct device *dev;
|
|
struct aon_shub_init_setup_request *setup_req;
|
|
u32 chip_id, gpio_ctlr_id;
|
|
s32 rst_gpio;
|
|
u32 i2c_info[3];
|
|
u32 gpios[2];
|
|
int ngpios;
|
|
u32 aux_chip_info[2];
|
|
bool aux_slave = false;
|
|
const char *aux_chip_name;
|
|
int i = 0;
|
|
|
|
dev = shub->dev;
|
|
|
|
for_each_child_of_node(np, cn) {
|
|
ret = of_property_read_u32(cn, "chip_id", &chip_id);
|
|
if (ret) {
|
|
dev_err(dev, "missing <%s> property\n", "chip_id");
|
|
return ret;
|
|
}
|
|
|
|
if (!is_chipid_valid(shub, chip_id, cn->name)) {
|
|
dev_err(dev, "chip_id %d : chip_name %s mismatch\n",
|
|
(int)chip_id, cn->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_u32(cn, "gpio_ctlr_id", &gpio_ctlr_id);
|
|
if (ret) {
|
|
dev_err(dev, "missing <%s> property\n", "gpio_ctlr_id");
|
|
return ret;
|
|
}
|
|
|
|
if (!is_gpio_ctlr_id_valid(gpio_ctlr_id)) {
|
|
dev_err(dev, "Invalid gpio_ctlr_id: %d\n",
|
|
(int)gpio_ctlr_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* If reset GPIO is not present, its not an error.
|
|
*/
|
|
ret = of_property_read_u32(cn, "reset_gpio", &rst_gpio);
|
|
if (ret)
|
|
rst_gpio = -1;
|
|
|
|
ngpios = of_property_count_u32_elems(cn, "gpios");
|
|
if (ngpios < 0) {
|
|
dev_err(dev, "missing <%s> property\n", "gpios");
|
|
return ngpios;
|
|
}
|
|
|
|
if (ngpios > ARRAY_SIZE(gpios)) {
|
|
dev_err(dev, "Invalid element count of <%s> property\n",
|
|
"gpio");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_u32_array(cn, "gpios", gpios, ngpios);
|
|
if (ret) {
|
|
dev_err(dev, "<%s> property parsing failed : %d\n",
|
|
"gpio", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = of_property_read_u32_array(cn, "i2c_info", i2c_info, 3);
|
|
if (ret) {
|
|
dev_err(dev, "missing <%s> property\n", "i2c_info");
|
|
return ret;
|
|
}
|
|
|
|
if (!is_i2cid_valid(i2c_info[0])) {
|
|
dev_err(dev, "Invalid I2C controller id\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* If the aux_chip property is not present, then it is not
|
|
* an error as it is not necessary to have an aux slave
|
|
* device. However, if aux_chip property is present and the
|
|
* aux_chip_name property is missing, then it is an error.
|
|
*/
|
|
ret = of_property_read_u32_array(cn, "aux_chip",
|
|
aux_chip_info, 2);
|
|
if (ret >= 0) {
|
|
ret = of_property_read_string(cn, "aux_chip_name",
|
|
&aux_chip_name);
|
|
if (ret < 0) {
|
|
dev_err(dev, "missing %s property\n",
|
|
"aux_chip_name");
|
|
return ret;
|
|
}
|
|
if (!is_chipid_valid(shub, aux_chip_info[0],
|
|
aux_chip_name)) {
|
|
dev_err(dev,
|
|
"aux chip_id : chip_name mismatch\n");
|
|
return -EINVAL;
|
|
}
|
|
aux_slave = true;
|
|
} else {
|
|
/*
|
|
* aux_chip_info[1] holds the I2C address of the slave
|
|
* chip. If the aux_chip_id is < 0, we don't care about
|
|
* its I2C address. Only when the chip_id is validated
|
|
* the I2C addresses are validated on the remote side.
|
|
*/
|
|
aux_chip_info[0] = -1;
|
|
}
|
|
|
|
mutex_lock(&shub->shub_mutex);
|
|
shub->shub_req->req_type = AON_SHUB_REQUEST_INIT;
|
|
shub->shub_req->data.init.req = AON_SHUB_INIT_REQUEST_SETUP;
|
|
setup_req = &shub->shub_req->data.init.data.setup;
|
|
for (i = 0; i < ngpios; i++)
|
|
setup_req->gpios[i] = gpios[i];
|
|
setup_req->ngpios = ngpios;
|
|
setup_req->reset_gpio = rst_gpio;
|
|
setup_req->gpio_ctlr_id = gpio_ctlr_id;
|
|
setup_req->chip_id = chip_id;
|
|
setup_req->i2c_id = i2c_info[0];
|
|
setup_req->i2c_addr = i2c_info[2];
|
|
setup_req->slave_chip_id = aux_chip_info[0];
|
|
setup_req->slave_i2c_addr = aux_slave ? aux_chip_info[1] : 0;
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
IVC_TIMEOUT);
|
|
if (ret) {
|
|
dev_err(shub->dev, "No response from AON SHUB...!\n");
|
|
goto err_exit;
|
|
}
|
|
if (shub->shub_resp->data.init.init_type !=
|
|
AON_SHUB_INIT_REQUEST_SETUP) {
|
|
ret = -EIO;
|
|
dev_err(shub->dev,
|
|
"Invalid response to INIT_SETUP request\n");
|
|
goto err_exit;
|
|
}
|
|
|
|
ret = shub->shub_resp->data.init.status;
|
|
if (ret) {
|
|
dev_err(shub->dev, "%s setup failed ERR: %d\n",
|
|
cn->name, ret);
|
|
goto err_exit;
|
|
}
|
|
|
|
if (i >= shub->nchips) {
|
|
dev_err(shub->dev, "Invalid num of chip_nodes: %d\n",
|
|
i);
|
|
ret = -EINVAL;
|
|
goto err_exit;
|
|
}
|
|
shub->chip_nodes[i].dn = cn;
|
|
shub->chip_nodes[i].chip_id = chip_id;
|
|
i++;
|
|
if (i >= shub->nchips) {
|
|
dev_err(shub->dev, "Invalid num of chip_nodes: %d\n",
|
|
i);
|
|
ret = -EINVAL;
|
|
goto err_exit;
|
|
}
|
|
if (aux_slave) {
|
|
shub->chip_nodes[i].dn = cn;
|
|
shub->chip_nodes[i].chip_id = aux_chip_info[0];
|
|
i++;
|
|
}
|
|
|
|
if (shub->i2c_clk_rates[i2c_info[0] - I2CID_MIN] < i2c_info[1])
|
|
shub->i2c_clk_rates[i2c_info[0] - I2CID_MIN] =
|
|
i2c_info[1];
|
|
shub->chip_id_mask |= BIT(chip_id - 1);
|
|
aux_slave = false;
|
|
mutex_unlock(&shub->shub_mutex);
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_exit:
|
|
mutex_unlock(&shub->shub_mutex);
|
|
return ret;
|
|
}
|
|
|
|
static inline int tegra_aon_shub_count_sensor_chips(struct device_node *dn)
|
|
{
|
|
return of_get_child_count(dn);
|
|
}
|
|
|
|
static int tegra_aon_shub_get_snsr_chips(struct tegra_aon_shub *shub)
|
|
{
|
|
int ret;
|
|
struct aon_shub_snsr_chips_response *resp;
|
|
|
|
shub->shub_req->req_type = AON_SHUB_REQUEST_SNSR_CHIPS;
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
IVC_TIMEOUT);
|
|
if (ret) {
|
|
dev_err(shub->dev, "No response from AON SHUB...!\n");
|
|
return ret;
|
|
}
|
|
|
|
resp = &shub->shub_resp->data.snsr_chips;
|
|
shub->nchips = resp->nchips;
|
|
if (shub->nchips > AON_SHUB_MAX_CHIPS) {
|
|
dev_err(shub->dev, "Invalid shub->nchips..!\n");
|
|
return -EINVAL;
|
|
}
|
|
shub->chips = devm_kzalloc(shub->dev,
|
|
sizeof(struct snsr_chip) * resp->nchips,
|
|
GFP_KERNEL);
|
|
shub->chip_nodes = devm_kzalloc(shub->dev,
|
|
sizeof(struct shub_chip_node) * resp->nchips,
|
|
GFP_KERNEL);
|
|
if (!shub->chips || !shub->chip_nodes)
|
|
return -ENOMEM;
|
|
|
|
memcpy(shub->chips, resp->chips,
|
|
sizeof(struct snsr_chip) * resp->nchips);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_aon_shub_get_snsr_cnt(struct tegra_aon_shub *shub)
|
|
{
|
|
int ret = 0;
|
|
|
|
shub->shub_req->req_type = AON_SHUB_REQUEST_SYS;
|
|
shub->shub_req->data.sys.req = AON_SHUB_SYS_REQUEST_SNSR_CNT;
|
|
/*
|
|
* Work around bug 1986718: The very first HSP interrupt from
|
|
* SPE to CCPLEX is delayed by several hundred milliseconds for
|
|
* reasons unknown currently. The following timeout count of
|
|
* "15" was emperically derived over 120 reboots. The max delay
|
|
* seen was around 1.5 secs. To account for unexpected conditions,
|
|
* I am using twice the max currently.
|
|
*/
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
INIT_TOUT_COUNT * IVC_TIMEOUT);
|
|
if (ret) {
|
|
dev_err(shub->dev, "%s No response from AON SHUB...!\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
shub->snsr_cnt = shub->shub_resp->data.sys.snsr_cnt;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_aon_shub_preinit(struct tegra_aon_shub *shub)
|
|
{
|
|
int ret = 0;
|
|
|
|
mutex_lock(&shub->shub_mutex);
|
|
ret = tegra_aon_shub_get_snsr_cnt(shub);
|
|
if (ret)
|
|
goto err_exit;
|
|
|
|
shub->snsrs = devm_kzalloc(shub->dev,
|
|
sizeof(struct aon_shub_sensor *) *
|
|
shub->snsr_cnt,
|
|
GFP_KERNEL);
|
|
if (!shub->snsrs) {
|
|
ret = -ENOMEM;
|
|
goto err_exit;
|
|
}
|
|
|
|
ret = tegra_aon_shub_get_snsr_chips(shub);
|
|
if (ret)
|
|
dev_err(shub->dev, "shub_get_snsr_chips failed : %d\n", ret);
|
|
|
|
err_exit:
|
|
mutex_unlock(&shub->shub_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct device_node *tegra_aon_shub_find_node_by_chip(
|
|
struct tegra_aon_shub *shub,
|
|
u8 chip_id)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < shub->nchips; i++)
|
|
if (shub->chip_nodes[i].chip_id == chip_id)
|
|
return shub->chip_nodes[i].dn;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
static int tegra_aon_shub_init(struct tegra_aon_shub *shub)
|
|
{
|
|
int ret = 0;
|
|
int snsrs = 0;
|
|
int i;
|
|
int count;
|
|
bool i2c_inited = false;
|
|
u32 chip_id_msk;
|
|
u8 chip_id;
|
|
struct aon_shub_init_i2c_request *i2c_req;
|
|
struct aon_shub_init_snsrs_request *snsrs_req;
|
|
struct aon_shub_chip_cfg_ids_response *cfg_ids_resp;
|
|
struct device_node *dn;
|
|
s8 cfg[AON_SHUB_MAX_SNSRS];
|
|
|
|
mutex_lock(&shub->shub_mutex);
|
|
for (i = 0; i < ARRAY_SIZE(shub->i2c_clk_rates); i++) {
|
|
if (!shub->i2c_clk_rates[i])
|
|
continue;
|
|
shub->shub_req->req_type = AON_SHUB_REQUEST_INIT;
|
|
shub->shub_req->data.init.req = AON_SHUB_INIT_REQUEST_I2C;
|
|
i2c_req = &shub->shub_req->data.init.data.i2c_init;
|
|
i2c_req->i2c_id = i + 1;
|
|
i2c_req->clk_rate = shub->i2c_clk_rates[i];
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
IVC_TIMEOUT);
|
|
if (ret) {
|
|
dev_err(shub->dev,
|
|
"%s : No response from AON SHUB...!\n",
|
|
__func__);
|
|
goto err_exit;
|
|
}
|
|
if (shub->shub_resp->data.init.init_type !=
|
|
AON_SHUB_INIT_REQUEST_I2C) {
|
|
ret = -EIO;
|
|
dev_err(shub->dev,
|
|
"Invalid response to I2C init request\n");
|
|
goto err_exit;
|
|
}
|
|
if (shub->shub_resp->data.init.status) {
|
|
dev_err(shub->dev, "I2C init failed\n");
|
|
ret = -1;
|
|
goto err_exit;
|
|
}
|
|
i2c_inited = true;
|
|
}
|
|
if (!i2c_inited) {
|
|
dev_err(shub->dev, "No I2C controllers inited\n");
|
|
ret = -1;
|
|
goto err_exit;
|
|
}
|
|
shub->shub_req->req_type = AON_SHUB_REQUEST_INIT;
|
|
shub->shub_req->data.init.req = AON_SHUB_INIT_REQUEST_SNSR;
|
|
snsrs_req = &shub->shub_req->data.init.data.snsrs_init;
|
|
snsrs_req->chip_id_mask = shub->chip_id_mask;
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
INIT_TOUT_COUNT * IVC_TIMEOUT);
|
|
if (ret) {
|
|
dev_err(shub->dev, "%s : No response from AON SHUB...!\n",
|
|
__func__);
|
|
goto err_exit;
|
|
}
|
|
if (shub->shub_resp->data.init.init_type !=
|
|
AON_SHUB_INIT_REQUEST_SNSR) {
|
|
ret = -EIO;
|
|
dev_err(shub->dev, "Invalid response to sensor init request\n");
|
|
goto err_exit;
|
|
}
|
|
if (shub->shub_resp->data.init.status) {
|
|
dev_err(shub->dev, "Sensors init failed\n");
|
|
ret = -1;
|
|
goto err_exit;
|
|
}
|
|
chip_id_msk = shub->chip_id_mask;
|
|
while (chip_id_msk) {
|
|
chip_id = __builtin_ctz(chip_id_msk);
|
|
chip_id_msk &= ~BIT(chip_id);
|
|
chip_id++;
|
|
dn = tegra_aon_shub_find_node_by_chip(shub, chip_id);
|
|
if (dn == NULL) {
|
|
dev_err(shub->dev,
|
|
"No device node for chip %d\n", chip_id);
|
|
ret = -EINVAL;
|
|
goto err_exit;
|
|
}
|
|
shub->shub_req->req_type = AON_SHUB_REQUEST_CHIP_CFG_IDS;
|
|
shub->shub_req->data.cfg_ids.chip_id = chip_id;
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
IVC_TIMEOUT);
|
|
if (ret) {
|
|
dev_err(shub->dev,
|
|
"chip_cfg_ids : No response from AON SHUB!\n");
|
|
goto err_exit;
|
|
}
|
|
cfg_ids_resp = &shub->shub_resp->data.cfg_ids;
|
|
count = cfg_ids_resp->num_snsrs;
|
|
if (count > AON_SHUB_MAX_SNSRS) {
|
|
dev_err(shub->dev,
|
|
"Invalid number of sensors supported..!\n");
|
|
goto err_exit;
|
|
}
|
|
for (i = 0; i < count; i++)
|
|
cfg[i] = cfg_ids_resp->ids[i];
|
|
for (i = 0; i < count; i++) {
|
|
if (cfg[i] == -1)
|
|
continue;
|
|
ret = tegra_aon_shub_get_cfg(shub, cfg[i]);
|
|
if (ret) {
|
|
dev_err(shub->dev,
|
|
"shub_get_cfg() failed for id : %d\n",
|
|
cfg[i]);
|
|
goto err_exit;
|
|
}
|
|
nvs_of_dt(dn, &shub->snsrs[cfg[i]]->cfg, NULL);
|
|
shub->snsrs[cfg[i]]->genable = true;
|
|
}
|
|
}
|
|
mutex_unlock(&shub->shub_mutex);
|
|
|
|
shub->nvs = nvs_iio();
|
|
if (shub->nvs == NULL)
|
|
return -ENODEV;
|
|
|
|
for (i = 0; i < shub->snsr_cnt; i++) {
|
|
if (shub->snsrs[i] == NULL || !shub->snsrs[i]->genable)
|
|
continue;
|
|
ret = shub->nvs->probe(&shub->snsrs[i]->nvs_st, (void *)shub,
|
|
shub->dev, &aon_shub_nvs_fn,
|
|
&shub->snsrs[i]->cfg);
|
|
if (!ret) {
|
|
shub->active_snsr_msk |= BIT(i);
|
|
snsrs++;
|
|
}
|
|
}
|
|
if (!snsrs) {
|
|
dev_err(shub->dev, "nvs_probe() failed..!\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_exit:
|
|
mutex_unlock(&shub->shub_mutex);
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_aon_shub_probe(struct platform_device *pdev)
|
|
{
|
|
struct tegra_aon_shub *shub;
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = pdev->dev.of_node;
|
|
int ret = 0;
|
|
int num_sensors;
|
|
|
|
dev_dbg(dev, "AON SHUB driver probe()\n");
|
|
|
|
if (!np) {
|
|
dev_err(dev, "tegra_aon_shub: DT data required\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
shub = devm_kzalloc(&pdev->dev, sizeof(*shub), GFP_KERNEL);
|
|
if (!shub)
|
|
return -ENOMEM;
|
|
|
|
dev_set_drvdata(&pdev->dev, shub);
|
|
shub->dev = &pdev->dev;
|
|
shub->cl.dev = &pdev->dev;
|
|
shub->cl.tx_block = true;
|
|
shub->cl.tx_tout = TX_BLOCK_PERIOD;
|
|
shub->cl.knows_txdone = false;
|
|
shub->cl.rx_callback = tegra_aon_shub_mbox_rcv_msg;
|
|
shub->cl.tx_done = tegra_aon_shub_mbox_tx_done;
|
|
shub->mbox = mbox_request_channel(&shub->cl, 0);
|
|
if (IS_ERR(shub->mbox)) {
|
|
ret = PTR_ERR(shub->mbox);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_warn(&pdev->dev, "can't get mailbox chan (%d)\n",
|
|
(int)PTR_ERR(shub->mbox));
|
|
return ret;
|
|
}
|
|
dev_dbg(dev, "shub->mbox = %p\n", shub->mbox);
|
|
|
|
shub->shub_req = devm_kzalloc(&pdev->dev, sizeof(*shub->shub_req),
|
|
GFP_KERNEL);
|
|
if (!shub->shub_req) {
|
|
ret = -ENOMEM;
|
|
goto exit_free_mbox;
|
|
}
|
|
|
|
shub->shub_resp = devm_kzalloc(&pdev->dev, sizeof(*shub->shub_resp),
|
|
GFP_KERNEL);
|
|
if (!shub->shub_resp) {
|
|
ret = -ENOMEM;
|
|
goto exit_free_mbox;
|
|
}
|
|
|
|
shub->wait_on = devm_kzalloc(&pdev->dev,
|
|
sizeof(struct completion), GFP_KERNEL);
|
|
if (!shub->wait_on) {
|
|
ret = -ENOMEM;
|
|
goto exit_free_mbox;
|
|
}
|
|
init_completion(shub->wait_on);
|
|
mutex_init(&shub->shub_mutex);
|
|
|
|
num_sensors = tegra_aon_shub_count_sensor_chips(np);
|
|
if (num_sensors <= 0) {
|
|
dev_err(dev, "No sensors on the shub\n");
|
|
ret = -EINVAL;
|
|
goto exit_free_mbox;
|
|
}
|
|
|
|
ret = tegra_aon_shub_preinit(shub);
|
|
if (ret) {
|
|
dev_err(dev, "shub pre-init failed\n");
|
|
goto exit_free_mbox;
|
|
}
|
|
|
|
ret = tegra_aon_shub_setup(shub, np);
|
|
if (ret) {
|
|
dev_err(dev, "shub setup failed\n");
|
|
goto exit_free_mbox;
|
|
}
|
|
|
|
ret = tegra_aon_shub_init(shub);
|
|
if (ret) {
|
|
dev_err(dev, "shub init failed\n");
|
|
goto exit_free_mbox;
|
|
}
|
|
|
|
shub->adjust_ts_counter = 0;
|
|
#define _PICO_SECS (1000000000000ULL)
|
|
shub->ts_res_ns = (_PICO_SECS / (u64)arch_timer_get_cntfrq())/1000;
|
|
#undef _PICO_SECS
|
|
shub->ts_adjustment = get_ts_adjustment(shub->ts_res_ns);
|
|
|
|
dev_info(&pdev->dev, "tegra_aon_shub_driver_probe() OK\n");
|
|
|
|
return 0;
|
|
|
|
exit_free_mbox:
|
|
mbox_free_channel(shub->mbox);
|
|
dev_err(&pdev->dev, "tegra_aon_shub_driver_probe() FAILED\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_aon_shub_remove(struct platform_device *pdev)
|
|
{
|
|
struct tegra_aon_shub *shub;
|
|
|
|
shub = dev_get_drvdata(&pdev->dev);
|
|
mbox_free_channel(shub->mbox);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int tegra_aon_shub_suspend(struct device *dev)
|
|
{
|
|
struct tegra_aon_shub *shub = dev_get_drvdata(dev);
|
|
struct aon_shub_pm_request *pm_req;
|
|
int ret = 0;
|
|
u32 en_snsrs, snsr;
|
|
|
|
en_snsrs = shub->active_snsr_msk;
|
|
while (en_snsrs) {
|
|
snsr = __builtin_ctz(en_snsrs);
|
|
ret = tegra_aon_shub_enable(shub,
|
|
shub->snsrs[snsr]->cfg.snsr_id,
|
|
0);
|
|
if (ret)
|
|
return ret;
|
|
en_snsrs &= ~BIT(snsr);
|
|
}
|
|
|
|
mutex_lock(&shub->shub_mutex);
|
|
shub->shub_req->req_type = AON_SHUB_REQUEST_SYS;
|
|
shub->shub_req->data.sys.req = AON_SHUB_SYS_REQUEST_PM;
|
|
pm_req = &shub->shub_req->data.sys.data.pm;
|
|
pm_req->flags = AON_SHUB_PM_REQUEST_SUSPEND;
|
|
pm_req->chip_id_msk = shub->chip_id_mask;
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
IVC_TIMEOUT);
|
|
if (ret)
|
|
dev_err(shub->dev, "AON SHUB Suspend ERR: %d\n", ret);
|
|
mutex_unlock(&shub->shub_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_aon_shub_resume(struct device *dev)
|
|
{
|
|
struct tegra_aon_shub *shub = dev_get_drvdata(dev);
|
|
struct aon_shub_pm_request *pm_req;
|
|
int ret;
|
|
|
|
mutex_lock(&shub->shub_mutex);
|
|
shub->shub_req->req_type = AON_SHUB_REQUEST_SYS;
|
|
shub->shub_req->data.sys.req = AON_SHUB_SYS_REQUEST_PM;
|
|
pm_req = &shub->shub_req->data.sys.data.pm;
|
|
pm_req->flags = AON_SHUB_PM_REQUEST_RESUME;
|
|
pm_req->chip_id_msk = shub->chip_id_mask;
|
|
ret = tegra_aon_shub_ivc_msg_send(shub,
|
|
sizeof(struct aon_shub_request),
|
|
IVC_TIMEOUT);
|
|
if (ret)
|
|
dev_err(shub->dev, "AON SHUB Resume ERR: %d\n", ret);
|
|
mutex_unlock(&shub->shub_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops tegra_aon_shub_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(tegra_aon_shub_suspend, tegra_aon_shub_resume)
|
|
};
|
|
#endif
|
|
|
|
static const struct of_device_id tegra_aon_shub_of_match[] = {
|
|
{
|
|
.compatible = "nvidia,tegra186_aon_shub",
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, tegra_aon_shub_of_match);
|
|
|
|
static struct platform_driver tegra_aon_shub_driver = {
|
|
.driver = {
|
|
.name = "tegra-aon-shub",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(tegra_aon_shub_of_match),
|
|
#ifdef CONFIG_PM_SLEEP
|
|
.pm = &tegra_aon_shub_pm_ops,
|
|
#endif
|
|
},
|
|
.probe = tegra_aon_shub_probe,
|
|
.remove = tegra_aon_shub_remove,
|
|
};
|
|
module_platform_driver(tegra_aon_shub_driver);
|
|
|
|
MODULE_DESCRIPTION("NVIDIA Tegra186 AON Sensor Hub Driver");
|
|
MODULE_AUTHOR("NVIDIA");
|
|
MODULE_LICENSE("GPL v2");
|