/* Copyright (c) 2014-2020, 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 */ /* The NVS implementation of scan_elements enable/disable works as follows * (See NVS HAL for further explaination): * To enable, the NVS HAL will: * 1. Disable buffer * 2. Enable channels * 3. Calculate buffer alignments based on enabled channels * 4. Enable buffer * It is expected that the NVS kernel driver will detect the channels enabled * and enable the device using the IIO iio_buffer_setup_ops. * To disable, the NVS HAL will: * 1. Disable buffer * 2. Disable channels * 3. Calculate buffer alignments based on enabled channels * 4. If (one or more channels are enabled) * 4a. Enable buffer * else * 4b. Disable master enable * It is expected that the master enable will be enabled as part of the * iio_buffer_setup_ops. * The NVS sysfs attribute for the master enable is "enable" without any * channel name. * The master enable is an enhancement of the standard IIO enable/disable * procedure. Consider a device that contains multiple sensors. To enable all * the sensors with the standard IIO enable mechanism, the device would be * powered off and on for each sensor that was enabled. By having a master * enable, the device does not have to be powerered down for each buffer * disable but only when the master enable is disabled, thereby improving * efficiency. */ /* This NVS kernel driver has a test mechanism for sending specific data up the * SW stack by writing the requested data value to the IIO raw 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 IIO raw sysfs attribute is written to again. */ /* The NVS HAL will use the IIO 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. See calibration steps below. */ /* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 14, 0) #include #endif #define NVS_IIO_DRIVER_VERSION (224) enum NVS_ATTR { NVS_ATTR_ENABLE, NVS_ATTR_PART, NVS_ATTR_VENDOR, NVS_ATTR_VERSION, NVS_ATTR_MILLIAMP, NVS_ATTR_FIFO_RSRV_EVNT_CNT, NVS_ATTR_FIFO_MAX_EVNT_CNT, NVS_ATTR_FLAGS, NVS_ATTR_MATRIX, NVS_ATTR_SELF_TEST, NVS_ATTR_UUID, }; 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 ssize_t nvs_info_show(struct device *dev, struct device_attribute *attr, char *buf); static ssize_t nvs_info_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); static ssize_t nvs_attr_show(struct device *dev, struct device_attribute *attr, char *buf); static ssize_t nvs_attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); static DEVICE_ATTR(nvs, S_IRUGO | S_IWUSR | S_IWGRP, nvs_info_show, nvs_info_store); static IIO_DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP, nvs_attr_show, nvs_attr_store, NVS_ATTR_ENABLE); static IIO_DEVICE_ATTR(part, S_IRUGO, nvs_attr_show, NULL, NVS_ATTR_PART); static IIO_DEVICE_ATTR(vendor, S_IRUGO, nvs_attr_show, NULL, NVS_ATTR_VENDOR); static IIO_DEVICE_ATTR(version, S_IRUGO, nvs_attr_show, NULL, NVS_ATTR_VERSION); static IIO_DEVICE_ATTR(milliamp, S_IRUGO, nvs_attr_show, NULL, NVS_ATTR_MILLIAMP); static IIO_DEVICE_ATTR(fifo_reserved_event_count, S_IRUGO, nvs_attr_show, NULL, NVS_ATTR_FIFO_RSRV_EVNT_CNT); static IIO_DEVICE_ATTR(fifo_max_event_count, S_IRUGO, nvs_attr_show, NULL, NVS_ATTR_FIFO_MAX_EVNT_CNT); static IIO_DEVICE_ATTR(flags, S_IRUGO | S_IWUSR | S_IWGRP, nvs_attr_show, nvs_attr_store, NVS_ATTR_FLAGS); static IIO_DEVICE_ATTR(uuid, S_IRUGO, nvs_attr_show, NULL, NVS_ATTR_UUID); /* matrix permissions are read only - writes are for debug */ static IIO_DEVICE_ATTR(matrix, S_IRUGO, nvs_attr_show, nvs_attr_store, NVS_ATTR_MATRIX); static IIO_DEVICE_ATTR(self_test, S_IRUGO, nvs_attr_show, NULL, NVS_ATTR_SELF_TEST); static struct attribute *nvs_attrs[] = { &dev_attr_nvs.attr, &iio_dev_attr_enable.dev_attr.attr, &iio_dev_attr_part.dev_attr.attr, &iio_dev_attr_vendor.dev_attr.attr, &iio_dev_attr_version.dev_attr.attr, &iio_dev_attr_milliamp.dev_attr.attr, &iio_dev_attr_fifo_reserved_event_count.dev_attr.attr, &iio_dev_attr_fifo_max_event_count.dev_attr.attr, &iio_dev_attr_flags.dev_attr.attr, &iio_dev_attr_uuid.dev_attr.attr, &iio_dev_attr_matrix.dev_attr.attr, &iio_dev_attr_self_test.dev_attr.attr, NULL }; struct nvs_iio_channel { unsigned int n; int i; }; struct nvs_state { void *client; struct device *dev; struct nvs_fn_dev *fn_dev; struct sensor_cfg *cfg; struct nvs_iio_channel *ch; struct iio_trigger *trig; struct iio_chan_spec *chs; struct attribute *attrs[ARRAY_SIZE(nvs_attrs)]; struct attribute_group attr_group; struct iio_info info; bool init; bool shutdown; bool suspend; bool flush; bool first_push; bool one_shot; bool on_change; bool special; int enabled; int batch_flags; unsigned int batch_period_us; unsigned int batch_timeout_us; unsigned int dbg; unsigned int fn_dev_sts; unsigned int fn_dev_errs; unsigned int snsr_type; long dbg_data_lock; s64 ts_diff; s64 ts; u8 *buf; }; struct nvs_iio_ch { const char *snsr_name; enum iio_chan_type typ; enum iio_modifier *mod; }; static enum iio_modifier nvs_iio_modifier_vec[] = { IIO_MOD_X, IIO_MOD_Y, IIO_MOD_Z, IIO_MOD_STATUS, IIO_NO_MOD, IIO_NO_MOD, }; static enum iio_modifier nvs_iio_modifier_qtn[] = { IIO_MOD_X, IIO_MOD_Y, IIO_MOD_Z, IIO_MOD_W, IIO_MOD_STATUS, IIO_NO_MOD, }; static enum iio_modifier nvs_iio_modifier_uncal[] = { IIO_MOD_X_UNCALIB, IIO_MOD_Y_UNCALIB, IIO_MOD_Z_UNCALIB, IIO_MOD_X_BIAS, IIO_MOD_Y_BIAS, IIO_MOD_Z_BIAS, IIO_MOD_STATUS, IIO_NO_MOD, }; static enum iio_modifier nvs_iio_modifier_heart[] = { IIO_MOD_BPM, IIO_MOD_STATUS, IIO_NO_MOD, }; static const struct nvs_iio_ch nvs_iio_chs[] = { /* unknown sensor type */ { .snsr_name = "generic_sensor", .typ = IIO_GENERIC, .mod = NULL, }, /* SENSOR_TYPE_ACCELEROMETER */ { .snsr_name = "accelerometer", .typ = IIO_ACCEL, .mod = nvs_iio_modifier_vec, }, /* SENSOR_TYPE_MAGNETIC_FIELD */ { .snsr_name = "magnetic_field", .typ = IIO_MAGN, .mod = nvs_iio_modifier_vec, }, /* SENSOR_TYPE_ORIENTATION */ { .snsr_name = "orientation", .typ = IIO_ORIENTATION, .mod = nvs_iio_modifier_vec, }, /* SENSOR_TYPE_GYROSCOPE */ { .snsr_name = "gyroscope", .typ = IIO_ANGL_VEL, .mod = nvs_iio_modifier_vec, }, /* SENSOR_TYPE_LIGHT */ { .snsr_name = "light", .typ = IIO_LIGHT, .mod = NULL, }, /* SENSOR_TYPE_PRESSURE */ { .snsr_name = "pressure", .typ = IIO_PRESSURE, .mod = NULL, }, /* SENSOR_TYPE_TEMPERATURE */ { .snsr_name = "temperature", .typ = IIO_TEMP, .mod = NULL, }, /* SENSOR_TYPE_PROXIMITY */ { .snsr_name = "proximity", .typ = IIO_PROXIMITY, .mod = NULL, }, /* SENSOR_TYPE_GRAVITY */ { .snsr_name = "gravity", .typ = IIO_GRAVITY, .mod = nvs_iio_modifier_vec, }, /* SENSOR_TYPE_LINEAR_ACCELERATION */ { .snsr_name = "linear_acceleration", .typ = IIO_LINEAR_ACCEL, .mod = nvs_iio_modifier_vec, }, /* SENSOR_TYPE_ROTATION_VECTOR */ { .snsr_name = "rotation_vector", .typ = IIO_ROT, .mod = nvs_iio_modifier_qtn, }, /* SENSOR_TYPE_RELATIVE_HUMIDITY */ { .snsr_name = "relative_humidity", .typ = IIO_HUMIDITY, .mod = NULL, }, /* SENSOR_TYPE_AMBIENT_TEMPERATURE */ { .snsr_name = "ambient_temperature", .typ = IIO_TEMP, .mod = NULL, }, /* SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED */ { .snsr_name = "magnetic_field_uncalibrated", .typ = IIO_MAGN, .mod = nvs_iio_modifier_uncal, }, /* SENSOR_TYPE_GAME_ROTATION_VECTOR */ { .snsr_name = "game_rotation_vector", .typ = IIO_GAME_ROT, .mod = nvs_iio_modifier_qtn, }, /* SENSOR_TYPE_GYROSCOPE_UNCALIBRATED */ { .snsr_name = "gyroscope_uncalibrated", .typ = IIO_ANGL_VEL, .mod = nvs_iio_modifier_uncal, }, /* SENSOR_TYPE_SIGNIFICANT_MOTION */ { .snsr_name = "significant_motion", .typ = IIO_MOTION, .mod = NULL, }, /* SENSOR_TYPE_STEP_DETECTOR */ { .snsr_name = "step_detector", .typ = IIO_STEP, .mod = NULL, }, /* SENSOR_TYPE_STEP_COUNTER */ { .snsr_name = "step_counter", .typ = IIO_STEP_COUNT, .mod = NULL, }, /* SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR */ { .snsr_name = "geomagnetic_rotation_vector", .typ = IIO_GEOMAGN_ROT, .mod = nvs_iio_modifier_qtn, }, /* SENSOR_TYPE_HEART_RATE */ { .snsr_name = "heart_rate", .typ = IIO_HEART_RATE, .mod = nvs_iio_modifier_heart, }, /* SENSOR_TYPE_TILT_DETECTOR */ { .snsr_name = "tilt_detector", .typ = IIO_INCLI, .mod = NULL, }, /* SENSOR_TYPE_WAKE_GESTURE */ { .snsr_name = "wake_gesture", .typ = IIO_GESTURE_WAKE, .mod = NULL, }, /* SENSOR_TYPE_GLANCE_GESTURE */ { .snsr_name = "glance_gesture", .typ = IIO_GESTURE_GLANCE, .mod = NULL, }, /* SENSOR_TYPE_PICK_UP_GESTURE */ { .snsr_name = "pick_up_gesture", .typ = IIO_GESTURE_PICKUP, .mod = NULL, }, /* SENSOR_TYPE_WRIST_TILT_GESTURE */ { .snsr_name = "wrist_tilt_gesture", .typ = IIO_GESTURE_WRIST_TILT, .mod = NULL, }, /* SENSOR_TYPE_DEVICE_ORIENTATION */ { .snsr_name = "device_orientation", .typ = IIO_DEVICE_ORIENTATION, .mod = NULL, }, /* SENSOR_TYPE_POSE_6DOF */ { .snsr_name = "pose_6dof", .typ = IIO_POSE_6DOF, .mod = NULL, }, /* SENSOR_TYPE_STATIONARY_DETECT */ { .snsr_name = "stationary_detect", .typ = IIO_STATIONARY_DETECT, .mod = NULL, }, /* SENSOR_TYPE_MOTION_DETECT */ { .snsr_name = "motion_detect", .typ = IIO_MOTION_DETECT, .mod = NULL, }, /* SENSOR_TYPE_HEART_BEAT */ { .snsr_name = "heart_beat", .typ = IIO_HEART_BEAT, .mod = NULL, }, /* SENSOR_TYPE_DYNAMIC_SENSOR_META */ { .snsr_name = "dynamic_sensor_meta", .typ = IIO_DYNAMIC_SENSOR_META, .mod = NULL, }, /* SENSOR_TYPE_ADDITIONAL_INFO */ { .snsr_name = "additional_info", .typ = IIO_ADDITIONAL_INFO, .mod = NULL, }, }; static void nvs_mutex_lock(void *handle) { struct iio_dev *indio_dev = (struct iio_dev *)handle; if (indio_dev) mutex_lock(&indio_dev->mlock); } static void nvs_mutex_unlock(void *handle) { struct iio_dev *indio_dev = (struct iio_dev *)handle; if (indio_dev) mutex_unlock(&indio_dev->mlock); } static ssize_t nvs_dbg_cfg(struct iio_dev *indio_dev, char *buf) { struct nvs_state *st = iio_priv(indio_dev); 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 unsigned int nvs_buf_index(unsigned int size, unsigned int *bytes) { unsigned int index; if (!(*bytes % size)) index = *bytes; else index = (*bytes - (*bytes % size)) + size; *bytes = index + size; return index; } static ssize_t nvs_dbg_data(struct iio_dev *indio_dev, char *buf, size_t buf_n) { struct nvs_state *st = iio_priv(indio_dev); struct iio_chan_spec const *ch; ssize_t t; unsigned int buf_i; unsigned int ch_n; unsigned int n; unsigned int i; unsigned int j; u64 data; t = snprintf(buf, buf_n, "%s: ", st->cfg->name); n = 0; for (i = 0; i < indio_dev->num_channels - 1; i++) { ch = &indio_dev->channels[i]; if (iio_scan_mask_query(indio_dev, indio_dev->buffer, i)) { ch_n = ch->scan_type.storagebits / 8; buf_i = nvs_buf_index(ch_n, &n); } else { t += snprintf(buf + t, buf_n - t, "disabled "); continue; } if (!ch_n) continue; if (ch_n <= sizeof(data)) { data = 0LL; memcpy(&data, &st->buf[buf_i], ch_n); if (ch->scan_type.sign == 's') { j = 64 - ch->scan_type.realbits; t += snprintf(buf + t, buf_n - t, "%lld (0x", (s64)(data << j) >> j); } else { t += snprintf(buf + t, buf_n - t, "%llu (0x", data); } } else { t += snprintf(buf + t, buf_n - t, "? (0x"); } for (j = ch_n - 1; j > 0; j--) t += snprintf(buf + t, buf_n - t, "%02X", st->buf[buf_i + j]); t += snprintf(buf + t, buf_n - t, "%02X) ", st->buf[buf_i]); } t += snprintf(buf + t, buf_n - t, "ts=%lld ts_diff=%lld\n", st->ts, st->ts_diff); return t; } static int nvs_buf_i(struct iio_dev *indio_dev, unsigned int ch) { unsigned int buf_i = 0; unsigned int n; unsigned int i; if (!iio_scan_mask_query(indio_dev, indio_dev->buffer, ch)) return -EINVAL; n = 0; for (i = 0; i <= ch; i++) { if (iio_scan_mask_query(indio_dev, indio_dev->buffer, i)) buf_i = nvs_buf_index(indio_dev->channels[i].scan_type. storagebits / 8, &n); } return buf_i; } static int nvs_buf_push(struct iio_dev *indio_dev, unsigned char *data, s64 ts) { struct nvs_state *st = iio_priv(indio_dev); bool push = true; bool buf_data = false; char char_buf[128]; unsigned int n; unsigned int i; unsigned int src_i = 0; unsigned int data_chan_n; int ret = 0; data_chan_n = indio_dev->num_channels - 1; /* It's possible to have events without data (data_chan_n == 0). * In this case, just the timestamp is sent. */ if (data_chan_n) { if (st->on_change) /* on-change needs data change for push */ push = false; for (i = 0; i < data_chan_n; i++) { if (st->ch[i].i >= 0) { if (!data) { /* buffer calculations only */ src_i += st->ch[i].n; continue; } buf_data = true; if (st->on_change) { /* wasted cycles when st->first_push * but saved cycles in the long run. */ ret = memcmp(&st->buf[st->ch[i].i], &data[src_i], st->ch[i].n); if (ret) /* data changed */ push = true; } if (!(st->dbg_data_lock & (1 << i))) memcpy(&st->buf[st->ch[i].i], &data[src_i], st->ch[i].n); src_i += st->ch[i].n; } } } if (st->first_push || !buf_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->batch_period_us * 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); } if (indio_dev->buffer->scan_timestamp) memcpy(&st->buf[st->ch[data_chan_n].i], &ts, st->ch[data_chan_n].n); if (push && iio_buffer_enabled(indio_dev)) { ret = iio_push_to_buffers(indio_dev, st->buf); 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->ch[data_chan_n].i + st->ch[data_chan_n].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(indio_dev, 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 = src_i; return ret; } static int nvs_handler(void *handle, void *buffer, s64 ts) { struct iio_dev *indio_dev = (struct iio_dev *)handle; unsigned char *buf = buffer; int ret = 0; if (indio_dev) ret = nvs_buf_push(indio_dev, buf, ts); return ret; } static int nvs_enable(struct iio_dev *indio_dev, bool en) { struct nvs_state *st = iio_priv(indio_dev); unsigned int i; unsigned int n; int enable = 0; int ret; if (en) { i = 0; n = 0; if (indio_dev->num_channels > 1) { for (; i < indio_dev->num_channels - 1; i++) { if (iio_scan_mask_query(indio_dev, indio_dev->buffer, i)) { enable |= 1 << i; st->ch[i].i = nvs_buf_index(st->ch[i].n, &n); } else { st->ch[i].i = -1; } } } else { enable = 1; } st->ch[i].i = nvs_buf_index(st->ch[i].n, &n); st->first_push = true; ret = st->fn_dev->enable(st->client, st->cfg->snsr_id, enable); if (!ret) st->enabled = enable; } else { ret = nvs_disable(st); } if (*st->fn_dev->sts & NVS_STS_SPEW_MSG) dev_info(st->dev, "%s %s enable=%x ret=%d", __func__, st->cfg->name, enable, ret); 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; 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_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct nvs_state *st = iio_priv(indio_dev); struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); s8 matrix[9]; char *str; const char *msg; unsigned int new; unsigned int old = 0; int ret; if (this_attr->address != NVS_ATTR_MATRIX) { ret = kstrtouint(buf, 0, &new); if (ret) return -EINVAL; } mutex_lock(&indio_dev->mlock); if (st->shutdown || st->suspend || (*st->fn_dev->sts & (NVS_STS_SHUTDOWN | NVS_STS_SUSPEND))) { mutex_unlock(&indio_dev->mlock); return -EPERM; } switch (this_attr->address) { case NVS_ATTR_ENABLE: msg = "ATTR_ENABLE"; old = st->fn_dev->enable(st->client, st->cfg->snsr_id, -1); ret = nvs_enable(indio_dev, (bool)new); if (ret > 0) ret = 0; break; case NVS_ATTR_FLAGS: msg = "ATTR_FLAGS"; old = st->cfg->flags; st->cfg->flags &= SENSOR_FLAG_READONLY_MASK; new &= ~SENSOR_FLAG_READONLY_MASK; st->cfg->flags |= new; new = st->cfg->flags; break; case NVS_ATTR_MATRIX: msg = "ATTR_MATRIX"; for (new = 0; new < sizeof(matrix); new++) { str = strsep((char **)&buf, " \0"); if (str != NULL) { ret = kstrtos8(str, 10, &matrix[new]); if (ret) break; if (matrix[new] < -1 || matrix[new] > 1) { ret = -EINVAL; break; } } else { ret = -EINVAL; break; } } if (new == sizeof(matrix)) memcpy(st->cfg->matrix, matrix, sizeof(st->cfg->matrix)); break; default: msg = "ATTR_UNKNOWN"; ret = -EINVAL; } mutex_unlock(&indio_dev->mlock); if (*st->fn_dev->sts & NVS_STS_SPEW_MSG) { if (ret) dev_err(st->dev, "%s %s %s %d->%d ERR=%d\n", __func__, st->cfg->name, msg, old, new, ret); else dev_info(st->dev, "%s %s %s %d->%d\n", __func__, st->cfg->name, msg, old, new); } if (ret) return ret; return count; } static ssize_t nvs_attr_show(struct device *dev, struct device_attribute *attr, char *buf) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct nvs_state *st = iio_priv(indio_dev); struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); ssize_t t = 0; int ret = -EINVAL; unsigned int i; switch (this_attr->address) { case NVS_ATTR_ENABLE: if (st->fn_dev->enable != nvs_fn_dev_enable) { mutex_lock(&indio_dev->mlock); ret = snprintf(buf, PAGE_SIZE, "%x\n", st->fn_dev->enable(st->client, st->cfg->snsr_id, -1)); mutex_unlock(&indio_dev->mlock); } return ret; case NVS_ATTR_PART: return snprintf(buf, PAGE_SIZE, "%s %s\n", st->cfg->part, st->cfg->name); case NVS_ATTR_VENDOR: return snprintf(buf, PAGE_SIZE, "%s\n", st->cfg->vendor); case NVS_ATTR_VERSION: return snprintf(buf, PAGE_SIZE, "%d\n", st->cfg->version); case NVS_ATTR_MILLIAMP: return snprintf(buf, PAGE_SIZE, "%d.%06u\n", st->cfg->milliamp.ival, st->cfg->milliamp.fval); case NVS_ATTR_FIFO_RSRV_EVNT_CNT: return snprintf(buf, PAGE_SIZE, "%u\n", st->cfg->fifo_rsrv_evnt_cnt); case NVS_ATTR_FIFO_MAX_EVNT_CNT: return snprintf(buf, PAGE_SIZE, "%u\n", st->cfg->fifo_max_evnt_cnt); case NVS_ATTR_FLAGS: return snprintf(buf, PAGE_SIZE, "%u\n", st->cfg->flags); case NVS_ATTR_MATRIX: 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; case NVS_ATTR_SELF_TEST: if (st->fn_dev->self_test) { mutex_lock(&indio_dev->mlock); ret = st->fn_dev->self_test(st->client, st->cfg->snsr_id, buf); mutex_unlock(&indio_dev->mlock); return ret; } break; case NVS_ATTR_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, "\n"); return t; default: return -EINVAL; } return -EINVAL; } static const char NVS_DBG_KEY[] = { 0x54, 0x65, 0x61, 0x67, 0x61, 0x6E, 0x26, 0x52, 0x69, 0x69, 0x73, 0x00 }; static ssize_t nvs_info_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct nvs_state *st = iio_priv(indio_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_info_show(struct device *dev, struct device_attribute *attr, char *buf) { struct iio_dev *indio_dev = dev_get_drvdata(dev); struct nvs_state *st = iio_priv(indio_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(indio_dev, buf, PAGE_SIZE); case NVS_INFO_VER: return snprintf(buf, PAGE_SIZE, "version=%u\n", NVS_IIO_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(&indio_dev->mlock); ret = st->fn_dev->reset(st->client, st->cfg->snsr_id); mutex_unlock(&indio_dev->mlock); } 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(indio_dev, 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 int nvs_attr_rm(struct nvs_state *st, struct attribute *rm_attr) { unsigned int i; for (i = 0; i < ARRAY_SIZE(st->attrs) - 1; i++) { if (st->attrs[i] == rm_attr) { for (; i < ARRAY_SIZE(st->attrs) - 1; i++) st->attrs[i] = st->attrs[i + 1]; return 0; } } return -EINVAL; } static int nvs_attr(struct iio_dev *indio_dev) { struct nvs_state *st = iio_priv(indio_dev); unsigned int i; memcpy(st->attrs, nvs_attrs, sizeof(st->attrs)); /* test if matrix data */ for (i = 0; i < ARRAY_SIZE(st->cfg->matrix); i++) { if (st->cfg->matrix[i]) break; } if (i >= ARRAY_SIZE(st->cfg->matrix)) /* remove matrix attribute */ nvs_attr_rm(st, &iio_dev_attr_matrix.dev_attr.attr); if (st->fn_dev->self_test == NULL) nvs_attr_rm(st, &iio_dev_attr_self_test.dev_attr.attr); st->attr_group.attrs = st->attrs; return 0; } static int nvs_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { struct nvs_state *st = iio_priv(indio_dev); unsigned int n; int ret; switch (mask) { case IIO_CHAN_INFO_RAW: ret = nvs_buf_i(indio_dev, chan->scan_index); if (ret < 0) return ret; *val = 0; n = chan->scan_type.storagebits / 8; if (n > sizeof(*val)) n = sizeof(*val); memcpy(val, &st->buf[ret], n); return IIO_VAL_INT; case IIO_CHAN_INFO_BATCH_FLUSH: *val = (int)st->flush; return IIO_VAL_INT; case IIO_CHAN_INFO_BATCH_PERIOD: 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, val, NULL); else *val = st->batch_period_us; } else { *val = st->cfg->delay_us_min; } return IIO_VAL_INT; case IIO_CHAN_INFO_BATCH_TIMEOUT: 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, val); else *val = st->batch_timeout_us; } else { *val = st->cfg->delay_us_max; } return IIO_VAL_INT; case IIO_CHAN_INFO_BATCH_FLAGS: *val = st->batch_flags; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: if (st->cfg->ch_n_max) { if (chan->scan_index >= st->cfg->ch_n_max) return -EINVAL; *val = st->cfg->scales[chan->scan_index].ival; *val2 = st->cfg->scales[chan->scan_index].fval; } else { *val = st->cfg->scale.ival; *val2 = st->cfg->scale.fval; } if (st->cfg->float_significance) return IIO_VAL_INT_PLUS_NANO; return IIO_VAL_INT_PLUS_MICRO; case IIO_CHAN_INFO_OFFSET: if (st->cfg->ch_n_max) { if (chan->scan_index >= st->cfg->ch_n_max) return -EINVAL; *val = st->cfg->offsets[chan->scan_index].ival; *val2 = st->cfg->offsets[chan->scan_index].fval; } else { *val = st->cfg->offset.ival; *val2 = st->cfg->offset.fval; } if (st->cfg->float_significance) return IIO_VAL_INT_PLUS_NANO; return IIO_VAL_INT_PLUS_MICRO; case IIO_CHAN_INFO_THRESHOLD_LOW: *val = st->cfg->thresh_lo; return IIO_VAL_INT; case IIO_CHAN_INFO_THRESHOLD_HIGH: *val = st->cfg->thresh_hi; return IIO_VAL_INT; case IIO_CHAN_INFO_PEAK: *val = st->cfg->max_range.ival; *val2 = st->cfg->max_range.fval; if (st->cfg->float_significance) return IIO_VAL_INT_PLUS_NANO; return IIO_VAL_INT_PLUS_MICRO; case IIO_CHAN_INFO_PEAK_SCALE: *val = st->cfg->resolution.ival; *val2 = st->cfg->resolution.fval; if (st->cfg->float_significance) return IIO_VAL_INT_PLUS_NANO; return IIO_VAL_INT_PLUS_MICRO; default: ret = -EINVAL; } return ret; } static int nvs_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) { struct nvs_state *st = iio_priv(indio_dev); char *msg; int ch = chan->scan_index; int old = 0; int old2 = 0; int ret = 0; mutex_lock(&indio_dev->mlock); if (st->shutdown || st->suspend || (*st->fn_dev->sts & (NVS_STS_SHUTDOWN | NVS_STS_SUSPEND))) { mutex_unlock(&indio_dev->mlock); return -EPERM; } switch (mask) { case IIO_CHAN_INFO_BATCH_FLUSH: msg = "IIO_CHAN_INFO_BATCH_FLUSH"; old = (int)st->flush; st->flush = true; if (st->fn_dev->flush) { ret = st->fn_dev->flush(st->client, st->cfg->snsr_id); if (ret) { nvs_buf_push(indio_dev, NULL, 0); ret = 0; } } else { nvs_buf_push(indio_dev, NULL, 0); } break; case IIO_CHAN_INFO_BATCH_PERIOD: msg = "IIO_CHAN_INFO_BATCH_PERIOD"; old = st->batch_period_us; if (!st->one_shot) { if (val < st->cfg->delay_us_min) val = st->cfg->delay_us_min; if (st->cfg->delay_us_max && (val > st->cfg->delay_us_max)) val = st->cfg->delay_us_max; } if (st->fn_dev->batch) { ret = st->fn_dev->batch(st->client, st->cfg->snsr_id, st->batch_flags, val, st->batch_timeout_us); if (!ret) st->batch_period_us = val; } else { if (st->batch_timeout_us) ret = -EINVAL; else st->batch_period_us = val; } break; case IIO_CHAN_INFO_BATCH_TIMEOUT: msg = "IIO_CHAN_INFO_BATCH_TIMEOUT"; old = st->batch_timeout_us; st->batch_timeout_us = val; break; case IIO_CHAN_INFO_BATCH_FLAGS: msg = "IIO_CHAN_INFO_BATCH_FLAGS"; old = st->batch_flags; st->batch_flags = val; break; case IIO_CHAN_INFO_SCALE: msg = "IIO_CHAN_INFO_SCALE"; if (st->cfg->ch_n_max) { if (ch >= st->cfg->ch_n_max) { ret = -EINVAL; break; } old = st->cfg->scales[ch].ival; old2 = st->cfg->scales[ch].fval; if (st->fn_dev->scale) { ret = st->fn_dev->scale(st->client, st->cfg->snsr_id, ch, val); if (ret > 0) { st->cfg->scales[ch].ival = val; st->cfg->scales[ch].fval = val2; ret = 0; } } else { st->cfg->scales[ch].ival = val; st->cfg->scales[ch].fval = val2; } } else { old = st->cfg->scale.ival; old2 = st->cfg->scale.fval; if (st->fn_dev->scale) { ret = st->fn_dev->scale(st->client, st->cfg->snsr_id, -1, val); if (ret > 0) { st->cfg->scale.ival = val; st->cfg->scale.fval = val2; ret = 0; } } else { st->cfg->scale.ival = val; st->cfg->scale.fval = val2; } } break; case IIO_CHAN_INFO_OFFSET: msg = "IIO_CHAN_INFO_OFFSET"; if (st->cfg->ch_n_max) { if (ch >= st->cfg->ch_n_max) { ret = -EINVAL; break; } old = st->cfg->offsets[ch].ival; old2 = st->cfg->offsets[ch].fval; if (st->fn_dev->offset) { ret = st->fn_dev->offset(st->client, st->cfg->snsr_id, ch, val); if (ret > 0) { st->cfg->offsets[ch].ival = val; st->cfg->offsets[ch].fval = val2; ret = 0; } } else { st->cfg->offsets[ch].ival = val; st->cfg->offsets[ch].fval = val2; } } else { old = st->cfg->offset.ival; old2 = st->cfg->offset.fval; if (st->fn_dev->offset) { ret = st->fn_dev->offset(st->client, st->cfg->snsr_id, -1, val); if (ret > 0) { st->cfg->offset.ival = val; st->cfg->offset.fval = val2; ret = 0; } } else { st->cfg->offset.ival = val; st->cfg->offset.fval = val2; } } break; case IIO_CHAN_INFO_THRESHOLD_LOW: msg = "IIO_CHAN_INFO_THRESHOLD_LOW"; old = st->cfg->thresh_lo; if (st->fn_dev->thresh_lo) { ret = st->fn_dev->thresh_lo(st->client, st->cfg->snsr_id, val); if (ret > 0) { st->cfg->thresh_lo = val; ret = 0; } } else { st->cfg->thresh_lo = val; } break; case IIO_CHAN_INFO_THRESHOLD_HIGH: msg = "IIO_CHAN_INFO_THRESHOLD_HIGH"; old = st->cfg->thresh_hi; if (st->fn_dev->thresh_hi) { ret = st->fn_dev->thresh_hi(st->client, st->cfg->snsr_id, val); if (ret > 0) { st->cfg->thresh_hi = val; ret = 0; } } else { st->cfg->thresh_hi = val; } break; case IIO_CHAN_INFO_PEAK: msg = "IIO_CHAN_INFO_PEAK"; old = st->cfg->max_range.ival; old2 = st->cfg->max_range.fval; if (st->fn_dev->max_range) { ret = st->fn_dev->max_range(st->client, st->cfg->snsr_id, val); if (ret > 0) { st->cfg->max_range.ival = val; st->cfg->max_range.fval = val2; ret = 0; } } else { st->cfg->max_range.ival = val; st->cfg->max_range.fval = val2; } break; case IIO_CHAN_INFO_PEAK_SCALE: msg = "IIO_CHAN_INFO_PEAK_SCALE"; old = st->cfg->resolution.ival; old2 = st->cfg->resolution.fval; if (st->fn_dev->resolution) { ret = st->fn_dev->resolution(st->client, st->cfg->snsr_id, val); if (ret > 0) { st->cfg->resolution.ival = val; st->cfg->resolution.fval = val2; ret = 0; } } else { st->cfg->resolution.ival = val; st->cfg->resolution.fval = val2; } break; case IIO_CHAN_INFO_RAW: /* writing to raw is a debug feature allowing a sticky data * value to be pushed up and the same value pushed until the * device is turned off. */ msg = "IIO_CHAN_INFO_RAW"; ret = nvs_buf_i(indio_dev, ch); if (ret >= 0) { memcpy(&st->buf[ret], &val, chan->scan_type.storagebits / 8); st->dbg_data_lock |= (1 << ch); ret = nvs_buf_push(indio_dev, st->buf, st->ts); if (ret > 0) ret = 0; } break; default: msg = "IIO_CHAN_INFO_UNKNOWN"; ret = -EINVAL; } mutex_unlock(&indio_dev->mlock); if (*st->fn_dev->sts & NVS_STS_SPEW_MSG) { if (ret) dev_err(st->dev, "%s %s %s c=%d %d:%d->%d:%d ERR=%d\n", __func__, st->cfg->name, msg, ch, old, old2, val, val2, ret); else dev_info(st->dev, "%s %s %s chan=%d %d:%d->%d:%d\n", __func__, st->cfg->name, msg, ch, old, old2, val, val2); } return ret; } static int nvs_buffer_preenable(struct iio_dev *indio_dev) { struct nvs_state *st = iio_priv(indio_dev); if (st->shutdown || st->suspend || (*st->fn_dev->sts & (NVS_STS_SHUTDOWN | NVS_STS_SUSPEND))) return -EINVAL; return 0; } static int nvs_buffer_postenable(struct iio_dev *indio_dev) { int ret; ret = nvs_enable(indio_dev, true); /* never return > 0 to IIO buffer engine */ if (ret > 0) ret = 0; return ret; } static const struct iio_buffer_setup_ops nvs_buffer_setup_ops = { /* iio_sw_buffer_preenable: * Generic function for equal sized ring elements + 64 bit timestamp * Assumes that any combination of channels can be enabled. * Typically replaced to implement restrictions on what combinations * can be captured (hardware scan modes). */ .preenable = &nvs_buffer_preenable, /* iio_triggered_buffer_postenable: * Generic function that simply attaches the pollfunc to the trigger. * Replace this to mess with hardware state before we attach the * trigger. */ .postenable = &nvs_buffer_postenable, /* this driver relies on the NVS HAL to power off this device with the * master enable. *.predisable = N/A *.postdisable = N/A */ }; static const long nvs_info_mask_dflt = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_BATCH_FLAGS) | BIT(IIO_CHAN_INFO_BATCH_PERIOD) | BIT(IIO_CHAN_INFO_BATCH_TIMEOUT) | BIT(IIO_CHAN_INFO_BATCH_FLUSH) | BIT(IIO_CHAN_INFO_PEAK) | BIT(IIO_CHAN_INFO_PEAK_SCALE) | BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET); static const long nvs_info_mask_shared_by_type_dflt = BIT(IIO_CHAN_INFO_BATCH_FLAGS) | BIT(IIO_CHAN_INFO_BATCH_PERIOD) | BIT(IIO_CHAN_INFO_BATCH_TIMEOUT) | BIT(IIO_CHAN_INFO_BATCH_FLUSH) | BIT(IIO_CHAN_INFO_PEAK) | BIT(IIO_CHAN_INFO_PEAK_SCALE); static const struct iio_chan_spec nvs_ch_ts[] = { IIO_CHAN_SOFT_TIMESTAMP(0) }; static void nvs_chan_scan_type(struct iio_chan_spec *ch, int ch_sz) { if (ch_sz < 0) { /* signed data */ ch->scan_type.sign = 's'; ch->scan_type.storagebits = -8 * ch_sz; } else { /* unsigned data */ ch->scan_type.sign = 'u'; ch->scan_type.storagebits = 8 * ch_sz; } ch->scan_type.realbits = ch->scan_type.storagebits; } static int nvs_ch_init(struct nvs_state *st, struct iio_dev *indio_dev) { int i; size_t n; n = indio_dev->num_channels * sizeof(struct nvs_iio_channel); st->ch = devm_kzalloc(st->dev, n, GFP_KERNEL); if (st->ch == NULL) return -ENOMEM; for (i = 0; i < indio_dev->num_channels; i++) { st->ch[i].n = indio_dev->channels[i].scan_type.storagebits / 8; st->ch[i].i = -1; } return 0; } static int nvs_chan(struct iio_dev *indio_dev) { struct nvs_state *st = iio_priv(indio_dev); long info_mask_msk = 0; long info_mask = nvs_info_mask_dflt; long info_mask_shared_by_type = nvs_info_mask_shared_by_type_dflt; int ch_status_sz = 0; unsigned int buf_sz = 0; unsigned int ch_type_i; unsigned int i; if (st->cfg->ch_inf) { /* if channels already defined */ if (!st->cfg->ch_n) return -ENODEV; indio_dev->channels = (struct iio_chan_spec *)st->cfg->ch_inf; indio_dev->num_channels = st->cfg->ch_n; for (i = 0; i < st->cfg->ch_n; i++) nvs_buf_index(indio_dev->channels[i]. scan_type.storagebits / 8, &buf_sz); st->buf = devm_kzalloc(st->dev, buf_sz, GFP_KERNEL); if (st->buf == NULL) return -ENOMEM; return nvs_ch_init(st, indio_dev); } /* create IIO channels */ ch_type_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_iio_chs); i++) { if (!strcmp(st->cfg->name, nvs_iio_chs[i].snsr_name)) break; } if (i < ARRAY_SIZE(nvs_iio_chs)) ch_type_i = i; /* found matching name */ else if (ch_type_i >= ARRAY_SIZE(nvs_iio_chs)) ch_type_i = 0; /* use generic sensor parameters */ } else { /* no st->cfg->name - use st->cfg->snsr_id to specify device */ if (ch_type_i >= ARRAY_SIZE(nvs_iio_chs)) ch_type_i = 0; st->cfg->name = nvs_iio_chs[ch_type_i].snsr_name; } st->snsr_type = ch_type_i; i = st->cfg->ch_n; i++; /* timestamp */ if (st->cfg->snsr_data_n && st->cfg->ch_n) { /* if more data than channel data then status channel */ ch_status_sz = abs(st->cfg->ch_sz) * st->cfg->ch_n; ch_status_sz = st->cfg->snsr_data_n - ch_status_sz; if (ch_status_sz <= 0) ch_status_sz = 0; else i++; } st->chs = devm_kzalloc(st->dev, i * sizeof(struct iio_chan_spec), GFP_KERNEL); if (st->chs == NULL) return -ENOMEM; if (st->one_shot) info_mask_msk = BIT(IIO_CHAN_INFO_BATCH_FLAGS) | BIT(IIO_CHAN_INFO_BATCH_PERIOD) | BIT(IIO_CHAN_INFO_BATCH_TIMEOUT) | BIT(IIO_CHAN_INFO_BATCH_FLUSH); if (st->special) info_mask = BIT(IIO_CHAN_INFO_RAW); info_mask &= ~info_mask_msk; info_mask_shared_by_type &= ~info_mask_msk; info_mask_msk = 0; if (st->cfg->thresh_lo || st->cfg->thresh_hi) info_mask_msk = BIT(IIO_CHAN_INFO_THRESHOLD_LOW) | BIT(IIO_CHAN_INFO_THRESHOLD_HIGH); info_mask |= info_mask_msk; info_mask_shared_by_type |= info_mask_msk; if (!st->cfg->ch_n_max) info_mask_shared_by_type |= BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET); i = 0; if (st->cfg->ch_n) { /* It's possible to not have any data channels */ for (; i < st->cfg->ch_n; i++) { st->chs[i].type = nvs_iio_chs[ch_type_i].typ; nvs_chan_scan_type(&st->chs[i], st->cfg->ch_sz); if (st->cfg->ch_n - 1) { /* multiple channels */ if (nvs_iio_chs[ch_type_i].mod) st->chs[i].channel2 = nvs_iio_chs[ch_type_i].mod[i]; st->chs[i].info_mask_shared_by_type = info_mask_shared_by_type; st->chs[i].info_mask_separate = info_mask & ~info_mask_shared_by_type; st->chs[i].modified = 1; } else { /* single channel */ st->chs[i].info_mask_separate = info_mask; } st->chs[i].scan_index = i; nvs_buf_index(st->chs[i].scan_type.storagebits / 8, &buf_sz); } } if (ch_status_sz) { /* create a status channel */ st->chs[i].type = nvs_iio_chs[ch_type_i].typ; st->chs[i].channel2 = IIO_MOD_STATUS; nvs_chan_scan_type(&st->chs[i], ch_status_sz); st->chs[i].info_mask_separate = BIT(IIO_CHAN_INFO_RAW); st->chs[i].modified = 1; st->chs[i].scan_index = i; nvs_buf_index(st->chs[i].scan_type.storagebits / 8, &buf_sz); i++; } /* timestamp channel */ memcpy(&st->chs[i], &nvs_ch_ts, sizeof(nvs_ch_ts)); st->chs[i].scan_index = i; nvs_buf_index(st->chs[i].scan_type.storagebits / 8, &buf_sz); i++; st->buf = devm_kzalloc(st->dev, (size_t)buf_sz, GFP_KERNEL); if (st->buf == NULL) return -ENOMEM; indio_dev->channels = st->chs; indio_dev->num_channels = i; return nvs_ch_init(st, indio_dev); } static const struct iio_trigger_ops nvs_trigger_ops = { .owner = THIS_MODULE, }; static int nvs_suspend(void *handle) { struct iio_dev *indio_dev = (struct iio_dev *)handle; struct nvs_state *st; int ret = 0; if (indio_dev == NULL) return 0; st = iio_priv(indio_dev); mutex_lock(&indio_dev->mlock); 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(&indio_dev->mlock); return ret; } static int nvs_resume(void *handle) { struct iio_dev *indio_dev = (struct iio_dev *)handle; struct nvs_state *st; int ret = 0; if (indio_dev == NULL) return 0; st = iio_priv(indio_dev); mutex_lock(&indio_dev->mlock); 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(&indio_dev->mlock); return ret; } static void nvs_shutdown(void *handle) { struct iio_dev *indio_dev = (struct iio_dev *)handle; struct nvs_state *st; int ret; if (indio_dev == NULL) return; st = iio_priv(indio_dev); mutex_lock(&indio_dev->mlock); 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(&indio_dev->mlock); } static int nvs_remove(void *handle) { struct iio_dev *indio_dev = (struct iio_dev *)handle; struct nvs_state *st; if (indio_dev == NULL) return 0; st = iio_priv(indio_dev); if (st->init) iio_device_unregister(indio_dev); if (st->trig != NULL) { iio_trigger_unregister(st->trig); iio_trigger_free(st->trig); st->trig = NULL; indio_dev->trig = NULL; } if (indio_dev->buffer && !st->init) devm_iio_kfifo_free(st->dev, indio_dev->buffer); if (st->cfg->name) dev_info(st->dev, "%s (iio) %s snsr_id=%d\n", __func__, st->cfg->name, st->cfg->snsr_id); else dev_info(st->dev, "%s (iio) snsr_id=%d\n", __func__, st->cfg->snsr_id); if (st->init && st->cfg->flags & SENSOR_FLAG_DYNAMIC_SENSOR) nvs_dsm_iio(indio_dev->id, false, st->snsr_type, st->cfg->uuid); iio_device_free(indio_dev); return 0; } static int nvs_init(struct iio_dev *indio_dev, struct nvs_state *st) { struct iio_buffer *buffer; int ret; nvs_report_mode(st); ret = nvs_attr(indio_dev); if (ret) { dev_err(st->dev, "%s nvs_attr ERR=%d\n", __func__, ret); return ret; } ret = nvs_chan(indio_dev); if (ret) { dev_err(st->dev, "%s nvs_chan ERR=%d\n", __func__, ret); return ret; } indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->currentmode = INDIO_DIRECT_MODE; indio_dev->dev.parent = st->dev; indio_dev->name = st->cfg->name; st->info.driver_module = THIS_MODULE; st->info.attrs = &st->attr_group; st->info.read_raw = &nvs_read_raw; st->info.write_raw = &nvs_write_raw; indio_dev->info = &st->info; indio_dev->setup_ops = &nvs_buffer_setup_ops; buffer = devm_iio_kfifo_allocate(st->dev); if (!buffer) { dev_err(st->dev, "%s devm_iio_kfifo_allocate ERR\n", __func__); return -ENOMEM; } iio_device_attach_buffer(indio_dev, buffer); indio_dev->buffer->scan_timestamp = true; if (st->cfg->kbuf_sz) { indio_dev->buffer->access->set_length(indio_dev->buffer, st->cfg->kbuf_sz); indio_dev->buffer->access->request_update(indio_dev->buffer); } st->trig = iio_trigger_alloc("%s-dev%d", indio_dev->name, indio_dev->id); if (st->trig == NULL) { dev_err(st->dev, "%s iio_allocate_trigger ERR\n", __func__); return -ENOMEM; } st->trig->dev.parent = st->dev; st->trig->ops = &nvs_trigger_ops; ret = iio_trigger_register(st->trig); if (ret) { dev_err(st->dev, "%s iio_trigger_register ERR\n", __func__); return -ENOMEM; } indio_dev->trig = st->trig; indio_dev->modes |= INDIO_BUFFER_TRIGGERED; ret = iio_device_register(indio_dev); if (ret) { dev_err(st->dev, "%s iio_device_register ERR\n", __func__); return ret; } return 0; } static int nvs_probe(void **handle, void *dev_client, struct device *dev, struct nvs_fn_dev *fn_dev, struct sensor_cfg *snsr_cfg) { struct iio_dev *indio_dev; struct nvs_state *st; int ret; dev_info(dev, "%s (iio)\n", __func__); if (!snsr_cfg) 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; } #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) indio_dev = nvs_device_alloc(sizeof(*st), true); #else indio_dev = iio_device_alloc(sizeof(*st)); #endif if (indio_dev == NULL) { dev_err(dev, "%s iio_device_alloc ERR\n", __func__); return -ENOMEM; } st = iio_priv(indio_dev); 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(indio_dev, 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); if (st->chs) devm_kfree(st->dev, st->chs); if (st->ch) devm_kfree(st->dev, st->ch); if (st->buf) devm_kfree(st->dev, st->buf); nvs_remove(indio_dev); return ret; } st->init = true; *handle = indio_dev; if (st->cfg->flags & SENSOR_FLAG_DYNAMIC_SENSOR) nvs_dsm_iio(indio_dev->id, true, st->snsr_type, st->cfg->uuid); dev_info(st->dev, "%s (iio) %s done\n", __func__, st->cfg->name); return ret; } static struct nvs_fn_if nvs_fn_if_iio = { .probe = nvs_probe, .remove = nvs_remove, .shutdown = nvs_shutdown, .nvs_mutex_lock = nvs_mutex_lock, .nvs_mutex_unlock = nvs_mutex_unlock, .suspend = nvs_suspend, .resume = nvs_resume, .handler = nvs_handler, }; struct nvs_fn_if *nvs_iio(void) { return &nvs_fn_if_iio; } EXPORT_SYMBOL_GPL(nvs_iio); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("NVidia Sensor IIO module"); MODULE_AUTHOR("NVIDIA Corporation");