forked from rrcarlosr/Jetpack
1894 lines
48 KiB
C
1894 lines
48 KiB
C
/* Copyright (c) 2016-2018, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that 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.
|
|
*/
|
|
|
|
/* The NVS = NVidia Sensor framework */
|
|
/* This NVS kernel driver has a test mechanism for sending specific data up the
|
|
* SW stack by writing the requested data value to the ch sysfs attribute.
|
|
* That data will be sent anytime there would normally be new data from the
|
|
* device.
|
|
* The feature is disabled whenever the device is disabled. It remains
|
|
* disabled until the ch sysfs attribute is written to again.
|
|
*/
|
|
/* The NVS HAL will use the scale and offset sysfs attributes to modify the
|
|
* data using the following formula: (data * scale) + offset
|
|
* A scale value of 0 disables scale.
|
|
* A scale value of 1 puts the NVS HAL into calibration mode where the scale
|
|
* and offset are read everytime the data is read to allow realtime calibration
|
|
* of the scale and offset values to be used in the device tree parameters.
|
|
* Keep in mind the data is buffered but the NVS HAL will display the data and
|
|
* scale/offset parameters in the log.
|
|
*/
|
|
/* This module automatically handles on-change sensors by testing for allowed
|
|
* report rate and whether data has changed. This allows sensors that are
|
|
* on-change in name only that normally stream data to behave as on-change.
|
|
*/
|
|
/* This module automatically handles one-shot sensors by disabling the sensor
|
|
* after an event.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/of.h>
|
|
#include <linux/string.h>
|
|
#include "nvs_sysfs.h"
|
|
|
|
#define NVS_SYSFS_DRIVER_VERSION (3)
|
|
|
|
|
|
static const char * const nvs_snsr_names[] = {
|
|
[0] = "generic_sensor",
|
|
[SENSOR_TYPE_ACCELEROMETER] = "accelerometer",
|
|
[SENSOR_TYPE_MAGNETIC_FIELD] = "magnetic_field",
|
|
[SENSOR_TYPE_ORIENTATION] = "orientation",
|
|
[SENSOR_TYPE_GYROSCOPE] = "gyroscope",
|
|
[SENSOR_TYPE_LIGHT] = "light",
|
|
[SENSOR_TYPE_PRESSURE] = "pressure",
|
|
[SENSOR_TYPE_TEMPERATURE] = "temperature",
|
|
[SENSOR_TYPE_PROXIMITY] = "proximity",
|
|
[SENSOR_TYPE_GRAVITY] = "gravity",
|
|
[SENSOR_TYPE_LINEAR_ACCELERATION] = "linear_acceleration",
|
|
[SENSOR_TYPE_ROTATION_VECTOR] = "rotation_vector",
|
|
[SENSOR_TYPE_RELATIVE_HUMIDITY] = "relative_humidity",
|
|
[SENSOR_TYPE_AMBIENT_TEMPERATURE] = "ambient_temperature",
|
|
[SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED] =
|
|
"magnetic_field_uncalibrated",
|
|
[SENSOR_TYPE_GAME_ROTATION_VECTOR] = "game_rotation_vector",
|
|
[SENSOR_TYPE_GYROSCOPE_UNCALIBRATED] = "gyroscope_uncalibrated",
|
|
[SENSOR_TYPE_SIGNIFICANT_MOTION] = "significant_motion",
|
|
[SENSOR_TYPE_STEP_DETECTOR] = "step_detector",
|
|
[SENSOR_TYPE_STEP_COUNTER] = "step_counter",
|
|
[SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR] =
|
|
"geomagnetic_rotation_vector",
|
|
[SENSOR_TYPE_HEART_RATE] = "heart_rate",
|
|
[SENSOR_TYPE_TILT_DETECTOR] = "tilt_detector",
|
|
[SENSOR_TYPE_WAKE_GESTURE] = "wake_gesture",
|
|
[SENSOR_TYPE_GLANCE_GESTURE] = "glance_gesture",
|
|
[SENSOR_TYPE_PICK_UP_GESTURE] = "pick_up_gesture",
|
|
[SENSOR_TYPE_WRIST_TILT_GESTURE] = "wrist_tilt_gesture",
|
|
[SENSOR_TYPE_DEVICE_ORIENTATION] = "device_orientation",
|
|
[SENSOR_TYPE_POSE_6DOF] = "pose_6dof",
|
|
[SENSOR_TYPE_STATIONARY_DETECT] = "stationary_detect",
|
|
[SENSOR_TYPE_MOTION_DETECT] = "motion_detect",
|
|
[SENSOR_TYPE_HEART_BEAT] = "heart_beat",
|
|
[SENSOR_TYPE_DYNAMIC_SENSOR_META] = "dynamic_sensor_meta",
|
|
[SENSOR_TYPE_ADDITIONAL_INFO] = "additional_info",
|
|
};
|
|
|
|
enum NVS_DBG {
|
|
NVS_INFO_DATA = 0,
|
|
NVS_INFO_VER,
|
|
NVS_INFO_ERRS,
|
|
NVS_INFO_RESET,
|
|
NVS_INFO_REGS,
|
|
NVS_INFO_CFG,
|
|
NVS_INFO_DBG,
|
|
NVS_INFO_DBG_DATA,
|
|
NVS_INFO_DBG_BUF,
|
|
NVS_INFO_DBG_IRQ,
|
|
NVS_INFO_LIMIT_MAX,
|
|
NVS_INFO_DBG_KEY = 0x131071D,
|
|
};
|
|
|
|
|
|
static inline struct nvs_state *dev_to_nvs_state(struct device *dev)
|
|
{
|
|
return dev_get_drvdata(dev);
|
|
}
|
|
|
|
void nvs_mutex_lock(void *handle)
|
|
{
|
|
struct nvs_state *st = (struct nvs_state *)handle;
|
|
|
|
if (st)
|
|
mutex_lock(&st->mutex);
|
|
}
|
|
|
|
void nvs_mutex_unlock(void *handle)
|
|
{
|
|
struct nvs_state *st = (struct nvs_state *)handle;
|
|
|
|
if (st)
|
|
mutex_unlock(&st->mutex);
|
|
}
|
|
|
|
static ssize_t nvs_dbg_cfg(struct nvs_state *st, char *buf)
|
|
{
|
|
unsigned int i;
|
|
ssize_t t;
|
|
|
|
t = snprintf(buf, PAGE_SIZE, "name=%s\n", st->cfg->name);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "snsr_id=%d\n",
|
|
st->cfg->snsr_id);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "timestamp_sz=%d\n",
|
|
st->cfg->timestamp_sz);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "snsr_data_n=%d\n",
|
|
st->cfg->snsr_data_n);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "kbuf_sz=%d\n",
|
|
st->cfg->kbuf_sz);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "ch_n=%u\n", st->cfg->ch_n);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "ch_sz=%d\n", st->cfg->ch_sz);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "ch_inf=%p\n", st->cfg->ch_inf);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "delay_us_min=%u\n",
|
|
st->cfg->delay_us_min);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "delay_us_max=%u\n",
|
|
st->cfg->delay_us_max);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "uuid: ");
|
|
for (i = 0; i < 16; i++)
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "%02x ",
|
|
st->cfg->uuid[i]);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "\nmatrix: ");
|
|
for (i = 0; i < 9; i++)
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "%hhd ",
|
|
st->cfg->matrix[i]);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "\nuncal_lo=%d\n",
|
|
st->cfg->uncal_lo);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "uncal_hi=%d\n",
|
|
st->cfg->uncal_hi);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "cal_lo=%d\n", st->cfg->cal_lo);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "cal_hi=%d\n", st->cfg->cal_hi);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "thresh_lo=%d\n",
|
|
st->cfg->thresh_lo);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "thresh_hi=%d\n",
|
|
st->cfg->thresh_hi);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "report_n=%d\n",
|
|
st->cfg->report_n);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "float_significance=%s\n",
|
|
nvs_float_significances[st->cfg->float_significance]);
|
|
return t;
|
|
}
|
|
|
|
/* dummy enable function to support client's NULL function pointer */
|
|
static int nvs_fn_dev_enable(void *client, int snsr_id, int enable)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int nvs_disable(struct nvs_state *st)
|
|
{
|
|
int ret;
|
|
|
|
ret = st->fn_dev->enable(st->client, st->cfg->snsr_id, 0);
|
|
if (!ret) {
|
|
st->enabled = 0;
|
|
st->dbg_data_lock = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t nvs_ch2buf(struct nvs_state *st, unsigned int ch,
|
|
char *buf, size_t buf_n, ssize_t t, bool dbg)
|
|
{
|
|
unsigned int i;
|
|
unsigned int n;
|
|
s64 val;
|
|
|
|
n = st->buf_ch[ch].byte_n;
|
|
if (n) {
|
|
if (n <= sizeof(val)) {
|
|
val = 0;
|
|
memcpy(&val, &st->buf[st->buf_ch[ch].buf_i], n);
|
|
if (st->buf_ch[ch].sign) {
|
|
i = sizeof(val) - n;
|
|
if (i) {
|
|
i *= 8;
|
|
val <<= i;
|
|
val >>= i;
|
|
}
|
|
if (dbg)
|
|
t += snprintf(buf + t, buf_n - t,
|
|
"%lld (0x", val);
|
|
else
|
|
return snprintf(buf + t, buf_n - t,
|
|
"%lld\n", val);
|
|
} else {
|
|
if (dbg)
|
|
t += snprintf(buf + t, buf_n - t,
|
|
"%llu (0x", val);
|
|
else
|
|
return snprintf(buf + t, buf_n - t,
|
|
"%llu\n", (u64)val);
|
|
}
|
|
} else {
|
|
if (dbg)
|
|
t += snprintf(buf + t, buf_n - t, "? (0x");
|
|
else
|
|
t += snprintf(buf + t, buf_n - t, "(0x");
|
|
}
|
|
|
|
for (i = n - 1; i > 0; i--)
|
|
t += snprintf(buf + t, buf_n - t, "%02X",
|
|
st->buf[st->buf_ch[ch].buf_i + i]);
|
|
|
|
if (dbg)
|
|
t += snprintf(buf + t, buf_n - t, "%02X) ",
|
|
st->buf[st->buf_ch[ch].buf_i]);
|
|
else
|
|
t += snprintf(buf + t, buf_n - t, "%02X\n",
|
|
st->buf[st->buf_ch[ch].buf_i]);
|
|
return t;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t nvs_dbg_data(struct nvs_state *st, char *buf, size_t buf_n)
|
|
{
|
|
ssize_t t;
|
|
unsigned int ch;
|
|
unsigned int n = st->ch_n - 1;
|
|
|
|
t = snprintf(buf, PAGE_SIZE, "%s: ", st->cfg->name);
|
|
for (ch = 0; ch < n; ch++) {
|
|
if (!(st->enabled & (1 << ch))) {
|
|
t += snprintf(buf + t, buf_n - t, "disabled ");
|
|
continue;
|
|
}
|
|
|
|
t += nvs_ch2buf(st, ch, buf, buf_n, t, true);
|
|
}
|
|
t += snprintf(buf + t, buf_n - t, "ts=%lld ts_diff=%lld\n",
|
|
st->ts, st->ts_diff);
|
|
return t;
|
|
}
|
|
|
|
static int nvs_buf_push(struct nvs_state *st, unsigned char *data, s64 ts)
|
|
{
|
|
bool push = true;
|
|
char char_buf[128];
|
|
unsigned int i;
|
|
unsigned int n;
|
|
unsigned int ret_n = 0;
|
|
int ret = 0;
|
|
|
|
n = st->ch_n - 1; /* - timestamp channel */
|
|
/* It's possible to have events without data (n == 0).
|
|
* In this case, just the timestamp is sent.
|
|
*/
|
|
if (n && data) {
|
|
if (st->on_change)
|
|
/* on-change needs data change for push */
|
|
push = false;
|
|
for (i = 0; i < n; i++) {
|
|
if (!(st->enabled & (1 << i)))
|
|
continue;
|
|
|
|
if (st->on_change) {
|
|
/* wasted cycles when st->first_push
|
|
* but saved cycles in the long run.
|
|
*/
|
|
ret = memcmp(&st->buf[st->buf_ch[i].buf_i],
|
|
&data[st->buf_ch[i].buf_i],
|
|
st->buf_ch[i].byte_n);
|
|
if (ret)
|
|
/* data changed */
|
|
push = true;
|
|
}
|
|
if (!(st->dbg_data_lock & (1 << i)))
|
|
memcpy(&st->buf[st->buf_ch[i].buf_i],
|
|
&data[st->buf_ch[i].buf_i],
|
|
st->buf_ch[i].byte_n);
|
|
ret_n += st->buf_ch[i].byte_n;
|
|
}
|
|
}
|
|
|
|
if (st->first_push || !data)
|
|
/* first push || pushing just timestamp */
|
|
push = true;
|
|
if (ts) {
|
|
st->ts_diff = ts - st->ts;
|
|
if (st->ts_diff < 0)
|
|
dev_err(st->dev, "%s %s ts_diff=%lld\n",
|
|
__func__, st->cfg->name, st->ts_diff);
|
|
else if (st->on_change && (st->ts_diff <
|
|
(s64)st->us_period * 1000)) {
|
|
/* data rate faster than requested */
|
|
if (!st->first_push)
|
|
push = false;
|
|
}
|
|
} else {
|
|
st->flush = false;
|
|
if (*st->fn_dev->sts & (NVS_STS_SPEW_MSG | NVS_STS_SPEW_DATA))
|
|
dev_info(st->dev, "%s %s FLUSH\n",
|
|
__func__, st->cfg->name);
|
|
}
|
|
memcpy(&st->buf[st->buf_ch[n].buf_i], &ts,
|
|
st->buf_ch[n].byte_n);
|
|
if (push) {
|
|
ret = st->kif_fn->push(st);
|
|
if (!ret) {
|
|
if (ts) {
|
|
st->first_push = false;
|
|
st->ts = ts; /* log ts push */
|
|
if (st->one_shot)
|
|
/* disable one-shot after event */
|
|
nvs_disable(st);
|
|
}
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_BUF) {
|
|
n = st->buf_ch[n].buf_i + st->buf_ch[n].byte_n;
|
|
for (i = 0; i < n; i++)
|
|
dev_info(st->dev, "%s buf[%u]=%02X\n",
|
|
st->cfg->name, i, st->buf[i]);
|
|
dev_info(st->dev, "%s ts=%lld diff=%lld\n",
|
|
st->cfg->name, ts, st->ts_diff);
|
|
}
|
|
}
|
|
}
|
|
if ((*st->fn_dev->sts & NVS_STS_SPEW_DATA) && ts) {
|
|
nvs_dbg_data(st, char_buf, sizeof(char_buf));
|
|
dev_info(st->dev, "%s", char_buf);
|
|
}
|
|
if (!ret)
|
|
/* return pushed byte count from data if no error.
|
|
* external entity can use as offset to next data set.
|
|
*/
|
|
ret = ret_n;
|
|
return ret;
|
|
}
|
|
|
|
int nvs_handler(void *handle, void *buffer, s64 ts)
|
|
{
|
|
struct nvs_state *st = (struct nvs_state *)handle;
|
|
unsigned char *buf = buffer;
|
|
int ret = 0;
|
|
|
|
if (st)
|
|
ret = nvs_buf_push(st, buf, ts);
|
|
return ret;
|
|
}
|
|
|
|
static void nvs_report_mode(struct nvs_state *st)
|
|
{
|
|
/* Currently this is called once during initialization. However, there
|
|
* may be mechanisms where this is allowed to change at runtime, hence
|
|
* this function above nvs_attr_store for st->cfg->flags.
|
|
*/
|
|
st->on_change = false;
|
|
st->one_shot = false;
|
|
st->special = false;
|
|
switch (st->cfg->flags & REPORTING_MODE_MASK) {
|
|
case SENSOR_FLAG_ON_CHANGE_MODE:
|
|
st->on_change = true;
|
|
break;
|
|
|
|
case SENSOR_FLAG_ONE_SHOT_MODE:
|
|
st->one_shot = true;
|
|
break;
|
|
|
|
case SENSOR_FLAG_SPECIAL_REPORTING_MODE:
|
|
st->special = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static ssize_t nvs_attr_ret(struct nvs_state *st, char *buf,
|
|
int ival, int fval)
|
|
{
|
|
if (st->cfg->float_significance == NVS_FLOAT_NANO) {
|
|
if (fval < 0)
|
|
return snprintf(buf, PAGE_SIZE,
|
|
"-%d.%09u\n", ival, -fval);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d.%09u\n", ival, fval);
|
|
}
|
|
|
|
if (fval < 0)
|
|
return snprintf(buf, PAGE_SIZE, "-%d.%06u\n", ival, -fval);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d.%06u\n", ival, fval);
|
|
}
|
|
|
|
static int nvs_attr_float(struct nvs_state *st, const char *buf,
|
|
int *ival, int *fval)
|
|
{
|
|
bool neg = false;
|
|
bool i_part = true;
|
|
int i = 0;
|
|
int f = 0;
|
|
int fs;
|
|
|
|
if (st->cfg->float_significance == NVS_FLOAT_NANO)
|
|
fs = NVS_FLOAT_SIGNIFICANCE_NANO;
|
|
else
|
|
fs = NVS_FLOAT_SIGNIFICANCE_MICRO;
|
|
if (buf[0] == '-') {
|
|
neg = true;
|
|
buf++;
|
|
} else if (buf[0] == '+') {
|
|
buf++;
|
|
}
|
|
|
|
while (*buf) {
|
|
if ('0' <= *buf && *buf <= '9') {
|
|
if (i_part) {
|
|
i = i * 10 + *buf - '0';
|
|
} else {
|
|
f += fs * (*buf - '0');
|
|
fs /= 10;
|
|
}
|
|
} else if (*buf == '\n') {
|
|
if (*(buf + 1) == '\0')
|
|
break;
|
|
else
|
|
return -EINVAL;
|
|
} else if (*buf == '.' && i_part) {
|
|
i_part = false;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
buf++;
|
|
}
|
|
|
|
if (neg) {
|
|
if (i)
|
|
i = -i;
|
|
else
|
|
f = -f;
|
|
}
|
|
|
|
*ival = i;
|
|
*fval = f;
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t nvs_attr_ch_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
struct nvs_dev_attr *nda = to_nvs_dev_attr(attr);
|
|
|
|
return nvs_ch2buf(st, nda->channel, buf, PAGE_SIZE, 0, false);
|
|
}
|
|
|
|
static ssize_t nvs_attr_ch_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
struct nvs_dev_attr *nda = to_nvs_dev_attr(attr);
|
|
unsigned int ch = nda->channel;
|
|
int ret;
|
|
u64 val;
|
|
|
|
if (kstrtou64(buf, 0, &val))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&st->mutex);
|
|
if (st->shutdown || st->suspend || (*st->fn_dev->sts &
|
|
(NVS_STS_SHUTDOWN | NVS_STS_SUSPEND))) {
|
|
ret = -EPERM;
|
|
} else {
|
|
/* writing to ch is a debug feature allowing a sticky data
|
|
* value to be pushed up and the same value pushed until the
|
|
* device is turned off.
|
|
*/
|
|
memcpy(&st->buf[st->buf_ch[ch].buf_i], &val,
|
|
st->buf_ch[ch].byte_n);
|
|
st->dbg_data_lock |= (1 << ch);
|
|
ret = nvs_buf_push(st, st->buf, nvs_timestamp());
|
|
if (ret > 0)
|
|
ret = 0;
|
|
}
|
|
mutex_unlock(&st->mutex);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_enable_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
ssize_t ret = -EINVAL;
|
|
|
|
if (st->fn_dev->enable != nvs_fn_dev_enable) {
|
|
mutex_lock(&st->mutex);
|
|
ret = snprintf(buf, PAGE_SIZE, "%X\n",
|
|
st->fn_dev->enable(st->client,
|
|
st->cfg->snsr_id, -1));
|
|
mutex_unlock(&st->mutex);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t nvs_attr_enable_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
unsigned int enable;
|
|
int ret = 0;
|
|
|
|
if (kstrtouint(buf, 0, &enable))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&st->mutex);
|
|
if (st->shutdown || st->suspend || (*st->fn_dev->sts &
|
|
(NVS_STS_SHUTDOWN | NVS_STS_SUSPEND))) {
|
|
ret = -EPERM;
|
|
} else {
|
|
if (enable) {
|
|
st->first_push = true;
|
|
ret = st->fn_dev->enable(st->client, st->cfg->snsr_id,
|
|
enable);
|
|
} else {
|
|
ret = nvs_disable(st);
|
|
}
|
|
}
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_MSG)
|
|
dev_info(st->dev, "%s %s %X->%X ret=%d",
|
|
st->cfg->name, __func__, st->enabled, enable, ret);
|
|
if (!ret)
|
|
st->enabled = enable;
|
|
mutex_unlock(&st->mutex);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_fmec_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", st->cfg->fifo_max_evnt_cnt);
|
|
}
|
|
|
|
static ssize_t nvs_attr_frec_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", st->cfg->fifo_rsrv_evnt_cnt);
|
|
}
|
|
|
|
static ssize_t nvs_attr_flags_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", st->cfg->flags);
|
|
}
|
|
|
|
static ssize_t nvs_attr_flags_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
unsigned int flags_old;
|
|
unsigned int flags_new;
|
|
|
|
if (kstrtoint(buf, 0, &flags_new))
|
|
return -EINVAL;
|
|
|
|
flags_old = st->cfg->flags;
|
|
st->cfg->flags &= SENSOR_FLAG_READONLY_MASK;
|
|
flags_new &= ~SENSOR_FLAG_READONLY_MASK;
|
|
st->cfg->flags |= flags_new;
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_MSG)
|
|
dev_info(st->dev, "%s %s %u->%u\n",
|
|
st->cfg->name, __func__, flags_old, st->cfg->flags);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_flush_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%x\n", st->flush);
|
|
}
|
|
|
|
static ssize_t nvs_attr_flush_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
int ret = 1;
|
|
|
|
mutex_lock(&st->mutex);
|
|
if (st->shutdown || st->suspend || (*st->fn_dev->sts &
|
|
(NVS_STS_SHUTDOWN | NVS_STS_SUSPEND))) {
|
|
ret = -EPERM;
|
|
} else {
|
|
st->flush = true;
|
|
if (st->fn_dev->flush)
|
|
ret = st->fn_dev->flush(st->client, st->cfg->snsr_id);
|
|
}
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_MSG)
|
|
dev_info(st->dev, "%s %s ret=%d\n",
|
|
st->cfg->name, __func__, ret);
|
|
if (ret > 0)
|
|
nvs_buf_push(st, NULL, 0);
|
|
mutex_unlock(&st->mutex);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_matrix_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
ssize_t t = 0;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < 8; i++)
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "%hhd,",
|
|
st->cfg->matrix[i]);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "%hhd\n",
|
|
st->cfg->matrix[i]);
|
|
return t;
|
|
}
|
|
|
|
static ssize_t nvs_attr_matrix_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
s8 matrix[9];
|
|
char *str;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
for (i = 0; i < sizeof(matrix); i++) {
|
|
str = strsep((char **)&buf, " \0");
|
|
if (str) {
|
|
ret = kstrtos8(str, 10, &matrix[i]);
|
|
if (ret)
|
|
break;
|
|
|
|
if (matrix[i] < -1 || matrix[i] > 1) {
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
} else {
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
if (i == sizeof(matrix))
|
|
memcpy(st->cfg->matrix, matrix, sizeof(st->cfg->matrix));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_milliamp_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d.%06u\n",
|
|
st->cfg->milliamp.ival, st->cfg->milliamp.fval);
|
|
}
|
|
|
|
static ssize_t nvs_attr_name_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", st->cfg->name);
|
|
}
|
|
|
|
static const char NVS_DBG_KEY[] = {
|
|
0x54, 0x65, 0x61, 0x67, 0x61, 0x6E, 0x26, 0x52, 0x69, 0x69, 0x73, 0x00
|
|
};
|
|
|
|
static ssize_t nvs_attr_nvs_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
enum NVS_DBG dbg;
|
|
unsigned int i;
|
|
int ret = -EINVAL;
|
|
|
|
dbg = st->dbg;
|
|
st->dbg = NVS_INFO_DATA;
|
|
switch (dbg) {
|
|
case NVS_INFO_DATA:
|
|
return nvs_dbg_data(st, buf, PAGE_SIZE);
|
|
|
|
case NVS_INFO_VER:
|
|
return snprintf(buf, PAGE_SIZE,
|
|
"sysfs_version=%u driver_version=%u\n",
|
|
NVS_SYSFS_DRIVER_VERSION,
|
|
st->kif_fn->driver_version);
|
|
|
|
case NVS_INFO_ERRS:
|
|
i = *st->fn_dev->errs;
|
|
*st->fn_dev->errs = 0;
|
|
return snprintf(buf, PAGE_SIZE, "error count=%u\n", i);
|
|
|
|
case NVS_INFO_RESET:
|
|
if (st->fn_dev->reset) {
|
|
mutex_lock(&st->mutex);
|
|
ret = st->fn_dev->reset(st->client, st->cfg->snsr_id);
|
|
mutex_unlock(&st->mutex);
|
|
}
|
|
if (ret)
|
|
return snprintf(buf, PAGE_SIZE, "reset ERR\n");
|
|
else
|
|
return snprintf(buf, PAGE_SIZE, "reset done\n");
|
|
|
|
case NVS_INFO_REGS:
|
|
if (st->fn_dev->regs)
|
|
return st->fn_dev->regs(st->client,
|
|
st->cfg->snsr_id, buf);
|
|
break;
|
|
|
|
case NVS_INFO_CFG:
|
|
return nvs_dbg_cfg(st, buf);
|
|
|
|
case NVS_INFO_DBG_KEY:
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", NVS_DBG_KEY);
|
|
|
|
case NVS_INFO_DBG:
|
|
return snprintf(buf, PAGE_SIZE, "DBG spew=%x\n",
|
|
!!(*st->fn_dev->sts & NVS_STS_SPEW_MSG));
|
|
|
|
case NVS_INFO_DBG_DATA:
|
|
return snprintf(buf, PAGE_SIZE, "DATA spew=%x\n",
|
|
!!(*st->fn_dev->sts & NVS_STS_SPEW_DATA));
|
|
|
|
case NVS_INFO_DBG_BUF:
|
|
return snprintf(buf, PAGE_SIZE, "BUF spew=%x\n",
|
|
!!(*st->fn_dev->sts & NVS_STS_SPEW_BUF));
|
|
|
|
case NVS_INFO_DBG_IRQ:
|
|
return snprintf(buf, PAGE_SIZE, "IRQ spew=%x\n",
|
|
!!(*st->fn_dev->sts & NVS_STS_SPEW_IRQ));
|
|
|
|
default:
|
|
if (dbg < NVS_INFO_LIMIT_MAX)
|
|
break;
|
|
|
|
if (st->fn_dev->nvs_read)
|
|
ret = st->fn_dev->nvs_read(st->client,
|
|
st->cfg->snsr_id, buf);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t nvs_attr_nvs_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
unsigned int dbg;
|
|
int ret;
|
|
|
|
ret = kstrtouint(buf, 0, &dbg);
|
|
if (ret)
|
|
return -EINVAL;
|
|
|
|
st->dbg = dbg;
|
|
switch (dbg) {
|
|
case NVS_INFO_DATA:
|
|
*st->fn_dev->sts &= ~NVS_STS_SPEW_MSK;
|
|
break;
|
|
|
|
case NVS_INFO_DBG:
|
|
*st->fn_dev->sts ^= NVS_STS_SPEW_MSG;
|
|
break;
|
|
|
|
case NVS_INFO_DBG_DATA:
|
|
*st->fn_dev->sts ^= NVS_STS_SPEW_DATA;
|
|
break;
|
|
|
|
case NVS_INFO_DBG_BUF:
|
|
*st->fn_dev->sts ^= NVS_STS_SPEW_BUF;
|
|
break;
|
|
|
|
case NVS_INFO_DBG_IRQ:
|
|
*st->fn_dev->sts ^= NVS_STS_SPEW_IRQ;
|
|
break;
|
|
|
|
case NVS_INFO_DBG_KEY:
|
|
break;
|
|
|
|
default:
|
|
if (dbg < NVS_INFO_LIMIT_MAX)
|
|
break;
|
|
|
|
if (st->fn_dev->nvs_write) {
|
|
st->dbg = NVS_INFO_LIMIT_MAX;
|
|
dbg -= NVS_INFO_LIMIT_MAX;
|
|
ret = st->fn_dev->nvs_write(st->client,
|
|
st->cfg->snsr_id, dbg);
|
|
if (ret < 0)
|
|
return ret;
|
|
} else if (!st->fn_dev->nvs_read) {
|
|
st->dbg = NVS_INFO_DATA;
|
|
return -EINVAL;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_offset_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
struct nvs_dev_attr *nda = to_nvs_dev_attr(attr);
|
|
unsigned int ch = nda->channel;
|
|
int ival;
|
|
int fval;
|
|
|
|
if (ch < NVS_CHANNEL_N_MAX) {
|
|
ival = st->cfg->offsets[ch].ival;
|
|
fval = st->cfg->offsets[ch].fval;
|
|
} else {
|
|
ival = st->cfg->offset.ival;
|
|
fval = st->cfg->offset.fval;
|
|
}
|
|
return nvs_attr_ret(st, buf, ival, fval);
|
|
}
|
|
|
|
static ssize_t nvs_attr_offset_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
struct nvs_dev_attr *nda = to_nvs_dev_attr(attr);
|
|
unsigned int ch = nda->channel;
|
|
int ival;
|
|
int fval;
|
|
int ret = 1;
|
|
|
|
if (nvs_attr_float(st, buf, &ival, &fval))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&st->mutex);
|
|
if (st->shutdown || st->suspend || (*st->fn_dev->sts &
|
|
(NVS_STS_SHUTDOWN | NVS_STS_SUSPEND))) {
|
|
ret = -EPERM;
|
|
} else if (ch < st->cfg->ch_n_max) {
|
|
if (st->fn_dev->offset)
|
|
ret = st->fn_dev->offset(st->client, st->cfg->snsr_id,
|
|
ch, ival);
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_MSG)
|
|
dev_info(st->dev, "%s %s ch=%u %d:%d->%d:%d ret=%d\n",
|
|
st->cfg->name, __func__, ch,
|
|
st->cfg->offsets[ch].ival,
|
|
st->cfg->offsets[ch].fval, ival, fval, ret);
|
|
if (ret > 0) {
|
|
st->cfg->offsets[ch].ival = ival;
|
|
st->cfg->offsets[ch].fval = fval;
|
|
}
|
|
} else {
|
|
if (st->fn_dev->offset)
|
|
ret = st->fn_dev->offset(st->client, st->cfg->snsr_id,
|
|
-1, ival);
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_MSG)
|
|
dev_info(st->dev, "%s %s %d:%d->%d:%d ret=%d\n",
|
|
st->cfg->name, __func__, st->cfg->offset.ival,
|
|
st->cfg->offset.fval, ival, fval, ret);
|
|
if (ret > 0) {
|
|
st->cfg->offset.ival = ival;
|
|
st->cfg->offset.fval = fval;
|
|
}
|
|
}
|
|
mutex_unlock(&st->mutex);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_part_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s %s\n",
|
|
st->cfg->part, st->cfg->name);
|
|
}
|
|
|
|
static ssize_t nvs_attr_range_max_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
int ival = st->cfg->max_range.ival;
|
|
int fval = st->cfg->max_range.fval;
|
|
|
|
return nvs_attr_ret(st, buf, ival, fval);
|
|
}
|
|
|
|
static ssize_t nvs_attr_range_max_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
int ival;
|
|
int fval;
|
|
int ret = 1;
|
|
|
|
if (nvs_attr_float(st, buf, &ival, &fval))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&st->mutex);
|
|
if (st->shutdown || st->suspend || (*st->fn_dev->sts &
|
|
(NVS_STS_SHUTDOWN | NVS_STS_SUSPEND)))
|
|
ret = -EPERM;
|
|
else if (st->fn_dev->max_range)
|
|
ret = st->fn_dev->max_range(st->client, st->cfg->snsr_id,
|
|
ival);
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_MSG)
|
|
dev_info(st->dev, "%s %s %d:%d->%d:%d ret=%d\n",
|
|
st->cfg->name, __func__, st->cfg->max_range.ival,
|
|
st->cfg->max_range.fval, ival, fval, ret);
|
|
if (ret > 0) {
|
|
st->cfg->max_range.ival = ival;
|
|
st->cfg->max_range.fval = fval;
|
|
}
|
|
mutex_unlock(&st->mutex);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_resolution_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
int ival = st->cfg->resolution.ival;
|
|
int fval = st->cfg->resolution.fval;
|
|
|
|
return nvs_attr_ret(st, buf, ival, fval);
|
|
}
|
|
|
|
static ssize_t nvs_attr_resolution_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
int ival;
|
|
int fval;
|
|
int ret = 1;
|
|
|
|
if (nvs_attr_float(st, buf, &ival, &fval))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&st->mutex);
|
|
if (st->shutdown || st->suspend || (*st->fn_dev->sts &
|
|
(NVS_STS_SHUTDOWN | NVS_STS_SUSPEND)))
|
|
ret = -EPERM;
|
|
else if (st->fn_dev->resolution)
|
|
ret = st->fn_dev->resolution(st->client, st->cfg->snsr_id,
|
|
ival);
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_MSG)
|
|
dev_info(st->dev, "%s %s %d:%d->%d:%d ret=%d\n",
|
|
st->cfg->name, __func__, st->cfg->resolution.ival,
|
|
st->cfg->resolution.fval, ival, fval, ret);
|
|
if (ret > 0) {
|
|
st->cfg->resolution.ival = ival;
|
|
st->cfg->resolution.fval = fval;
|
|
}
|
|
mutex_unlock(&st->mutex);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_scale_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
struct nvs_dev_attr *nda = to_nvs_dev_attr(attr);
|
|
unsigned int ch = nda->channel;
|
|
int ival;
|
|
int fval;
|
|
|
|
if (ch < NVS_CHANNEL_N_MAX) {
|
|
ival = st->cfg->scales[ch].ival;
|
|
fval = st->cfg->scales[ch].fval;
|
|
} else {
|
|
ival = st->cfg->scale.ival;
|
|
fval = st->cfg->scale.fval;
|
|
}
|
|
return nvs_attr_ret(st, buf, ival, fval);
|
|
}
|
|
|
|
static ssize_t nvs_attr_scale_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
struct nvs_dev_attr *nda = to_nvs_dev_attr(attr);
|
|
unsigned int ch = nda->channel;
|
|
int ival;
|
|
int fval;
|
|
int ret = 1;
|
|
|
|
if (nvs_attr_float(st, buf, &ival, &fval))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&st->mutex);
|
|
if (st->shutdown || st->suspend || (*st->fn_dev->sts &
|
|
(NVS_STS_SHUTDOWN | NVS_STS_SUSPEND))) {
|
|
ret = -EPERM;
|
|
} else if (ch < st->cfg->ch_n_max) {
|
|
if (st->fn_dev->scale)
|
|
ret = st->fn_dev->scale(st->client, st->cfg->snsr_id,
|
|
ch, ival);
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_MSG)
|
|
dev_info(st->dev, "%s %s ch=%u %d:%d->%d:%d ret=%d\n",
|
|
st->cfg->name, __func__, ch,
|
|
st->cfg->scales[ch].ival,
|
|
st->cfg->scales[ch].fval, ival, fval, ret);
|
|
if (ret > 0) {
|
|
st->cfg->scales[ch].ival = ival;
|
|
st->cfg->scales[ch].fval = fval;
|
|
}
|
|
} else {
|
|
if (st->fn_dev->scale)
|
|
ret = st->fn_dev->scale(st->client, st->cfg->snsr_id,
|
|
-1, ival);
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_MSG)
|
|
dev_info(st->dev, "%s %s %d:%d->%d:%d ret=%d\n",
|
|
st->cfg->name, __func__, st->cfg->scale.ival,
|
|
st->cfg->scale.fval, ival, fval, ret);
|
|
if (ret > 0) {
|
|
st->cfg->scale.ival = ival;
|
|
st->cfg->scale.fval = fval;
|
|
}
|
|
}
|
|
mutex_unlock(&st->mutex);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_self_test_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
ssize_t ret = -EINVAL;
|
|
|
|
mutex_lock(&st->mutex);
|
|
if (st->shutdown || st->suspend || (*st->fn_dev->sts &
|
|
(NVS_STS_SHUTDOWN | NVS_STS_SUSPEND)))
|
|
ret = -EPERM;
|
|
else if (st->fn_dev->self_test)
|
|
ret = st->fn_dev->self_test(st->client, st->cfg->snsr_id, buf);
|
|
mutex_unlock(&st->mutex);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t nvs_attr_thresh_hi_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", st->cfg->thresh_hi);
|
|
}
|
|
|
|
static ssize_t nvs_attr_thresh_hi_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
int thresh_hi;
|
|
int ret = 1;
|
|
|
|
if (kstrtoint(buf, 0, &thresh_hi))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&st->mutex);
|
|
if (st->shutdown || st->suspend || (*st->fn_dev->sts &
|
|
(NVS_STS_SHUTDOWN | NVS_STS_SUSPEND)))
|
|
ret = -EPERM;
|
|
else if (st->fn_dev->thresh_hi)
|
|
ret = st->fn_dev->thresh_hi(st->client, st->cfg->snsr_id,
|
|
thresh_hi);
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_MSG)
|
|
dev_info(st->dev, "%s %s %d->%d ret=%d\n",
|
|
st->cfg->name, __func__,
|
|
st->cfg->thresh_hi, thresh_hi, ret);
|
|
if (ret > 0)
|
|
st->cfg->thresh_hi = thresh_hi;
|
|
mutex_unlock(&st->mutex);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_thresh_lo_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", st->cfg->thresh_lo);
|
|
}
|
|
|
|
static ssize_t nvs_attr_thresh_lo_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
int thresh_lo;
|
|
int ret = 1;
|
|
|
|
if (kstrtoint(buf, 0, &thresh_lo))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&st->mutex);
|
|
if (st->shutdown || st->suspend || (*st->fn_dev->sts &
|
|
(NVS_STS_SHUTDOWN | NVS_STS_SUSPEND)))
|
|
ret = -EPERM;
|
|
else if (st->fn_dev->thresh_lo)
|
|
ret = st->fn_dev->thresh_lo(st->client, st->cfg->snsr_id,
|
|
thresh_lo);
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_MSG)
|
|
dev_info(st->dev, "%s %s %d->%d ret=%d\n",
|
|
st->cfg->name, __func__,
|
|
st->cfg->thresh_lo, thresh_lo, ret);
|
|
if (ret > 0)
|
|
st->cfg->thresh_lo = thresh_lo;
|
|
mutex_unlock(&st->mutex);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_us_period_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
unsigned int period_us;
|
|
int ret;
|
|
|
|
if (st->fn_dev->enable == nvs_fn_dev_enable) {
|
|
ret = st->enabled;
|
|
} else {
|
|
ret = st->fn_dev->enable(st->client,
|
|
st->cfg->snsr_id, -1);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
if (ret) {
|
|
if (st->fn_dev->batch_read) {
|
|
ret = st->fn_dev->batch_read(st->client,
|
|
st->cfg->snsr_id,
|
|
&period_us, NULL);
|
|
if (ret < 0)
|
|
return ret;
|
|
} else {
|
|
period_us = st->us_period;
|
|
}
|
|
} else {
|
|
period_us = st->cfg->delay_us_min;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", period_us);
|
|
}
|
|
|
|
static ssize_t nvs_attr_us_period_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
unsigned int us_period;
|
|
int ret = 0;
|
|
|
|
if (kstrtouint(buf, 0, &us_period))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&st->mutex);
|
|
if (st->shutdown || st->suspend || (*st->fn_dev->sts &
|
|
(NVS_STS_SHUTDOWN | NVS_STS_SUSPEND))) {
|
|
ret = -EPERM;
|
|
} else {
|
|
if (!st->one_shot) {
|
|
if (us_period < st->cfg->delay_us_min)
|
|
us_period = st->cfg->delay_us_min;
|
|
if (st->cfg->delay_us_max && (us_period >
|
|
st->cfg->delay_us_max))
|
|
us_period = st->cfg->delay_us_max;
|
|
}
|
|
if (st->fn_dev->batch) {
|
|
ret = st->fn_dev->batch(st->client, st->cfg->snsr_id,
|
|
0, us_period, st->us_timeout);
|
|
} else {
|
|
if (st->us_timeout)
|
|
ret = -EINVAL;
|
|
}
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_MSG)
|
|
dev_info(st->dev, "%s %s %u->%u ret=%d\n",
|
|
st->cfg->name, __func__,
|
|
st->us_period, us_period, ret);
|
|
if (!ret)
|
|
st->us_period = us_period;
|
|
}
|
|
mutex_unlock(&st->mutex);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_us_timeout_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
unsigned int timeout_us;
|
|
int ret;
|
|
|
|
if (st->fn_dev->enable == nvs_fn_dev_enable) {
|
|
ret = st->enabled;
|
|
} else {
|
|
ret = st->fn_dev->enable(st->client,
|
|
st->cfg->snsr_id, -1);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
if (ret) {
|
|
if (st->fn_dev->batch_read) {
|
|
ret = st->fn_dev->batch_read(st->client,
|
|
st->cfg->snsr_id,
|
|
NULL, &timeout_us);
|
|
if (ret < 0)
|
|
return ret;
|
|
} else {
|
|
timeout_us = st->us_timeout;
|
|
}
|
|
} else {
|
|
timeout_us = st->cfg->delay_us_max;
|
|
}
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", timeout_us);
|
|
}
|
|
|
|
static ssize_t nvs_attr_us_timeout_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
unsigned int us_timeout;
|
|
|
|
if (kstrtouint(buf, 0, &us_timeout))
|
|
return -EINVAL;
|
|
|
|
if (*st->fn_dev->sts & NVS_STS_SPEW_MSG)
|
|
dev_info(st->dev, "%s %s %u->%u\n",
|
|
st->cfg->name, __func__, st->us_timeout, us_timeout);
|
|
st->us_timeout = us_timeout;
|
|
return count;
|
|
}
|
|
|
|
static ssize_t nvs_attr_uuid_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
ssize_t t = 0;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < 15; i++)
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "%02X ",
|
|
st->cfg->uuid[i]);
|
|
t += snprintf(buf + t, PAGE_SIZE - t, "%02X\n", st->cfg->uuid[i]);
|
|
return t;
|
|
}
|
|
|
|
static ssize_t nvs_attr_vendor_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", st->cfg->vendor);
|
|
}
|
|
|
|
static ssize_t nvs_attr_version_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct nvs_state *st = dev_to_nvs_state(dev);
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", st->cfg->version);
|
|
}
|
|
|
|
static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
nvs_attr_enable_show, nvs_attr_enable_store);
|
|
static DEVICE_ATTR(fifo_max_event_count, S_IRUGO,
|
|
nvs_attr_fmec_show, NULL);
|
|
static DEVICE_ATTR(fifo_reserved_event_count, S_IRUGO,
|
|
nvs_attr_frec_show, NULL);
|
|
static DEVICE_ATTR(flags, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
nvs_attr_flags_show, nvs_attr_flags_store);
|
|
static DEVICE_ATTR(flush, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
nvs_attr_flush_show, nvs_attr_flush_store);
|
|
/* matrix permissions are read only - writes are for debug */
|
|
static DEVICE_ATTR(matrix, S_IRUGO,
|
|
nvs_attr_matrix_show, nvs_attr_matrix_store);
|
|
static DEVICE_ATTR(milliamp, S_IRUGO,
|
|
nvs_attr_milliamp_show, NULL);
|
|
static DEVICE_ATTR(name, S_IRUGO,
|
|
nvs_attr_name_show, NULL);
|
|
static DEVICE_ATTR(nvs, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
nvs_attr_nvs_show, nvs_attr_nvs_store);
|
|
static DEVICE_ATTR(part, S_IRUGO,
|
|
nvs_attr_part_show, NULL);
|
|
static DEVICE_ATTR(range_max, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
nvs_attr_range_max_show, nvs_attr_range_max_store);
|
|
static DEVICE_ATTR(resolution, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
nvs_attr_resolution_show, nvs_attr_resolution_store);
|
|
static DEVICE_ATTR(self_test, S_IRUGO,
|
|
nvs_attr_self_test_show, NULL);
|
|
static DEVICE_ATTR(thresh_hi, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
nvs_attr_thresh_hi_show, nvs_attr_thresh_hi_store);
|
|
static DEVICE_ATTR(thresh_lo, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
nvs_attr_thresh_lo_show, nvs_attr_thresh_lo_store);
|
|
static DEVICE_ATTR(us_period, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
nvs_attr_us_period_show, nvs_attr_us_period_store);
|
|
static DEVICE_ATTR(us_timeout, S_IRUGO | S_IWUSR | S_IWGRP,
|
|
nvs_attr_us_timeout_show, nvs_attr_us_timeout_store);
|
|
static DEVICE_ATTR(uuid, S_IRUGO,
|
|
nvs_attr_uuid_show, NULL);
|
|
static DEVICE_ATTR(vendor, S_IRUGO,
|
|
nvs_attr_vendor_show, NULL);
|
|
static DEVICE_ATTR(version, S_IRUGO,
|
|
nvs_attr_version_show, NULL);
|
|
|
|
/* commented attributes are just FYI and dynamically created */
|
|
static struct attribute *nvs_attrs[] = {
|
|
/* &dev_attr_ch, */
|
|
&dev_attr_enable.attr,
|
|
&dev_attr_fifo_max_event_count.attr,
|
|
&dev_attr_fifo_reserved_event_count.attr,
|
|
&dev_attr_flags.attr,
|
|
&dev_attr_flush.attr,
|
|
&dev_attr_matrix.attr,
|
|
&dev_attr_milliamp.attr,
|
|
&dev_attr_name.attr,
|
|
&dev_attr_nvs.attr,
|
|
/* &dev_attr_offset.attr, */
|
|
&dev_attr_part.attr,
|
|
&dev_attr_range_max.attr,
|
|
&dev_attr_resolution.attr,
|
|
/* &dev_attr_scale.attr, */
|
|
&dev_attr_self_test.attr,
|
|
&dev_attr_thresh_hi.attr,
|
|
&dev_attr_thresh_lo.attr,
|
|
&dev_attr_us_period.attr,
|
|
&dev_attr_us_timeout.attr,
|
|
&dev_attr_uuid.attr,
|
|
&dev_attr_vendor.attr,
|
|
&dev_attr_version.attr,
|
|
NULL
|
|
};
|
|
|
|
static int nvs_attr_rm(struct nvs_state *st, struct attribute *attr)
|
|
{
|
|
unsigned int i;
|
|
unsigned int n;
|
|
|
|
n = ARRAY_SIZE(nvs_attrs) - 1;
|
|
for (i = 0; i < n; i++) {
|
|
if (!st->attrs[i])
|
|
return -EINVAL;
|
|
|
|
if (st->attrs[i] == attr) {
|
|
for (; i < n; i++)
|
|
st->attrs[i] = st->attrs[i + 1];
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int nvs_init_sysfs(struct nvs_state *st)
|
|
{
|
|
bool matrix = false;
|
|
unsigned int i;
|
|
unsigned int j;
|
|
unsigned int k;
|
|
unsigned int n;
|
|
|
|
i = st->cfg->snsr_id; /* st->cfg->snsr_id will be >= 0 */
|
|
/* Here we have two ways to identify the sensor:
|
|
* 1. By name which we try to match to
|
|
* 2. By sensor ID (st->cfg->snsr_id). This method is typically used
|
|
* by the sensor hub so that name strings don't have to be passed
|
|
* and because there are multiple sensors the sensor hub driver has
|
|
* to track via the sensor ID.
|
|
*/
|
|
if (st->cfg->name) {
|
|
/* if st->cfg->name exists then we use that */
|
|
for (i = 0; i < ARRAY_SIZE(nvs_snsr_names); i++) {
|
|
if (!strcmp(st->cfg->name, nvs_snsr_names[i]))
|
|
break;
|
|
}
|
|
if (i >= ARRAY_SIZE(nvs_snsr_names))
|
|
i = 0; /* use generic sensor name */
|
|
} else {
|
|
/* no st->cfg->name - use st->cfg->snsr_id to specify device */
|
|
if (i >= ARRAY_SIZE(nvs_snsr_names))
|
|
i = 0; /* use generic sensor name */
|
|
|
|
st->cfg->name = nvs_snsr_names[i];
|
|
}
|
|
|
|
st->snsr_type = i;
|
|
/* count attributes */
|
|
if (st->cfg->ch_n_max)
|
|
n = (st->cfg->ch_n_max << 1); /* scales and offsets */
|
|
else
|
|
n = 2; /* scale and offset */
|
|
/* allocate memory for scale(s) and offset(s) attributes */
|
|
st->attr_so = kzalloc(n * sizeof(st->attr_so[0]), GFP_KERNEL);
|
|
if (!st->attr_so)
|
|
return -ENOMEM;
|
|
|
|
st->attr_so_n = n;
|
|
/* allocate memory for channel attributes */
|
|
st->attr_ch = kzalloc(st->ch_n * sizeof(st->attr_ch[0]), GFP_KERNEL);
|
|
if (!st->attr_so)
|
|
return -ENOMEM;
|
|
|
|
n += st->ch_n;
|
|
n += ARRAY_SIZE(nvs_attrs);
|
|
/* subtract the ones that will be removed */
|
|
if (!st->fn_dev->enable)
|
|
n--;
|
|
if ((st->special || st->one_shot) && !st->fn_dev->batch)
|
|
n -= 2;
|
|
if ((st->special || st->one_shot) && !st->fn_dev->flush)
|
|
n--;
|
|
if ((!st->cfg->thresh_lo) && (!st->cfg->thresh_hi) &&
|
|
(!st->fn_dev->thresh_lo) && (!st->fn_dev->thresh_hi))
|
|
n -= 2;
|
|
if (!st->fn_dev->self_test)
|
|
n--;
|
|
/* test if matrix data */
|
|
for (i = 0; i < ARRAY_SIZE(st->cfg->matrix); i++) {
|
|
if (st->cfg->matrix[i]) {
|
|
matrix = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!matrix)
|
|
n--;
|
|
/* allocate memory */
|
|
if (n < ARRAY_SIZE(nvs_attrs))
|
|
n = ARRAY_SIZE(nvs_attrs);
|
|
st->attrs = kzalloc(n * sizeof(st->attrs[0]), GFP_KERNEL);
|
|
if (!st->attrs)
|
|
return -ENOMEM;
|
|
|
|
memcpy(st->attrs, nvs_attrs, n * sizeof(st->attrs[0]));
|
|
/* remove unused attributes */
|
|
if (!st->fn_dev->enable)
|
|
nvs_attr_rm(st, &dev_attr_enable.attr);
|
|
if ((st->special || st->one_shot) && !st->fn_dev->batch) {
|
|
nvs_attr_rm(st, &dev_attr_us_period.attr);
|
|
nvs_attr_rm(st, &dev_attr_us_timeout.attr);
|
|
}
|
|
if ((st->special || st->one_shot) && !st->fn_dev->flush)
|
|
nvs_attr_rm(st, &dev_attr_flush.attr);
|
|
if ((!st->cfg->thresh_lo) && (!st->cfg->thresh_hi) &&
|
|
(!st->fn_dev->thresh_lo) && (!st->fn_dev->thresh_hi)) {
|
|
nvs_attr_rm(st, &dev_attr_thresh_lo.attr);
|
|
nvs_attr_rm(st, &dev_attr_thresh_hi.attr);
|
|
}
|
|
if (!st->fn_dev->self_test)
|
|
nvs_attr_rm(st, &dev_attr_self_test.attr);
|
|
if (!matrix)
|
|
nvs_attr_rm(st, &dev_attr_matrix.attr);
|
|
/* find end of list */
|
|
for (i = 0; i < ARRAY_SIZE(nvs_attrs); i++) {
|
|
if (!st->attrs[i])
|
|
break;
|
|
}
|
|
|
|
/* create scale and offset attributes */
|
|
n = st->attr_so_n >> 1;
|
|
for (j = 0; j < n; j++) {
|
|
k = j << 1;
|
|
if (st->cfg->ch_n_max) {
|
|
st->attr_so[k].channel = j;
|
|
st->attr_so[k].dev_attr.attr.name =
|
|
kasprintf(GFP_KERNEL, "scale%u", j);
|
|
} else {
|
|
st->attr_so[k].channel = -1;
|
|
st->attr_so[k].dev_attr.attr.name =
|
|
kasprintf(GFP_KERNEL, "scale");
|
|
}
|
|
st->attr_so[k].dev_attr.attr.mode =
|
|
S_IRUGO | S_IWUSR | S_IWGRP;
|
|
st->attr_so[k].dev_attr.show = nvs_attr_scale_show;
|
|
st->attr_so[k].dev_attr.store = nvs_attr_scale_store;
|
|
st->attrs[i] = &st->attr_so[k].dev_attr.attr;
|
|
i++;
|
|
}
|
|
|
|
for (j = 0; j < n; j++) {
|
|
k = (j << 1) + 1;
|
|
if (st->cfg->ch_n_max) {
|
|
st->attr_so[k].channel = j;
|
|
st->attr_so[k].dev_attr.attr.name =
|
|
kasprintf(GFP_KERNEL, "offset%u", j);
|
|
} else {
|
|
st->attr_so[k].channel = -1;
|
|
st->attr_so[k].dev_attr.attr.name =
|
|
kasprintf(GFP_KERNEL, "offset");
|
|
}
|
|
st->attr_so[k].dev_attr.attr.mode =
|
|
S_IRUGO | S_IWUSR | S_IWGRP;
|
|
st->attr_so[k].dev_attr.show = nvs_attr_offset_show;
|
|
st->attr_so[k].dev_attr.store = nvs_attr_offset_store;
|
|
st->attrs[i] = &st->attr_so[k].dev_attr.attr;
|
|
i++;
|
|
}
|
|
|
|
/* create channel attributes */
|
|
n = st->ch_n - 1; /* - timestamp */
|
|
for (j = 0; j < n; j++) {
|
|
st->attr_ch[j].channel = j;
|
|
if (st->buf_ch[j].sign)
|
|
st->attr_ch[j].dev_attr.attr.name =
|
|
kasprintf(GFP_KERNEL,
|
|
"ch%u_%u_s_%u",
|
|
j, st->buf_ch[j].buf_i,
|
|
st->buf_ch[j].byte_n);
|
|
else
|
|
st->attr_ch[j].dev_attr.attr.name =
|
|
kasprintf(GFP_KERNEL,
|
|
"ch%u_%u_u_%u",
|
|
j, st->buf_ch[j].buf_i,
|
|
st->buf_ch[j].byte_n);
|
|
st->attr_ch[j].dev_attr.attr.mode =
|
|
S_IRUGO | S_IWUSR | S_IWGRP;
|
|
st->attr_ch[j].dev_attr.show = nvs_attr_ch_show;
|
|
st->attr_ch[j].dev_attr.store = nvs_attr_ch_store;
|
|
st->attrs[i] = &st->attr_ch[j].dev_attr.attr;
|
|
i++;
|
|
}
|
|
|
|
/* timestamp */
|
|
st->attr_ch[j].channel = n;
|
|
st->attr_ch[j].dev_attr.attr.name = kasprintf(GFP_KERNEL,
|
|
"ch%u_%u_t_%u", j,
|
|
st->buf_ch[j].buf_i,
|
|
st->buf_ch[j].byte_n);
|
|
st->attr_ch[j].dev_attr.attr.mode = S_IRUGO;
|
|
st->attr_ch[j].dev_attr.show = nvs_attr_ch_show;
|
|
st->attr_ch[j].dev_attr.store = NULL;
|
|
st->attrs[i] = &st->attr_ch[j].dev_attr.attr;
|
|
i++;
|
|
for (j = 0; j < i; j++) {
|
|
sysfs_attr_init(st->attrs[j]);
|
|
}
|
|
st->attrs[i] = NULL;
|
|
st->attr_grp.attrs = st->attrs;
|
|
return 0;
|
|
}
|
|
|
|
static int nvs_init_buf(struct nvs_state *st)
|
|
{
|
|
unsigned int buf_n;
|
|
unsigned int i;
|
|
unsigned int n;
|
|
|
|
n = st->cfg->ch_n;
|
|
buf_n = abs(st->cfg->ch_sz);
|
|
buf_n *= n;
|
|
if (st->cfg->snsr_data_n > buf_n)
|
|
/* extra channel */
|
|
n++;
|
|
n++; /* timestamp */
|
|
st->ch_n = n;
|
|
/* allocate buffer index memory */
|
|
st->buf_ch = kzalloc(sizeof(st->buf_ch[0]) * st->ch_n, GFP_KERNEL);
|
|
if (!st->buf_ch)
|
|
return -ENOMEM;
|
|
|
|
/* data channels */
|
|
for (i = 0; i < st->cfg->ch_n; i++) {
|
|
if (st->cfg->ch_sz < 0) {
|
|
st->buf_ch[i].sign = true;
|
|
st->buf_ch[i].byte_n = abs(st->cfg->ch_sz);
|
|
} else {
|
|
st->buf_ch[i].sign = false;
|
|
st->buf_ch[i].byte_n = st->cfg->ch_sz;
|
|
}
|
|
}
|
|
|
|
if (st->cfg->snsr_data_n > buf_n) {
|
|
/* extra channel (status) */
|
|
st->buf_ch[i].sign = false;
|
|
st->buf_ch[i].byte_n = st->cfg->snsr_data_n - buf_n;
|
|
buf_n += st->buf_ch[i].byte_n;
|
|
i++;
|
|
}
|
|
/* timestamp */
|
|
st->buf_ch[i].sign = false;
|
|
st->buf_ch[i].byte_n = sizeof(nvs_timestamp());
|
|
buf_n += st->buf_ch[i].byte_n;
|
|
/* allocate buffer memory */
|
|
st->buf = kzalloc(buf_n, GFP_KERNEL);
|
|
if (!st->buf)
|
|
return -ENOMEM;
|
|
|
|
/* buffer indexes */
|
|
buf_n = 0;
|
|
for (i = 1; i < st->ch_n; i++) {
|
|
buf_n += st->buf_ch[i - 1].byte_n;
|
|
st->buf_ch[i].buf_i = buf_n;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int nvs_suspend(void *handle)
|
|
{
|
|
struct nvs_state *st = (struct nvs_state *)handle;
|
|
int ret = 0;
|
|
|
|
if (st == NULL)
|
|
return 0;
|
|
|
|
mutex_lock(&st->mutex);
|
|
st->suspend = true;
|
|
if (!(st->cfg->flags & SENSOR_FLAG_WAKE_UP)) {
|
|
ret = st->fn_dev->enable(st->client, st->cfg->snsr_id, -1);
|
|
if (ret >= 0)
|
|
st->enabled = ret;
|
|
if (ret > 0)
|
|
ret = st->fn_dev->enable(st->client,
|
|
st->cfg->snsr_id, 0);
|
|
else
|
|
ret = 0;
|
|
}
|
|
mutex_unlock(&st->mutex);
|
|
return ret;
|
|
}
|
|
|
|
int nvs_resume(void *handle)
|
|
{
|
|
struct nvs_state *st = (struct nvs_state *)handle;
|
|
int ret = 0;
|
|
|
|
if (st == NULL)
|
|
return 0;
|
|
|
|
mutex_lock(&st->mutex);
|
|
if (!(st->cfg->flags & SENSOR_FLAG_WAKE_UP)) {
|
|
if (st->enabled)
|
|
ret = st->fn_dev->enable(st->client,
|
|
st->cfg->snsr_id, st->enabled);
|
|
}
|
|
st->suspend = false;
|
|
mutex_unlock(&st->mutex);
|
|
return ret;
|
|
}
|
|
|
|
static void nvs_remove_sysfs(struct nvs_state *st)
|
|
{
|
|
unsigned int i;
|
|
|
|
kfree(st->attrs);
|
|
if (st->attr_so) {
|
|
for (i = 0; i < st->attr_so_n; i++)
|
|
kfree((void *)st->attr_so[i].dev_attr.attr.name);
|
|
kfree(st->attr_so);
|
|
st->attr_so_n = 0;
|
|
}
|
|
|
|
if (st->attr_ch) {
|
|
for (i = 0; i < st->ch_n; i++)
|
|
kfree((void *)st->attr_ch[i].dev_attr.attr.name);
|
|
kfree(st->attr_ch);
|
|
st->ch_n = 0;
|
|
}
|
|
}
|
|
|
|
static void nvs_remove_buf(struct nvs_state *st)
|
|
{
|
|
kfree(st->buf_ch);
|
|
kfree(st->buf);
|
|
}
|
|
|
|
void nvs_shutdown(void *handle)
|
|
{
|
|
struct nvs_state *st = (struct nvs_state *)handle;
|
|
int ret;
|
|
|
|
if (st == NULL)
|
|
return;
|
|
|
|
mutex_lock(&st->mutex);
|
|
st->shutdown = true;
|
|
ret = st->fn_dev->enable(st->client, st->cfg->snsr_id, -1);
|
|
if (ret > 0)
|
|
st->fn_dev->enable(st->client, st->cfg->snsr_id, 0);
|
|
mutex_unlock(&st->mutex);
|
|
}
|
|
|
|
int nvs_remove(void *handle)
|
|
{
|
|
struct nvs_state *st = (struct nvs_state *)handle;
|
|
|
|
if (st == NULL)
|
|
return 0;
|
|
|
|
st->kif_fn->remove(st);
|
|
nvs_remove_sysfs(st);
|
|
nvs_remove_buf(st);
|
|
if (st->cfg->name)
|
|
dev_info(st->dev, "%s %s snsr_id=%d\n",
|
|
__func__, st->cfg->name, st->cfg->snsr_id);
|
|
else
|
|
dev_info(st->dev, "%s snsr_id=%d\n",
|
|
__func__, st->cfg->snsr_id);
|
|
kfree(st);
|
|
return 0;
|
|
}
|
|
|
|
static int nvs_init(struct nvs_state *st)
|
|
{
|
|
int ret;
|
|
|
|
mutex_init(&st->mutex);
|
|
nvs_report_mode(st);
|
|
ret = nvs_init_buf(st);
|
|
if (ret) {
|
|
dev_err(st->dev, "%s nvs_init_buf ERR=%d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = nvs_init_sysfs(st);
|
|
if (ret) {
|
|
dev_err(st->dev, "%s nvs_init_sysfs ERR=%d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = st->kif_fn->init(st);
|
|
if (ret) {
|
|
dev_err(st->dev, "%s nvs_init_kif ERR=%d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int nvs_probe(void **handle, void *dev_client, struct device *dev,
|
|
struct nvs_fn_dev *fn_dev, struct sensor_cfg *snsr_cfg,
|
|
struct nvs_kif_fn *kif_fn, unsigned int kif_st_n)
|
|
{
|
|
struct nvs_state *st;
|
|
unsigned int n;
|
|
int ret;
|
|
|
|
if (kif_fn) {
|
|
dev_info(dev, "%s (%s)\n", __func__, kif_fn->name);
|
|
} else {
|
|
dev_err(dev, "%s ERR: kif_fn NULL\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!snsr_cfg) {
|
|
dev_err(dev, "%s ERR: snsr_cfg NULL\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (snsr_cfg->snsr_id < 0) {
|
|
/* device has been disabled */
|
|
if (snsr_cfg->name)
|
|
dev_info(dev, "%s %s disabled\n",
|
|
__func__, snsr_cfg->name);
|
|
else
|
|
dev_info(dev, "%s device disabled\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
n = sizeof(*st);
|
|
if (kif_st_n) {
|
|
n = ALIGN(n, NVS_ALIGN);
|
|
n += kif_st_n;
|
|
}
|
|
n += NVS_ALIGN - 1;
|
|
st = kzalloc(n, GFP_KERNEL);
|
|
if (!st) {
|
|
dev_err(dev, "%s kzalloc ERR\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
dev_set_drvdata(dev, st);
|
|
st->kif_fn = kif_fn;
|
|
st->client = dev_client;
|
|
st->dev = dev;
|
|
st->fn_dev = fn_dev;
|
|
/* protect ourselves from NULL pointers */
|
|
if (st->fn_dev->enable == NULL)
|
|
/* hook a dummy benign function */
|
|
st->fn_dev->enable = nvs_fn_dev_enable;
|
|
if (st->fn_dev->sts == NULL)
|
|
st->fn_dev->sts = &st->fn_dev_sts;
|
|
if (st->fn_dev->errs == NULL)
|
|
st->fn_dev->errs = &st->fn_dev_errs;
|
|
/* all other pointers are tested for NULL in this code */
|
|
st->cfg = snsr_cfg;
|
|
ret = nvs_init(st);
|
|
if (ret) {
|
|
if (st->cfg->name)
|
|
dev_err(st->dev, "%s %s snsr_id=%d EXIT ERR=%d\n",
|
|
__func__, st->cfg->name,
|
|
st->cfg->snsr_id, ret);
|
|
else
|
|
dev_err(st->dev, "%s snsr_id=%d EXIT ERR=%d\n",
|
|
__func__, st->cfg->snsr_id, ret);
|
|
nvs_remove(st);
|
|
} else {
|
|
*handle = st;
|
|
}
|
|
dev_info(st->dev, "%s (%s) %s done\n",
|
|
__func__, kif_fn->name, st->cfg->name);
|
|
return ret;
|
|
}
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("NVidia Sensor sysfs module");
|
|
MODULE_AUTHOR("NVIDIA Corporation");
|
|
|