iio: light: Add Android sensors API 1.3 support.
Erik Lilliebjerg [Sun, 30 Nov 2014 10:56:35 +0000 (03:56 -0700)]
- Add support for SENSORS_DEVICE_API_VERSION_1_3
- Add support for LTR659PS
- Add support for ISL2902x
- Add common light (ALS) module
- Add common proximity module
- Fix JSA1127 overflows

Bug 200032233

Change-Id: I2f3393b64de6ab85f32a7e019b991caec336cb78
Signed-off-by: Erik Lilliebjerg <elilliebjerg@nvidia.com>
Reviewed-on: http://git-master/r/663645
(cherry picked from commit cf7c3261fe9485d265073587cdc04f8e6312fbee)
Reviewed-on: http://git-master/r/657335
Reviewed-by: Robert Collins <rcollins@nvidia.com>
Tested-by: Robert Collins <rcollins@nvidia.com>

17 files changed:
drivers/iio/common/nvs/Kconfig
drivers/iio/common/nvs/Makefile
drivers/iio/common/nvs/nvs_iio.c
drivers/iio/common/nvs/nvs_light.c [new file with mode: 0644]
drivers/iio/common/nvs/nvs_of_dt.c
drivers/iio/common/nvs/nvs_proximity.c [new file with mode: 0644]
drivers/iio/light/Kconfig
drivers/iio/light/Makefile
drivers/iio/light/nvs_cm3217.c
drivers/iio/light/nvs_cm3218.c
drivers/iio/light/nvs_isl2902x.c [new file with mode: 0644]
drivers/iio/light/nvs_jsa1127.c
drivers/iio/light/nvs_ltr659.c [new file with mode: 0644]
drivers/iio/light/nvs_max4400x.c
include/linux/nvs.h
include/linux/nvs_light.h [new file with mode: 0644]
include/linux/nvs_proximity.h [new file with mode: 0644]

index ee81c84..10e0831 100644 (file)
@@ -6,4 +6,27 @@ config NVS_IIO
        depends on SYSFS && IIO && IIO_KFIFO_BUF && IIO_TRIGGER
        default n
        help
-         Support for IIO NVSensor framework.
+         Say Y here for IIO NVSensor framework support.
+         This is a common module for NVS (NVidia Sensor)
+         IIO (Industrial Input/Output) drivers (nvs_<device>).
+
+config NVS_LIGHT
+       tristate "Common module for NVSensor light drivers"
+       depends on SYSFS && IIO && IIO_KFIFO_BUF && IIO_TRIGGER
+       default n
+       help
+         Say Y here for the NVSensor framework for ambient light sensors.
+
+         This is required for NVS (NVidia Sensor) drivers (nvs_<device>)
+         that control an ambient light sensor (ALS) in their device.
+         This is typically autoselected by these drivers.
+
+config NVS_PROXIMITY
+       tristate "Common module for NVSensor proximity drivers"
+       depends on SYSFS && IIO && IIO_KFIFO_BUF && IIO_TRIGGER
+       default n
+       help
+         Say Y here for the NVSensor framework for proximity sensors.
+         This is required for NVS (NVidia Sensor) drivers (nvs_<device>)
+         that control a proximity sensor in their device.
+
index 02baebf..e6432d8 100644 (file)
@@ -2,3 +2,6 @@
 # Makefile for NVSensor IIO.
 #
 obj-$(CONFIG_NVS_IIO) += nvs_iio.o nvs_vreg.o nvs_of_dt.o
+obj-$(CONFIG_NVS_LIGHT) += nvs_light.o
+obj-$(CONFIG_NVS_PROXIMITY) += nvs_proximity.o
+
index c468bc2..60bffd2 100644 (file)
  * 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.
+ */
 
 
 #include <linux/init.h>
@@ -68,7 +77,7 @@
 
 #define NVS_IIO_DRIVER_VERSION         (200)
 #define NVS_IIO_SENSOR_TYPE_ERR                (0xFF)
-#define NVS_ATTRS_ARRAY_SIZE           (11)
+#define NVS_ATTRS_ARRAY_SIZE           (12)
 
 enum NVS_ATTR {
        NVS_ATTR_ENABLE,
@@ -80,6 +89,7 @@ enum NVS_ATTR {
        NVS_ATTR_FIFO_MAX_EVNT_CNT,
        NVS_ATTR_FLAGS,
        NVS_ATTR_MATRIX,
+       NVS_ATTR_SELF_TEST,
 };
 
 enum NVS_DBG {
@@ -105,6 +115,8 @@ struct nvs_state {
        struct attribute *attrs[NVS_ATTRS_ARRAY_SIZE];
        struct attribute_group attr_group;
        struct iio_info info;
+       bool shutdown;
+       bool suspend;
        bool flush;
        int batch_flags;
        unsigned int batch_period_us;
@@ -201,7 +213,7 @@ static ssize_t nvs_dbg_data(struct iio_dev *indio_dev, char *buf)
        unsigned int i;
        u64 data;
 
-       t = sprintf(buf, "data: ");
+       t = sprintf(buf, "%s: ", st->cfg->name);
        n = 0;
        for (i = 0; i < indio_dev->num_channels - 1; i++) {
                ch = &indio_dev->channels[i];
@@ -327,7 +339,8 @@ static ssize_t nvs_attr_store(struct device *dev,
                return -EINVAL;
 
        mutex_lock(&indio_dev->mlock);
-       if (*st->fn_dev->sts & (NVS_STS_SHUTDOWN | NVS_STS_SUSPEND)) {
+       if (st->shutdown || st->suspend || (*st->fn_dev->sts &
+                                      (NVS_STS_SHUTDOWN | NVS_STS_SUSPEND))) {
                mutex_unlock(&indio_dev->mlock);
                return -EPERM;
        }
@@ -375,7 +388,7 @@ static ssize_t nvs_attr_show(struct device *dev,
                                                  st->cfg->snsr_id, -1));
 
        case NVS_ATTR_PART:
-               return sprintf(buf, "%s\n", st->cfg->part);
+               return sprintf(buf, "%s %s\n", st->cfg->part, st->cfg->name);
 
        case NVS_ATTR_VENDOR:
                return sprintf(buf, "%s\n", st->cfg->vendor);
@@ -409,6 +422,12 @@ static ssize_t nvs_attr_show(struct device *dev,
                               st->cfg->matrix[7],
                               st->cfg->matrix[8]);
 
+       case NVS_ATTR_SELF_TEST:
+               if (st->fn_dev->self_test)
+                       return st->fn_dev->self_test(st->client,
+                                                    st->cfg->snsr_id, buf);
+               break;
+
        default:
                return -EINVAL;
        }
@@ -543,6 +562,8 @@ static IIO_DEVICE_ATTR(flags, S_IRUGO,
                       nvs_attr_show, NULL, NVS_ATTR_FLAGS);
 static IIO_DEVICE_ATTR(matrix, S_IRUGO,
                       nvs_attr_show, NULL, 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,
@@ -555,13 +576,32 @@ static struct attribute *nvs_attrs[] = {
        &iio_dev_attr_fifo_max_event_count.dev_attr.attr,
        &iio_dev_attr_flags.dev_attr.attr,
        &iio_dev_attr_matrix.dev_attr.attr,
+       &iio_dev_attr_self_test.dev_attr.attr,
        NULL
 };
 
+static int nvs_attr_rm(struct nvs_state *st, struct attribute *rm_attr)
+{
+       unsigned int n;
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(st->attrs); i++) {
+               if (st->attrs[i] == rm_attr) {
+                       do {
+                               n = i + 1;
+                               st->attrs[i] = st->attrs[n];
+                               i++;
+                       } while (st->attrs[i] != NULL);
+                       return 0;
+               }
+       }
+
+       return -EINVAL;
+}
+
 static int nvs_attr(struct iio_dev *indio_dev)
 {
        struct nvs_state *st = iio_priv(indio_dev);
-       unsigned int n;
        unsigned int i;
 
        BUG_ON(NVS_ATTRS_ARRAY_SIZE < ARRAY_SIZE(nvs_attrs));
@@ -571,20 +611,12 @@ static int nvs_attr(struct iio_dev *indio_dev)
                if (st->cfg->matrix[i])
                        break;
        }
-       if (i >= ARRAY_SIZE(st->cfg->matrix)) {
+       if (i >= ARRAY_SIZE(st->cfg->matrix))
                /* remove matrix attribute */
-               for (i = 0; i < ARRAY_SIZE(st->attrs); i++) {
-                       if (st->attrs[i] == &iio_dev_attr_matrix.
-                                           dev_attr.attr) {
-                               do {
-                                       n = i + 1;
-                                       st->attrs[i] = st->attrs[n];
-                                       i++;
-                               } while (st->attrs[i] != NULL);
-                               break;
-                       }
-               }
-       }
+               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;
@@ -679,7 +711,8 @@ static int nvs_write_raw(struct iio_dev *indio_dev,
        int ret = 0;
 
        mutex_lock(&indio_dev->mlock);
-       if (*st->fn_dev->sts & (NVS_STS_SHUTDOWN | NVS_STS_SUSPEND)) {
+       if (st->shutdown || st->suspend || (*st->fn_dev->sts &
+                                      (NVS_STS_SHUTDOWN | NVS_STS_SUSPEND))) {
                mutex_unlock(&indio_dev->mlock);
                return -EPERM;
        }
@@ -841,7 +874,8 @@ static int nvs_buffer_preenable(struct iio_dev *indio_dev)
 {
        struct nvs_state *st = iio_priv(indio_dev);
 
-       if (*st->fn_dev->sts & (NVS_STS_SHUTDOWN | NVS_STS_SUSPEND))
+       if (st->shutdown || st->suspend || (*st->fn_dev->sts &
+                                        (NVS_STS_SHUTDOWN | NVS_STS_SUSPEND)))
                return -EINVAL;
 
        return iio_sw_buffer_preenable(indio_dev);
@@ -997,16 +1031,47 @@ static const struct iio_trigger_ops nvs_trigger_ops = {
 
 static int nvs_suspend(void *handle)
 {
-       return 0;
+       struct iio_dev *indio_dev = (struct iio_dev *)handle;
+       struct nvs_state *st = iio_priv(indio_dev);
+       int ret = 0;
+
+       mutex_lock(&indio_dev->mlock);
+       st->suspend = true;
+       if (!st->cfg->no_suspend) {
+               ret = st->fn_dev->enable(st->client, st->cfg->snsr_id, -1);
+               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 = iio_priv(indio_dev);
+
+       mutex_lock(&indio_dev->mlock);
+       st->suspend = false;
+       mutex_unlock(&indio_dev->mlock);
        return 0;
 }
 
 static void nvs_shutdown(void *handle)
 {
+       struct iio_dev *indio_dev = (struct iio_dev *)handle;
+       struct nvs_state *st = iio_priv(indio_dev);
+       int ret;
+
+       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)
diff --git a/drivers/iio/common/nvs/nvs_light.c b/drivers/iio/common/nvs/nvs_light.c
new file mode 100644 (file)
index 0000000..eccd15c
--- /dev/null
@@ -0,0 +1,369 @@
+/* Copyright (c) 2014, 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 common NVS ALS module allows, along with the NVS IIO common module, an
+ * ALS driver to offload the code interacting with IIO and ALS reporting, and
+ * just have code that interacts with the HW.
+ * The commonality between this module and the NVS ALS driver is the nvs_light
+ * structure.  It is expected that the NVS ALS driver will:
+ * - call nvs_light_enable when the device is enabled to initialize variables.
+ * - read the HW and place the value in nvs_light.hw
+ * - call nvs_light_read
+ * - depending on the nvs_light_read return value:
+ *     - -1 = poll HW using nvs_light.poll_delay_ms delay.
+ *     - 0 = if interrupt driven, do nothing or resume regular polling
+ *     - 1 = set new thresholds using the nvs_light.hw_thresh_lo/hi
+ * Reporting the lux is handled within this module.
+ * See nvs_light.h for nvs_light structure details.
+ */
+/* 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.
+ */
+/* The configuration threshold values are HW value based.  In other words, to
+ * obtain the upper and lower HW thresholds, the configuration threshold is
+ * simply added or subtracted from the HW data read, respectively.
+ * A little history about this: this code originally expected the configuration
+ * threshold values to be in lux.  It then converted the threshold lux value to
+ * a HW value by reversing the calibrated value to uncalibrated and then the
+ * scaling of the resolution.  The idea was to make configuration easy by just
+ * setting how much lux needed to change before reporting.  However, there were
+ * two issues with this method:
+ * 1) that's a lot of overhead for each sample, and 2) the lux range isn't
+ * exactly linear.  Lux values in a dark room will probably want to be reported
+ * every +/- 10 or 100 if not less.  This is opposed to a bright room or even
+ * outside on a sunny day where lux value changes can be reported every
+ * +/- 1000 or even 10000.  Since many ALS's have dynamic resolution, changing
+ * the range depending on the lux reading, it makes sense to use HW threshold
+ * values that will automatically scale with the HW resolution used.
+ */
+/* NVS light drivers have two calibration mechanisms:
+ * Method 1 (preferred):
+ * This method uses interpolation and requires a low and high uncalibrated
+ * value along with the corresponding low and high calibrated values.  The
+ * uncalibrated values are what is read from the sensor in the steps below.
+ * The corresponding calibrated values are what the correct value should be.
+ * All values are programmed into the device tree settings.
+ * 1. Read scale sysfs attribute.  This value will need to be written back.
+ * 2. Disable device.
+ * 3. Write 1 to the scale sysfs attribute.
+ * 4. Enable device.
+ * 5. The NVS HAL will announce in the log that calibration mode is enabled and
+ *    display the data along with the scale and offset parameters applied.
+ * 6. Write the scale value read in step 1 back to the scale sysfs attribute.
+ * 7. Put the device into a state where the data read is a low value.
+ * 8. Note the values displayed in the log.  Separately measure the actual
+ *    value.  The value from the sensor will be the uncalibrated value and the
+ *    separately measured value will be the calibrated value for the current
+ *    state (low or high values).
+ * 9. Put the device into a state where the data read is a high value.
+ * 10. Repeat step 8.
+ * 11. Enter the values in the device tree settings for the device.  Both
+ *     calibrated and uncalibrated values will be the values before scale and
+ *     offset are applied.
+ *     The light sensor has the following device tree parameters for this:
+ *     light_uncalibrated_lo
+ *     light_calibrated_lo
+ *     light_uncalibrated_hi
+ *     light_calibrated_hi
+ *
+ * Method 2:
+ * 1. Disable device.
+ * 2. Write 1 to the scale sysfs attribute.
+ * 3. Enable device.
+ * 4. The NVS HAL will announce in the log that calibration mode is enabled and
+ *    display the data along with the scale and offset parameters applied.
+ * 5. Write to scale and offset sysfs attributes as needed to get the data
+ *    modified as desired.
+ * 6. Disabling the device disables calibration mode.
+ * 7. Set the new scale and offset parameters in the device tree:
+ *    light_scale_ival = the integer value of the scale.
+ *    light_scale_fval = the floating value of the scale.
+ *    light_offset_ival = the integer value of the offset.
+ *    light_offset_fval = the floating value of the offset.
+ *    The values are in the NVS_SCALE_SIGNIFICANCE format (see nvs.h).
+ */
+/* The reason calibration method 1 is preferred is that the NVS ALS driver
+ * already sets the scaling to coordinate with the resolution by multiplying
+ * the HW data value read with resolution * scaling and then divides it back
+ * down with the scaling so that no significance is lost.
+ */
+
+
+#include <linux/of.h>
+#include <linux/nvs_light.h>
+
+
+static unsigned int nvs_light_interpolate(int x1, s64 x2, int x3,
+                                         int y1, int y3)
+{
+       s64 dividend;
+       s64 divisor;
+
+       /* y2 = ((x2 - x1)(y3 - y1)/(x3 - x1)) + y1 */
+       divisor = (x3 - x1);
+       if (!divisor)
+               return (unsigned int)x2;
+
+       dividend = (x2 - x1) * (y3 - y1);
+       do_div(dividend, divisor);
+       dividend += y1;
+       if (dividend < 0)
+               dividend = 0;
+       return (unsigned int)dividend;
+}
+
+static int nvs_light_nld(struct nvs_light *nl, unsigned int nld_i)
+{
+       nl->nld_i = nld_i;
+       nl->nld_i_change = true;
+       nl->cfg->resolution.ival = nl->nld_tbl[nld_i].resolution.ival;
+       nl->cfg->resolution.fval = nl->nld_tbl[nld_i].resolution.fval;
+       nl->cfg->max_range.ival = nl->nld_tbl[nld_i].max_range.ival;
+       nl->cfg->max_range.fval = nl->nld_tbl[nld_i].max_range.fval;
+       nl->cfg->milliamp.ival = nl->nld_tbl[nld_i].milliamp.ival;
+       nl->cfg->milliamp.fval = nl->nld_tbl[nld_i].milliamp.fval;
+       nl->cfg->delay_us_min = nl->nld_tbl[nld_i].delay_min_ms * 1000;
+       return RET_POLL_NEXT;
+}
+
+/**
+ * nvs_light_read - called after HW is read and placed in nl.
+ * @nl: the common structure between driver and common module.
+ *
+ * This will handle the conversion of HW to lux value,
+ * reporting, calculation of thresholds and poll time.
+ *
+ * Returns: -1 = Error and/or polling is required for next
+ *               sample regardless of being interrupt driven.
+ *          0 = Do nothing.  Lux has not changed for reporting
+ *              and same threshold values if interrupt driven.
+ *              If not interrupt driven use poll_delay_ms.
+ *          1 = New HW thresholds are needed.
+ *              If not interrupt driven use poll_delay_ms.
+ */
+int nvs_light_read(struct nvs_light *nl)
+{
+       u64 calc_i;
+       u64 calc_f;
+       s64 calc;
+       s64 timestamp_diff;
+       s64 delay;
+       bool report_delay_min = true;
+       unsigned int poll_delay = 0;
+       unsigned int thresh_lo;
+       unsigned int thresh_hi;
+       int ret;
+
+       if (nl->calibration_en)
+               /* always report without report_delay_min */
+               nl->report = nl->cfg->report_n;
+       if (nl->report < nl->cfg->report_n) { /* always report first sample */
+               /* calculate elapsed time for allowed report rate */
+               timestamp_diff = nl->timestamp - nl->timestamp_report;
+               delay = nl->delay_us * 1000;
+               if (timestamp_diff < delay) {
+                       /* data changes are happening faster than allowed to
+                        * report so we poll for the next data at an allowed
+                        * rate with interrupts disabled.
+                        */
+                       delay -= timestamp_diff;
+                       do_div(delay, 1000); /* ns => us */
+                       poll_delay = delay;
+                       report_delay_min = false;
+               }
+       }
+       /* threshold flags */
+       thresh_lo = nl->cfg->thresh_lo;
+       thresh_hi = nl->cfg->thresh_lo;
+       if (thresh_lo < nl->hw_mask) {
+               nl->thresh_valid_lo = true;
+       } else {
+               nl->thresh_valid_lo = false;
+               thresh_lo = 0;
+       }
+       if (thresh_hi < nl->hw_mask) {
+               nl->thresh_valid_hi = true;
+       } else {
+               nl->thresh_valid_hi = false;
+               thresh_hi = 0;
+       }
+       if (nl->thresh_valid_lo && nl->thresh_valid_hi)
+               nl->thresholds_valid = true;
+       else
+               nl->thresholds_valid = false;
+       /* limit flags */
+       if ((nl->hw < thresh_lo) || (nl->hw == 0))
+               nl->hw_limit_lo = true;
+       else
+               nl->hw_limit_lo = false;
+       if ((nl->hw == nl->hw_mask) || (nl->hw > (nl->hw_mask - thresh_hi)))
+               nl->hw_limit_hi = true;
+       else
+               nl->hw_limit_hi = false;
+       /* reporting and thresholds */
+       if (nl->nld_i_change) {
+               /* HW resolution just changed.  Need thresholds and reporting
+                * based on new settings.  Reporting may not be this cycle due
+                * to report_delay_min.
+                */
+               nl->report = nl->cfg->report_n;
+       } else {
+               if (nl->thresholds_valid) {
+                       if (nl->hw < nl->hw_thresh_lo)
+                               nl->report = nl->cfg->report_n;
+                       else if (nl->hw > nl->hw_thresh_hi)
+                               nl->report = nl->cfg->report_n;
+               } else {
+                       /* report everything if no thresholds */
+                       nl->report = nl->cfg->report_n;
+               }
+       }
+       ret = RET_NO_CHANGE;
+       /* lux reporting */
+       if (nl->report && report_delay_min) {
+               nl->report--;
+               nl->timestamp_report = nl->timestamp;
+               /* lux = HW * (resolution * NVS_SCALE_SIGNIFICANCE) / scale */
+               calc_f = 0;
+               if (nl->cfg->resolution.fval) {
+                       calc_f = (u64)(nl->hw * nl->cfg->resolution.fval);
+                       if (nl->cfg->scale.fval)
+                               do_div(calc_f, nl->cfg->scale.fval);
+               }
+               calc_i = 0;
+               if (nl->cfg->resolution.ival) {
+                       calc_i = NVS_SCALE_SIGNIFICANCE / nl->cfg->scale.fval;
+                       calc_i *= (u64)(nl->hw * nl->cfg->resolution.ival);
+               }
+               calc = (s64)(calc_i + calc_f);
+               /* get calibrated value */
+               nl->lux = nvs_light_interpolate(nl->cfg->uncal_lo, calc,
+                                               nl->cfg->uncal_hi,
+                                               nl->cfg->cal_lo,
+                                               nl->cfg->cal_hi);
+               /* report lux */
+               nl->handler(nl->nvs_data, &nl->lux, nl->timestamp_report);
+               if ((nl->thresholds_valid) && !nl->report) {
+                       /* calculate low threshold */
+                       calc = (s64)nl->hw;
+                       calc -= thresh_lo;
+                       if (calc < 0)
+                               /* low threshold is disabled */
+                               nl->hw_thresh_lo = 0;
+                       else
+                               nl->hw_thresh_lo = calc;
+                       /* calculate high threshold */
+                       calc = nl->hw + thresh_hi;
+                       if (calc > nl->hw_mask)
+                               /* high threshold is disabled */
+                               nl->hw_thresh_hi = nl->hw_mask;
+                       else
+                               nl->hw_thresh_hi = calc;
+                       ret = RET_HW_UPDATE;
+               }
+       }
+       /* dynamic resolution */
+       nl->nld_i_change = false;
+       if (nl->nld_tbl) { /* if dynamic resolution is enabled */
+               /* adjust resolution if need to make room for thresholds */
+               if (nl->hw_limit_hi && (nl->nld_i < nl->nld_i_hi))
+                       /* too many photons - need to increase resolution */
+                       ret = nvs_light_nld(nl, nl->nld_i + 1);
+               else if (nl->hw_limit_lo && (nl->nld_i > nl->nld_i_lo))
+                       /* not enough photons - need to decrease resolution */
+                       ret = nvs_light_nld(nl, nl->nld_i - 1);
+       }
+       /* poll time */
+       if (nl->nld_i_change) {
+               nl->poll_delay_ms = nl->nld_tbl[nl->nld_i].delay_min_ms;
+       } else {
+               if (report_delay_min)
+                       poll_delay = nl->delay_us;
+               if ((poll_delay < nl->cfg->delay_us_min) || nl->calibration_en)
+                       poll_delay = nl->cfg->delay_us_min;
+               nl->poll_delay_ms = poll_delay / 1000;
+       }
+       if (nl->report || nl->calibration_en)
+               ret = RET_POLL_NEXT; /* poll for next sample */
+       return ret;
+}
+
+/**
+ * nvs_light_enable - called when the light sensor is enabled.
+ * @nl: the common structure between driver and common module.
+ *
+ * This inititializes the nl NVS variables.
+ *
+ * Returns 0 on success or a negative error code.
+ */
+int nvs_light_enable(struct nvs_light *nl)
+{
+       if (!nl->cfg->report_n)
+               nl->cfg->report_n = 1;
+       nl->report = nl->cfg->report_n;
+       nl->timestamp_report = 0;
+       nl->hw_thresh_hi = 0;
+       nl->hw_thresh_lo = -1;
+       if (nl->nld_tbl)
+               nvs_light_nld(nl, nl->nld_i_hi);
+       else
+               nl->poll_delay_ms = nl->cfg->delay_us_min / 1000;
+       if (nl->cfg->scale.ival == 1 && !nl->cfg->scale.fval)
+               nl->calibration_en = true;
+       else
+               nl->calibration_en = false;
+       return 0;
+}
+
+/**
+ * nvs_light_of_dt - called during system boot to acquire
+ * dynamic resolution table index limits.
+ * @nl: the common structure between driver and common module.
+ * @np: device node pointer.
+ * @dev_name: device name string.  Typically a string to "light"
+ *            or NULL.
+ *
+ * Returns 0 on success or a negative error code.
+ *
+ * Driver must initialize variables if no success.
+ * NOTE: DT must have both indexes for a success.
+ */
+int nvs_light_of_dt(struct nvs_light *nl, const struct device_node *np,
+                   const char *dev_name)
+{
+       char str[256];
+       int ret;
+       int ret_t = -EINVAL;
+
+       if (np == NULL)
+               return -EINVAL;
+
+       if (dev_name == NULL)
+               dev_name = NVS_LIGHT_STRING;
+       ret = sprintf(str, "%s_dynamic_resolution_index_limit_low", dev_name);
+       if (ret > 0)
+               ret_t = of_property_read_u32(np, str, &nl->nld_i_lo);
+       ret = sprintf(str, "%s_dynamic_resolution_index_limit_high", dev_name);
+       if (ret > 0)
+               ret_t |= of_property_read_u32(np, str, &nl->nld_i_hi);
+       if (nl->nld_i_hi < nl->nld_i_lo)
+               return -EINVAL;
+
+       return ret_t;
+}
+
index 16cc15d..89ed538 100644 (file)
@@ -30,79 +30,79 @@ int nvs_of_dt(const struct device_node *np, struct sensor_cfg *cfg,
                dev_name = cfg->name;
        ret = sprintf(str, "%s_buffer_size", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->kbuf_sz);
+               of_property_read_s32(np, str, (s32 *)&cfg->kbuf_sz);
        ret = sprintf(str, "%s_max_range_ival", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->max_range.ival);
+               of_property_read_s32(np, str, (s32 *)&cfg->max_range.ival);
        ret = sprintf(str, "%s_max_range_fval", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->max_range.fval);
+               of_property_read_s32(np, str, (s32 *)&cfg->max_range.fval);
        ret = sprintf(str, "%s_resolution_ival", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->resolution.ival);
+               of_property_read_s32(np, str, (s32 *)&cfg->resolution.ival);
        ret = sprintf(str, "%s_resolution_fval", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->resolution.fval);
+               of_property_read_s32(np, str, (s32 *)&cfg->resolution.fval);
        ret = sprintf(str, "%s_milliamp_ival", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->milliamp.ival);
+               of_property_read_s32(np, str, (s32 *)&cfg->milliamp.ival);
        ret = sprintf(str, "%s_milliamp_fval", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->milliamp.fval);
+               of_property_read_s32(np, str, (s32 *)&cfg->milliamp.fval);
        ret = sprintf(str, "%s_delay_us_min", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->delay_us_min);
+               of_property_read_s32(np, str, (s32 *)&cfg->delay_us_min);
        ret = sprintf(str, "%s_delay_us_max", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->delay_us_max);
+               of_property_read_s32(np, str, (s32 *)&cfg->delay_us_max);
        ret = sprintf(str, "%s_fifo_max_event_count", dev_name);
        if (ret > 0)
-               of_property_read_u32(np, str, (u32 *)cfg->fifo_rsrv_evnt_cnt);
+               of_property_read_u32(np, str, (u32 *)&cfg->fifo_rsrv_evnt_cnt);
        ret = sprintf(str, "%s_fifo_reserved_event_count", dev_name);
        if (ret > 0)
-               of_property_read_u32(np, str, (u32 *)cfg->fifo_max_evnt_cnt);
+               of_property_read_u32(np, str, (u32 *)&cfg->fifo_max_evnt_cnt);
        ret = sprintf(str, "%s_flags", dev_name);
        if (ret > 0)
-               of_property_read_u32(np, str, (u32 *)cfg->flags);
+               of_property_read_u32(np, str, (u32 *)&cfg->flags);
        ret = sprintf(str, "%s_matrix", dev_name);
        if (ret > 0) {
                charp = of_get_property(np, str, &lenp);
                if (charp && lenp == sizeof(cfg->matrix))
-                       memcpy(cfg->matrix, charp, lenp);
+                       memcpy(&cfg->matrix, charp, lenp);
        }
        ret = sprintf(str, "%s_scale_ival", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->scale.ival);
+               of_property_read_s32(np, str, (s32 *)&cfg->scale.ival);
        ret = sprintf(str, "%s_scale_fval", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->scale.fval);
+               of_property_read_s32(np, str, (s32 *)&cfg->scale.fval);
        ret = sprintf(str, "%s_offset_ival", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->offset.ival);
+               of_property_read_s32(np, str, (s32 *)&cfg->offset.ival);
        ret = sprintf(str, "%s_offset_fval", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->offset.fval);
+               of_property_read_s32(np, str, (s32 *)&cfg->offset.fval);
        ret = sprintf(str, "%s_uncalibrated_lo", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->uncal_lo);
+               of_property_read_s32(np, str, (s32 *)&cfg->uncal_lo);
        ret = sprintf(str, "%s_uncalibrated_hi", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->uncal_hi);
+               of_property_read_s32(np, str, (s32 *)&cfg->uncal_hi);
        ret = sprintf(str, "%s_calibrated_lo", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->cal_lo);
+               of_property_read_s32(np, str, (s32 *)&cfg->cal_lo);
        ret = sprintf(str, "%s_calibrated_hi", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->cal_hi);
+               of_property_read_s32(np, str, (s32 *)&cfg->cal_hi);
        ret = sprintf(str, "%s_threshold_lo", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->thresh_lo);
+               of_property_read_s32(np, str, (s32 *)&cfg->thresh_lo);
        ret = sprintf(str, "%s_threshold_hi", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->thresh_hi);
+               of_property_read_s32(np, str, (s32 *)&cfg->thresh_hi);
        ret = sprintf(str, "%s_report_count", dev_name);
        if (ret > 0)
-               of_property_read_s32(np, str, (s32 *)cfg->report_n);
+               of_property_read_s32(np, str, (s32 *)&cfg->report_n);
        return 0;
 }
 
diff --git a/drivers/iio/common/nvs/nvs_proximity.c b/drivers/iio/common/nvs/nvs_proximity.c
new file mode 100644 (file)
index 0000000..97bcdac
--- /dev/null
@@ -0,0 +1,403 @@
+/* Copyright (c) 2014, 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 common NVS proximity module allows, along with the NVS IIO common
+ * module, a proximity driver to offload the code interacting with IIO and
+ * proximity reporting, and just have code that interacts with the HW.
+ * The commonality between this module and the NVS ALS driver is the
+ * nvs_proximity structure.  It is expected that the NVS proximity driver will:
+ * - call nvs_proximity_enable when the device is enabled for initialization.
+ * - read the HW and place the value in nvs_proximity.hw
+ * - call nvs_proximity_read
+ * - depending on the nvs_proximity_read return value:
+ *     - -1 = poll HW using nvs_proximity.poll_delay_ms delay.
+ *     - 0 = if interrupt driven, do nothing or resume regular polling
+ *     - 1 = set new thresholds using the nvs_proximity.hw_thresh_lo/hi
+ * Reporting the distance is handled within this module.
+ * See nvs_proximity.h for nvs_proximity structure details.
+ */
+/* NVS proximity drivers can be configured for binary output.  If the max_range
+ * and resolution settings in the device tree is set for 1.0, the driver
+ * will configure the rest of the settings so that a 1 is reported for
+ * "far away" and 0 for "near".  The low threshold is typically set for maximum
+ * range allowing the minimal LED drive power to determine the actual range.
+ * If proximity binary output is disabled, the driver will then require the
+ * interpolation calibration for reporting actual distances.
+ */
+/* 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.
+ */
+/* Because the proximity HW can use dynamic resolution depending on the
+ * distance range, configuration threshold values are HW based.  In other
+ * words, the threshold will automatically scale based on the resolution.
+ */
+/* If the NVS proximity driver is not configured for binary output, then
+ * there are two calibration mechanisms that can be used:
+ * Method 1 (preferred):
+ * This method uses interpolation and requires a low and high uncalibrated
+ * value along with the corresponding low and high calibrated values.  The
+ * uncalibrated values are what is read from the sensor in the steps below.
+ * The corresponding calibrated values are what the correct value should be.
+ * All values are programmed into the device tree settings.
+ * 1. Read scale sysfs attribute.  This value will need to be written back.
+ * 2. Disable device.
+ * 3. Write 1 to the scale sysfs attribute.
+ * 4. Enable device.
+ * 5. The NVS HAL will announce in the log that calibration mode is enabled and
+ *    display the data along with the scale and offset parameters applied.
+ * 6. Write the scale value read in step 1 back to the scale sysfs attribute.
+ * 7. Put the device into a state where the data read is a low value.
+ * 8. Note the values displayed in the log.  Separately measure the actual
+ *    value.  The value from the sensor will be the uncalibrated value and the
+ *    separately measured value will be the calibrated value for the current
+ *    state (low or high values).
+ * 9. Put the device into a state where the data read is a high value.
+ * 10. Repeat step 8.
+ * 11. Enter the values in the device tree settings for the device.  Both
+ *     calibrated and uncalibrated values will be the values before scale and
+ *     offset are applied.
+ *     The proximity sensor has the following device tree parameters for this:
+ *     proximity_uncalibrated_lo
+ *     proximity_calibrated_lo
+ *     proximity_uncalibrated_hi
+ *     proximity_calibrated_hi
+ *
+ * Method 2:
+ * 1. Disable device.
+ * 2. Write 1 to the scale sysfs attribute.
+ * 3. Enable device.
+ * 4. The NVS HAL will announce in the log that calibration mode is enabled and
+ *    display the data along with the scale and offset parameters applied.
+ * 5. Write to scale and offset sysfs attributes as needed to get the data
+ *    modified as desired.
+ * 6. Disabling the device disables calibration mode.
+ * 7. Set the new scale and offset parameters in the device tree:
+ *    proximity_scale_ival = the integer value of the scale.
+ *    proximity_scale_fval = the floating value of the scale.
+ *    proximity_offset_ival = the integer value of the offset.
+ *    proximity_offset_fval = the floating value of the offset.
+ *    The values are in the NVS_SCALE_SIGNIFICANCE format (see nvs.h).
+ */
+/* The reason calibration method 1 is preferred is that the NVS proximity
+ * driver already sets the scaling to coordinate with the resolution by
+ * multiplying the HW data value read with resolution * scaling and then
+ * divides it back down with the scaling so that no significance is lost.
+ */
+/* If the NVS proximity driver is configured for binary output, then
+ * interpolation is not used and the thresholds are used to trigger either the
+ * 0 or 1 output.  To calibrate the thresholds:
+ * 1. Disable device.
+ * 2. Write 1 to the scale sysfs attribute.
+ * 3. Enable device.
+ * 4. The NVS HAL will announce in the log that calibration mode is enabled and
+ *    display the HW proximity data.
+ * 5. Move an object (your hand) through the proximity range.  Note the HW
+ *    value when the object is at a point that the output should be 0.  This
+ *    will be the high threshold value.  Move the object away from the sensor
+ *    and note the HW value where the output should change to 1.  This will be
+ *    the low threshold value.
+ * NOTE: Proximity typically works by reading the reflected IR light from an
+ *       LED.  The more light reflected, the higher the HW value and the closer
+ *       the object is.  Because of this, the thresholds appear to be reversed
+ *       to the output, but keep in mind, the thresholds are HW based, so low
+ *       threshold means low HW value regardless of the actual output.
+ * NOTE: If greater range is needed, modify the LED output strength if the
+ *       proximity HW supports it.  This will be a DT configuration option that
+ *       is specific to the driver and HW.
+ * 6. Enter the threshold values in the device tree settings for the device.
+ *     The proximity sensor has the following device tree parameters for this:
+ *     proximity_threshold_lo
+ *     proximity_threshold_hi
+ */
+/* If the NVS proximity driver is not configured for binary output, then the
+ * thresholds are used for hysterysis.  The threshold settings are HW based and
+ * allow a window around the last reported HW value.  For example, if the low
+ * threshold is set to 10 and the high threshold set to 20, if the proximity HW
+ * value is 100, the proximity won't be reported again until the proximity HW
+ * value is either < 90 or > than 120.
+ * The low/high threshold values are typically the same, but they can be
+ * configured so that proximity changes at a different rate based on the
+ * direction of change.
+ * Use the calibration methods for a steady output of data to get an idea of
+ * the debounce desired.
+ * NOTE: If both configuration thresholds are 0, then thresholds are disabled.
+ * NOTE: An NVS feature is the use of the report_count configuration variable,
+ *       proximity_report_count in DT (see nvs.h).  This allows additional
+ *       reporting of proximity a set amount of times while still within the
+ *       threshold window.
+ */
+/* If the NVS proximity driver is configured for binary output, then the
+ * thresholds are absolute HW values.  If not configured for binary output,
+ * then the thresholds are relative HW values to set a trigger window around
+ * the last read HW value.
+ */
+
+
+#include <linux/nvs_proximity.h>
+
+
+static void nvs_proximity_interpolate(int x1, s64 x2, int x3,
+                                     int y1, unsigned int *y2, int y3)
+{
+       s64 dividend;
+       s64 divisor;
+
+       /* y2 = ((x2 - x1)(y3 - y1)/(x3 - x1)) + y1 */
+       divisor = (x3 - x1);
+       if (!divisor) {
+               *y2 = (unsigned int)x2;
+               return;
+       }
+
+       dividend = (x2 - x1) * (y3 - y1);
+       do_div(dividend, divisor);
+       dividend += y1;
+       if (dividend < 0)
+               dividend = 0;
+       *y2 = (unsigned int)dividend;
+}
+
+/**
+ * nvs_proximity_read - called after HW is read and written to
+ *                      np.
+ * @np: the common structure between driver and common module.
+ *
+ * This will handle the conversion of HW to distance value,
+ * reporting, calculation of thresholds and poll time.
+ *
+ * Returns: -1 = Error and/or polling is required for next
+ *               sample regardless of being interrupt driven.
+ *          0 = Do nothing.  Value has not changed for reporting
+ *              and same threshold values if interrupt driven.
+ *              If not interrupt driven use poll_delay_ms.
+ *          1 = New HW thresholds are needed.
+ *              If not interrupt driven use poll_delay_ms.
+ */
+int nvs_proximity_read(struct nvs_proximity *np)
+{
+       u64 calc_i;
+       u64 calc_f;
+       s64 calc;
+       s64 timestamp_diff;
+       s64 delay;
+       bool report_delay_min = true;
+       unsigned int poll_delay = 0;
+       unsigned int thresh_lo;
+       unsigned int thresh_hi;
+       unsigned int hw_distance;
+       int ret;
+
+       if (np->calibration_en)
+               /* always report without report_delay_min */
+               np->report = np->cfg->report_n;
+       if (np->report < np->cfg->report_n) { /* always report first sample */
+               /* calculate elapsed time for allowed report rate */
+               timestamp_diff = np->timestamp - np->timestamp_report;
+               delay = np->delay_us * 1000;
+               if (timestamp_diff < delay) {
+                       /* data changes are happening faster than allowed to
+                        * report so we poll for the next data at an allowed
+                        * rate with interrupts disabled.
+                        */
+                       delay -= timestamp_diff;
+                       do_div(delay, 1000); /* ns => us */
+                       poll_delay = delay;
+                       report_delay_min = false;
+               }
+       }
+       /* threshold flags */
+       thresh_lo = np->cfg->thresh_lo;
+       thresh_hi = np->cfg->thresh_hi;
+       if (thresh_lo < np->hw_mask) {
+               np->thresh_valid_lo = true;
+       } else {
+               np->thresh_valid_lo = false;
+               thresh_lo = 0;
+       }
+       if (thresh_hi < np->hw_mask) {
+               np->thresh_valid_hi = true;
+       } else {
+               np->thresh_valid_hi = false;
+               thresh_hi = 0;
+       }
+       if (np->thresh_valid_lo && np->thresh_valid_hi)
+               np->thresholds_valid = true;
+       else
+               np->thresholds_valid = false;
+       /* limit flags */
+       if ((np->hw < thresh_lo) || (np->hw == 0))
+               np->hw_limit_lo = true;
+       else
+               np->hw_limit_lo = false;
+       if (np->proximity_binary_en) {
+               if (np->hw > thresh_hi)
+                       np->hw_limit_hi = true;
+               else
+                       np->hw_limit_hi = false;
+       } else {
+               if ((np->hw == np->hw_mask) || (np->hw >
+                                               (np->hw_mask - thresh_hi)))
+                       np->hw_limit_hi = true;
+               else
+                       np->hw_limit_hi = false;
+       }
+       ret = RET_NO_CHANGE;
+       if (np->proximity_binary_en) {
+               /* proximity has binary threshold */
+               if (!np->thresholds_valid) {
+                       /* Invalid thresholds is an NVS feature that forces
+                        * polling.  However, with this binary mechanism,
+                        * thresholds are required.  So although the feature
+                        * is somewhat crippled, we make it work by setting
+                        * the trigger in the middle of the HW range.
+                        */
+                       thresh_lo = np->hw_mask / 2;
+                       thresh_hi = thresh_lo;
+                       np->report = np->cfg->report_n;
+               }
+               if (np->hw < np->hw_thresh_lo) {
+                       np->proximity = 1;
+                       np->report = np->cfg->report_n;
+                       /* disable lower threshold */
+                       np->hw_thresh_lo = 0;
+                       /* enable upper threshold */
+                       np->hw_thresh_hi = thresh_hi;
+               } else if (np->hw > np->hw_thresh_hi) {
+                       np->proximity = 0;
+                       np->report = np->cfg->report_n;
+                       /* disable upper threshold */
+                       np->hw_thresh_hi = np->hw_mask;
+                       /* enable lower threshold */
+                       np->hw_thresh_lo = thresh_lo;
+               }
+               if (np->calibration_en)
+                       np->proximity = np->hw;
+               if (np->report && report_delay_min) {
+                       np->report--;
+                       np->timestamp_report = np->timestamp;
+                       np->handler(np->nvs_data, &np->proximity,
+                                   np->timestamp_report);
+                       ret = RET_HW_UPDATE;
+               }
+       } else {
+               /* reporting and thresholds */
+               if (np->thresholds_valid) {
+                       if (np->hw < np->hw_thresh_lo)
+                               np->report = np->cfg->report_n;
+                       else if (np->hw > np->hw_thresh_hi)
+                               np->report = np->cfg->report_n;
+               } else {
+                       /* report everything if no thresholds */
+                       np->report = np->cfg->report_n;
+               }
+               if (np->report && report_delay_min) {
+                       np->report--;
+                       np->timestamp_report = np->timestamp;
+                       /* reverse the value in the range */
+                       hw_distance = np->hw_mask - np->hw;
+                       /* distance = HW * (resolution *
+                        *                  NVS_SCALE_SIGNIFICANCE) / scale
+                        */
+                       calc_f = 0;
+                       if (np->cfg->resolution.fval) {
+                               calc_f = (u64)(hw_distance *
+                                              np->cfg->resolution.fval);
+                               if (np->cfg->scale.fval)
+                                       do_div(calc_f, np->cfg->scale.fval);
+                       }
+                       calc_i = 0;
+                       if (np->cfg->resolution.ival) {
+                               calc_i = NVS_SCALE_SIGNIFICANCE /
+                                                          np->cfg->scale.fval;
+                               calc_i *= (u64)(hw_distance *
+                                               np->cfg->resolution.ival);
+                       }
+                       calc = (s64)(calc_i + calc_f);
+                       /* get calibrated value */
+                       nvs_proximity_interpolate(np->cfg->uncal_lo, calc,
+                                                 np->cfg->uncal_hi,
+                                                 np->cfg->cal_lo,
+                                                 &np->proximity,
+                                                 np->cfg->cal_hi);
+                       /* report proximity */
+                       np->handler(np->nvs_data, &np->proximity,
+                                   np->timestamp_report);
+                       if ((np->thresholds_valid) && !np->report) {
+                               /* calculate low threshold */
+                               calc = (s64)np->hw;
+                               calc -= thresh_lo;
+                               if (calc < 0)
+                                       /* low threshold is disabled */
+                                       np->hw_thresh_lo = 0;
+                               else
+                                       np->hw_thresh_lo = calc;
+                               /* calculate high threshold */
+                               calc = np->hw + thresh_hi;
+                               if (calc > np->hw_mask)
+                                       /* high threshold is disabled */
+                                       np->hw_thresh_hi = np->hw_mask;
+                               else
+                                       np->hw_thresh_hi = calc;
+                               ret = RET_HW_UPDATE;
+                       }
+               }
+       }
+       if (report_delay_min)
+               poll_delay = np->delay_us;
+       if ((poll_delay < np->cfg->delay_us_min) || np->calibration_en)
+               poll_delay = np->cfg->delay_us_min;
+       np->poll_delay_ms = poll_delay / 1000;
+       if (np->report || np->calibration_en)
+               ret = RET_POLL_NEXT; /* poll for next sample */
+       return ret;
+}
+
+/**
+ * nvs_proximity_enable - called when the proximity sensor is
+ *                        enabled.
+ * @np: the common structure between driver and common module.
+ *
+ * This inititializes the np NVS variables.
+ *
+ * Returns 0 on success or a negative error code.
+ */
+int nvs_proximity_enable(struct nvs_proximity *np)
+{
+       if (!np->cfg->report_n)
+               np->cfg->report_n = 1;
+       np->report = np->cfg->report_n;
+       np->timestamp_report = 0;
+       np->hw_thresh_hi = 0;
+       np->hw_thresh_lo = -1;
+       np->proximity = 1;
+       if (np->cfg->resolution.ival == 1 && !np->cfg->resolution.fval &&
+                     np->cfg->max_range.ival == 1 && !np->cfg->max_range.fval)
+               np->proximity_binary_en = true;
+       else
+               np->proximity_binary_en = false;
+       if (np->cfg->scale.ival == 1 && !np->cfg->scale.fval)
+               np->calibration_en = true;
+       else
+               np->calibration_en = false;
+       if (np->delay_us)
+               np->poll_delay_ms = np->delay_us * 1000;
+       else
+               np->poll_delay_ms = np->cfg->delay_us_min * 1000;
+       return 0;
+}
+
index 6df7792..64c4f27 100644 (file)
@@ -65,39 +65,88 @@ config HID_SENSOR_ALS
 
 config NVS_CM3217
        tristate "Capella CM3217 ALS"
+       select NVS_IIO
+       select NVS_LIGHT
        select IIO_BUFFER
        select IIO_TRIGGERED_BUFFER
        depends on I2C
        help
-        Say Y here for Capella Microsystems, Inc. CM3217
-        Ambient Light Sensor (ALS) support.
+         Say Y here for Capella Microsystems, Inc. CM3217
+         Ambient Light Sensor (ALS) support.
+
+         The driver uses the NVS (NVidia Sensor) IIO, light framework.
 
 config NVS_CM3218
        tristate "Capella CM3218 ALS"
+       select NVS_IIO
+       select NVS_LIGHT
        select IIO_BUFFER
        select IIO_TRIGGERED_BUFFER
        depends on I2C
        help
-        Say Y here for Capella Microsystems, Inc. CM3218 and CM32181
-        Ambient Light Sensor (ALS) support.
+         Say Y here for Capella Microsystems, Inc. CM3218 and CM32181
+         Ambient Light Sensor (ALS) driver support.
 
-config NVS_MAX4400X
-       tristate "Maxim MAX4400X ALS/Proximity"
+         The driver uses the NVS (NVidia Sensor) IIO, light framework.
+
+config NVS_ISL2902X
+       tristate "INTERSIL ISL2902X ALS/Proximity"
+       select NVS_IIO
+       select NVS_LIGHT
+       select NVS_PROXIMITY
        select IIO_BUFFER
        select IIO_TRIGGERED_BUFFER
        depends on I2C
        help
-        Say Y here for Maxim MAX44005 and MAX44006/MAX44008
-        Ambient Light Sensor (ALS) support.
-        Proximity is supported on the MAX44005.
+         Say Y here for Intersil ISL29028 and ISL29029
+         Ambient Light Sensor (ALS) and proximity driver
+         support.
+
+         The driver uses the NVS (NVidia Sensor) IIO, light and
+         proximity framework.
 
 config NVS_JSA1127
        tristate "SOLTEAMOPTO JSA1127 ALS"
+       select NVS_IIO
+       select NVS_LIGHT
+       select IIO_BUFFER
+       select IIO_TRIGGERED_BUFFER
+       depends on I2C
+       help
+         Say Y here for Solteamopto JSA1127
+         Ambient Light Sensor (ALS) driver support.
+
+         The driver uses the NVS (NVidia Sensor) IIO, light framework.
+
+config NVS_LTR659
+       tristate "LITEON LTR-659PS or LTR_558ALS"
+       select NVS_IIO
+       select NVS_LIGHT
+       select NVS_PROXIMITY
+       select IIO_BUFFER
+       select IIO_TRIGGERED_BUFFER
+       depends on I2C
+       help
+         Say Y here for LITEON LTR-659PS or LTR-558ALS
+         ALS/proximity sensor driver support.
+
+         The driver uses the NVS (NVidia Sensor) IIO, light and
+         proximity framework.
+
+config NVS_MAX4400X
+       tristate "Maxim MAX4400X ALS/Proximity"
+       select NVS_IIO
+       select NVS_LIGHT
+       select NVS_PROXIMITY
        select IIO_BUFFER
        select IIO_TRIGGERED_BUFFER
        depends on I2C
        help
-        Say Y here for Solteamopto JSA1127
-        Ambient Light Sensor (ALS) support.
+         Say Y here for Maxim MAX44005 and MAX44006/MAX44008
+         Ambient Light Sensor (ALS) support.
+         Proximity is supported on the MAX44005.
+
+         The driver uses the NVS (NVidia Sensor) IIO, light and
+         proximity framework.
 
 endmenu
index 671ee3c..4aa1e43 100644 (file)
@@ -14,4 +14,6 @@ obj-$(CONFIG_NVS_CM3217)      += nvs_cm3217.o
 obj-$(CONFIG_NVS_CM3218)       += nvs_cm3218.o
 obj-$(CONFIG_NVS_MAX4400X)     += nvs_max4400x.o
 obj-$(CONFIG_NVS_JSA1127)      += nvs_jsa1127.o
+obj-$(CONFIG_NVS_ISL2902X)     += nvs_isl2902x.o
+obj-$(CONFIG_NVS_LTR659)       += nvs_ltr659.o
 
index b00146e..77e3baf 100644 (file)
  * GNU General Public License for more details.
  */
 
-/* IT = Integration Time.  The amount of time the photons hit the sensor.
- * STEP = the value from HW which is the photon count during IT.
- * LUX = STEP * (CM_RESOLUTION_STEP / IT) / CM_RESOLUTION_DIVISOR
- * The above LUX reported as LUX * CM_LIGHT_DIVISOR.
- */
 /* 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 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.
- */
-/* NVS light/proximity drivers have two calibration mechanisms:
- * Method 1 (preferred):
- * This method uses interpolation and requires a low and high uncalibrated
- * value along with the corresponding low and high calibrated values.  The
- * uncalibrated values are what is read from the sensor in the steps below.
- * The corresponding calibrated values are what the correct value should be.
- * All values are programmed into the device tree settings.
- * 1. Read scale sysfs attribute.  This value will need to be written back.
- * 2. Disable device.
- * 3. Write 1 to the scale sysfs attribute.
- * 4. Enable device.
- * 5. The NVS HAL will announce in the log that calibration mode is enabled and
- *    display the data along with the scale and offset parameters applied.
- * 6. Write the scale value read in step 1 back to the scale sysfs attribute.
- * 7. Put the device into a state where the data read is a low value.
- * 8. Note the values displayed in the log.  Separately measure the actual
- *    value.  The value from the sensor will be the uncalibrated value and the
- *    separately measured value will be the calibrated value for the current
- *    state (low or high values).
- * 9. Put the device into a state where the data read is a high value.
- * 10. Repeat step 8.
- * 11. Enter the values in the device tree settings for the device.  Both
- *     calibrated and uncalibrated values will be the values before scale and
- *     offset are applied.
- *     For example, a light sensor has the following device tree parameters:
- *     light_uncalibrated_lo
- *     light_calibrated_lo
- *     light_uncalibrated_hi
- *     light_calibrated_hi
- *     The proximity sensor parameters are:
- *     proximity_uncalibrated_lo
- *     proximity_calibrated_lo
- *     proximity_uncalibrated_hi
- *     proximity_calibrated_hi
- *
- * Method 2:
- * 1. Disable device.
- * 2. Write 1 to the scale sysfs attribute.
- * 3. Enable device.
- * 4. The NVS HAL will announce in the log that calibration mode is enabled and
- *    display the data along with the scale and offset parameters applied.
- * 5. Write to scale and offset sysfs attributes as needed to get the data
- *    modified as desired.
- * 6. Disabling the device disables calibration mode.
- * 7. Set the new scale and offset parameters in the device tree:
- *    <IIO channel>_scale_val
- *    <IIO channel>_scale_val2
- *    <IIO channel>_offset_val
- *    <IIO channel>_offset_val2
- *    The values are in IIO IIO_VAL_INT_PLUS_MICRO format.
- */
+/* See nvs_iio.c and nvs.h for documentation */
+/* See nvs_light.c and nvs_light.h for documentation */
 
 
 #include <linux/i2c.h>
 #include <linux/regulator/consumer.h>
 #include <linux/workqueue.h>
 #include <linux/of.h>
-#include <linux/iio/iio.h>
-#include <linux/iio/sysfs.h>
-#include <linux/iio/buffer.h>
-#include <linux/iio/kfifo_buf.h>
-#include <linux/iio/trigger.h>
+#include <linux/nvs.h>
+#include <linux/nvs_light.h>
 
-#define CM_VERSION_DRIVER              (103)
 #define CM_VENDOR                      "Capella Microsystems, Inc."
 #define CM_NAME                                "cm3217"
-/* setting _REPORT_N to 2 causes an extra reading after crossing the threshold
- * allowing a more accurate settled reported value.
- */
-#define CM_REPORT_N                    (2)
 #define CM_LIGHT_VERSION               (1)
 #define CM_LIGHT_MAX_RANGE_IVAL                (119154)
 #define CM_LIGHT_MAX_RANGE_MICRO       (420000)
 #define CM_LIGHT_RESOLUTION_IVAL       (0)
-#define CM_LIGHT_RESOLUTION_MICRO      (10000)
+#define CM_LIGHT_RESOLUTION_MICRO      (18750)
+#define CM_LIGHT_MILLIAMP_IVAL         (0)
+#define CM_LIGHT_MILLIAMP_MICRO                (90000)
 #define CM_LIGHT_SCALE_IVAL            (0)
 #define CM_LIGHT_SCALE_MICRO           (10000)
 #define CM_LIGHT_OFFSET_IVAL           (0)
 #define CM_LIGHT_OFFSET_MICRO          (0)
-#define CM_LIGHT_MILLIAMP              "0.09"
-#define CM_RESOLUTION_STEP             (6000000L)
-#define CM_RESOLUTION_DIVISOR          (1000L)
-#define CM_LIGHT_DIVISOR               (1000000 / CM_LIGHT_SCALE_MICRO)
-#define CM_POLL_DELAY_MS_DFLT          (2000)
-#define CM_THRESHOLD_LUX_DFLT          (100)
+#define CM_LIGHT_THRESHOLD_LO          (100)
+#define CM_LIGHT_THRESHOLD_HI          (100)
+#define CM_POLL_DLY_MS_MIN             (100)
+#define CM_POLL_DLY_MS_MAX             (4000)
 /* HW registers */
 #define CM_I2C_ADDR_CMD1_WR            (0x10)
 #define CM_I2C_ADDR_CMD2_WR            (0x11)
 #define CM_HW_CMD1_DFLT                        (0x22)
 #define CM_HW_CMD1_BIT_SD              (0)
 #define CM_HW_CMD1_BIT_IT_T            (2)
+#define CM_HW_CMD1_IT_T_MASK           (0x0C)
 #define CM_HW_CMD2_BIT_FD_IT           (5)
+#define CM_HW_CMD2_FD_IT_MASK          (0xE0)
 #define CM_HW_DELAY_MS                 (10)
-/* _buf_push expects this scan order */
-#define CM_SCAN_LIGHT                  0
-#define CM_SCAN_TIMESTAMP              1
-/* debug flags */
-#define CM_DBG_SPEW_MSG                        (1 << 0)
-#define CM_DBG_SPEW_LIGHT              (1 << 1)
-#define CM_DBG_SPEW_LIGHT_POLL         (1 << 2)
-#define CM_DBG_VAL_LIGHT               (1 << 3)
-
-enum CM_ATTR {
-       CM_ATTR_PART,
-       CM_ATTR_VENDOR,
-       CM_ATTR_VERSION,
-       CM_ATTR_MILLIAMP,
-       CM_ATTR_ENABLE,
-       CM_ATTR_THRESH_LO,
-       CM_ATTR_THRESH_HI,
-};
 
-enum CM_INFO {
-       CM_INFO_DATA = 0,
-       CM_INFO_VER,
-       CM_INFO_ERRS,
-       CM_INFO_REGS,
-       CM_INFO_DBG,
-       CM_INFO_LIGHT_SPEW,
-       CM_INFO_LIGHT_POLL_SPEW,
-       CM_INFO_LIMIT_MAX,
-};
 
 /* regulator names in order of powering on */
 static char *cm_vregs[] = {
        "vdd",
 };
 
-struct cm_it {                         /* integration time */
-       unsigned int ms;                /* time ms */
-       u8 fd_it;                       /* FD_IT HW */
-       u8 it_t;                        /* IT_T HW */
-};
-
-static struct cm_it cm_it_tbl[] = {    /* (ms)       lux_lo    lux_hi   */
-       { 3200, 0, 3 },                 /* (800ms * 4)  0.01 <-> 1228.78 */
-       { 1600, 0, 2 },                 /* (800ms * 2)  0.03 <-> 2457.56 */
-       { 800, 0, 1 },                  /* (800ms * 1)  0.07 <-> 4915.12 */
-       { 400, 1, 1 },                  /* (400ms * 1)  0.15 <-> 9830.25 */
-       { 266, 2, 1 },                  /* (266ms * 1) 0.22 <-> 14782.07 */
-       { 200, 3, 1 },                  /* (200ms * 1) 0.30 <-> 19660.50 */
-       { 130, 4, 1 },                  /* (130ms * 1) 0.46 <-> 30246.36 */
-       { 100, 5, 1 },                  /* (100ms * 1) 0.60 <-> 39321.00 */
-       { 80, 6, 1 },                   /* (80ms * 1)  0.75 <-> 49151.25 */
-       { 66, 7, 1 },                   /* (66ms * 1)  0.90 <-> 59577.21 */
-       { 33, 7, 0 }                    /* (66ms / 2) 1.81 <-> 119154.42 */
-                                       /* note: lux_lo = resolution */
+static struct nvs_light_dynamic cm_nld_tbl[] = {
+       {{0, 18750},  {1228,   780000}, {0, 90000}, 3210, (0 << 5) | (3 << 2)},
+       {{0, 37500},  {2457,   560000}, {0, 90000}, 1610, (0 << 5) | (2 << 2)},
+       {{0, 75000},  {4915,   120000}, {0, 90000}, 810,  (0 << 5) | (1 << 2)},
+       {{0, 150000}, {9830,   250000}, {0, 90000}, 410,  (1 << 5) | (1 << 2)},
+       {{0, 225564}, {14782,  70000},  {0, 90000}, 276,  (2 << 5) | (1 << 2)},
+       {{0, 300000}, {19660,  500000}, {0, 90000}, 210,  (3 << 5) | (1 << 2)},
+       {{0, 461539}, {30246,  360000}, {0, 90000}, 140,  (4 << 5) | (1 << 2)},
+       {{0, 600000}, {39321,  0},      {0, 90000}, 110,  (5 << 5) | (1 << 2)},
+       {{0, 750000}, {49151,  250000}, {0, 90000}, 90,   (6 << 5) | (1 << 2)},
+       {{0, 909091}, {59577,  210000}, {0, 90000}, 76,   (7 << 5) | (1 << 2)},
+       {{1, 818181}, {119154, 420000}, {0, 90000}, 43,   (7 << 5) | (0 << 2)}
 };
 
 struct cm_state {
        struct i2c_client *i2c;
-       struct iio_trigger *trig;
+       struct nvs_fn_if *nvs;
+       void *nvs_data;
+       struct sensor_cfg cfg;
        struct delayed_work dw;
        struct regulator_bulk_data vreg[ARRAY_SIZE(cm_vregs)];
-       unsigned int info;              /* info data to return */
-       unsigned int dbg;               /* debug flags */
+       struct nvs_light light;
+       unsigned int sts;               /* debug flags */
        unsigned int errs;              /* error count */
-       unsigned int enable;            /* enable status */
-       unsigned int poll_delay_ms;     /* requested sampling delay (ms) */
-       unsigned int queue_delay_ms;    /* workqueue delay time (ms) */
-       unsigned int scale_i;           /* index into HW IT settings table */
-       int scale_val;                  /* user scale val */
-       int scale_val2;                 /* user scale val2 */
-       int offset_val;                 /* user offset val */
-       int offset_val2;                /* user offset val2 */
-       int lux_uc_lo;                  /* interpolation x1 uncalibrated lo */
-       int lux_uc_hi;                  /* interpolation x3 uncalibrated hi */
-       int lux_c_lo;                   /* interpolation y1 calibrated lo */
-       int lux_c_hi;                   /* interpolation y3 calibrated hi */
-       unsigned int report;            /* used to report first valid sample */
-       unsigned int report_n;          /* this many on-change data reports */
-       unsigned int lux_thr_lo;        /* report when new lux below this */
-       unsigned int lux_thr_hi;        /* report when new lux above this */
-       unsigned int it_i_lo;           /* integration time index low limit */
-       unsigned int it_i_hi;           /* integration time index high limit */
-       unsigned long mult;             /* used to calc lux from HW values */
+       unsigned int enabled;           /* enable status */
        bool iio_ts_en;                 /* use IIO timestamps */
-       bool shutdown;                  /* shutdown active flag */
-       bool suspend;                   /* suspend active flag */
        bool hw_change;                 /* HW changed so drop first sample */
-       bool hw_sync;                   /* queue time match HW sample time */
        u8 cmd1;                        /* store for register dump */
        u8 cmd2;                        /* store for register dump */
-       u32 light;                      /* sample data */
-       s64 ts;                         /* sample data timestamp */
 };
 
 
@@ -306,141 +159,29 @@ static int cm_i2c_wr(struct cm_state *st, u8 cmd1, u8 cmd2)
        return 0;
 }
 
-static int cm_vreg_dis(struct cm_state *st, unsigned int i)
-{
-       int ret = 0;
-
-       if (st->vreg[i].ret && (st->vreg[i].consumer != NULL)) {
-               ret = regulator_disable(st->vreg[i].consumer);
-               if (ret)
-                       dev_err(&st->i2c->dev, "%s %s ERR\n",
-                               __func__, st->vreg[i].supply);
-               else
-                       st->vreg[i].ret = 0;
-                       dev_dbg(&st->i2c->dev, "%s %s\n",
-                               __func__, st->vreg[i].supply);
-       }
-       return ret;
-}
-
-static int cm_vreg_dis_all(struct cm_state *st)
-{
-       unsigned int i;
-       int ret = 0;
-
-       for (i = ARRAY_SIZE(cm_vregs); i > 0; i--)
-               ret |= cm_vreg_dis(st, (i - 1));
-       return ret;
-}
-
-static int cm_vreg_en(struct cm_state *st, unsigned int i)
-{
-       int ret = 0;
-
-       if ((!st->vreg[i].ret) && (st->vreg[i].consumer != NULL)) {
-               ret = regulator_enable(st->vreg[i].consumer);
-               if (ret) {
-                       dev_err(&st->i2c->dev, "%s %s ERR\n",
-                               __func__, st->vreg[i].supply);
-               } else {
-                       st->vreg[i].ret = 1;
-                       dev_dbg(&st->i2c->dev, "%s %s\n",
-                               __func__, st->vreg[i].supply);
-                       ret = 1; /* flag regulator state change */
-               }
-       }
-       return ret;
-}
-
-static int cm_vreg_en_all(struct cm_state *st)
-{
-       unsigned int i;
-       int ret = 0;
-
-       for (i = 0; i < ARRAY_SIZE(cm_vregs); i++)
-               ret |= cm_vreg_en(st, i);
-       return ret;
-}
-
-static void cm_vreg_exit(struct cm_state *st)
-{
-       unsigned int i;
-
-       for (i = 0; i < ARRAY_SIZE(cm_vregs); i++) {
-               if (st->vreg[i].consumer != NULL) {
-                       devm_regulator_put(st->vreg[i].consumer);
-                       st->vreg[i].consumer = NULL;
-                       dev_dbg(&st->i2c->dev, "%s %s\n",
-                               __func__, st->vreg[i].supply);
-               }
-       }
-}
-
-static int cm_vreg_init(struct cm_state *st)
-{
-       unsigned int i;
-       int ret = 0;
-
-       for (i = 0; i < ARRAY_SIZE(cm_vregs); i++) {
-               st->vreg[i].supply = cm_vregs[i];
-               st->vreg[i].ret = 0;
-               st->vreg[i].consumer = devm_regulator_get(&st->i2c->dev,
-                                                         st->vreg[i].supply);
-               if (IS_ERR(st->vreg[i].consumer)) {
-                       ret |= PTR_ERR(st->vreg[i].consumer);
-                       dev_err(&st->i2c->dev, "%s ret %d for %s\n",
-                               __func__, ret, st->vreg[i].supply);
-                       st->vreg[i].consumer = NULL;
-               } else {
-                       dev_dbg(&st->i2c->dev, "%s %s\n",
-                               __func__, st->vreg[i].supply);
-               }
-       }
-       return ret;
-}
-
-static int cm_vreg_sts(struct cm_state *st)
-{
-       unsigned int i;
-       int ret = 0;
-
-       for (i = 0; i < ARRAY_SIZE(cm_vregs); i++) {
-               if (st->vreg[i].consumer != NULL)
-                       break;
-       }
-       if (i < ARRAY_SIZE(cm_vregs)) {
-               /* ret == number of regulators on */
-               for (i = 0; i < ARRAY_SIZE(cm_vregs); i++) {
-                       if (st->vreg[i].ret)
-                               ret++;
-               }
-       } else {
-               /* no regulator support (can assume always on) */
-               ret = -EINVAL;
-       }
-       return ret;
-}
-
 static int cm_pm(struct cm_state *st, bool enable)
 {
-       int ret = 0;
+       int ret;
 
        if (enable) {
-               ret = cm_vreg_en_all(st);
+               ret = nvs_vregs_enable(&st->i2c->dev, st->vreg,
+                                      ARRAY_SIZE(cm_vregs));
                if (ret)
                        mdelay(CM_HW_DELAY_MS);
        } else {
-               ret = cm_vreg_sts(st);
+               ret = nvs_vregs_sts(st->vreg, ARRAY_SIZE(cm_vregs));
                if ((ret < 0) || (ret == ARRAY_SIZE(cm_vregs))) {
                        ret = cm_i2c_wr(st, (CM_HW_CMD1_DFLT |
                                             CM_HW_CMD1_BIT_SD), 0);
                } else if (ret > 0) {
-                       cm_vreg_en_all(st);
+                       nvs_vregs_enable(&st->i2c->dev, st->vreg,
+                                        ARRAY_SIZE(cm_vregs));
                        mdelay(CM_HW_DELAY_MS);
                        ret = cm_i2c_wr(st, (CM_HW_CMD1_DFLT |
                                             CM_HW_CMD1_BIT_SD), 0);
                }
-               ret |= cm_vreg_dis_all(st);
+               ret |= nvs_vregs_disable(&st->i2c->dev, st->vreg,
+                                        ARRAY_SIZE(cm_vregs));
        }
        if (ret > 0)
                ret = 0;
@@ -448,7 +189,7 @@ static int cm_pm(struct cm_state *st, bool enable)
                dev_err(&st->i2c->dev, "%s pwr=%x ERR=%d\n",
                        __func__, enable, ret);
        } else {
-               if (st->dbg & CM_DBG_SPEW_MSG)
+               if (st->sts & NVS_STS_SPEW_MSG)
                        dev_info(&st->i2c->dev, "%s pwr=%x\n",
                                 __func__, enable);
        }
@@ -458,154 +199,44 @@ static int cm_pm(struct cm_state *st, bool enable)
 static void cm_pm_exit(struct cm_state *st)
 {
        cm_pm(st, false);
-       cm_vreg_exit(st);
+       nvs_vregs_exit(&st->i2c->dev, st->vreg, ARRAY_SIZE(cm_vregs));
 }
 
 static int cm_pm_init(struct cm_state *st)
 {
        int ret;
 
-       st->enable = 0;
-       st->poll_delay_ms = CM_POLL_DELAY_MS_DFLT;
-       cm_vreg_init(st);
+       st->enabled = 0;
+       nvs_vregs_init(&st->i2c->dev,
+                      st->vreg, ARRAY_SIZE(cm_vregs), cm_vregs);
        /* on off for low power mode if regulator still on */
        ret = cm_pm(st, true);
        ret |= cm_pm(st, false);
        return ret;
 }
 
-static unsigned int cm_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 void cm_buf_push(struct iio_dev *indio_dev, struct cm_state *st)
-{
-       unsigned char buf[16];
-       unsigned int n;
-       unsigned int i;
-       unsigned int bytes = 0;
-
-       if (!iio_buffer_enabled(indio_dev))
-               return;
-
-       if (iio_scan_mask_query(indio_dev, indio_dev->buffer, CM_SCAN_LIGHT)) {
-               n = sizeof(st->light);
-               i = cm_buf_index(n, &bytes);
-               memcpy(&buf[i], &st->light, n);
-               if (st->dbg & CM_DBG_SPEW_LIGHT)
-                       dev_info(&st->i2c->dev, "light %u %lld\n",
-                                st->light, st->ts);
-       }
-       if (indio_dev->buffer->scan_timestamp) {
-               n = sizeof(st->ts);
-               i = cm_buf_index(n, &bytes);
-               memcpy(&buf[i], &st->ts, n);
-       }
-       iio_push_to_buffers(indio_dev, buf);
-}
-
-static int cm_cmd_wr(struct cm_state *st, u8 it_t, u8 fd_it)
+static int cm_cmd_wr(struct cm_state *st)
 {
        u8 cmd1;
        u8 cmd2;
        int ret;
 
        cmd1 = CM_HW_CMD1_DFLT;
-       cmd1 |= (it_t << CM_HW_CMD1_BIT_IT_T);
-       cmd2 = fd_it << CM_HW_CMD2_BIT_FD_IT;
+       cmd1 |= cm_nld_tbl[st->light.nld_i].driver_data & CM_HW_CMD1_IT_T_MASK;
+       cmd2 = cm_nld_tbl[st->light.nld_i].driver_data & CM_HW_CMD2_FD_IT_MASK;
        ret = cm_i2c_wr(st, cmd1, cmd2);
-       if (st->dbg & CM_DBG_SPEW_MSG)
+       if (!ret)
+               st->hw_change = true;
+       if (st->sts & NVS_STS_SPEW_MSG)
                dev_info(&st->i2c->dev, "%s cmd1=%hhx cmd2=%hhx err=%d\n",
                         __func__, cmd1, cmd2, ret);
        return ret;
 }
 
-static void cm_delay(struct cm_state *st, bool hw_sync)
+static int cm_rd(struct cm_state *st)
 {
-       unsigned int ms;
-
-       if (hw_sync)
-               st->hw_sync = true;
-       ms = cm_it_tbl[st->scale_i].ms;
-       ms += CM_HW_DELAY_MS;
-       if ((ms < st->poll_delay_ms) && !hw_sync)
-               st->queue_delay_ms = st->poll_delay_ms;
-       else
-               /* we're either outside the HW integration time (IT) window and
-                * want to get within the window as fast as HW allows us
-                * (hw_sync = true)
-                * OR
-                * HW IT is not as fast as requested polling time
-                */
-               st->queue_delay_ms = ms;
-       if (st->dbg & CM_DBG_SPEW_MSG)
-               dev_info(&st->i2c->dev, "%s queue_delay_ms=%u\n",
-                        __func__, st->queue_delay_ms);
-}
-
-static int cm_it_wr(struct cm_state *st, unsigned int ms)
-{
-       unsigned int i;
-       int ret;
-
-       /* get the HW settings for integration time (IT) ms */
-       for (i = st->it_i_lo; i < st->it_i_hi; i++) {
-               if (ms >= cm_it_tbl[i].ms)
-                       break;
-       }
-       if (i >= st->it_i_hi)
-               i = (st->it_i_hi - 1);
-       ret = cm_cmd_wr(st, cm_it_tbl[i].it_t, cm_it_tbl[i].fd_it);
-       if (!ret) {
-               st->hw_change = true;
-               st->scale_i = i;
-               ms = cm_it_tbl[i].ms;
-               st->mult = CM_RESOLUTION_STEP / (long)ms;
-       }
-       if (st->dbg & CM_DBG_SPEW_MSG)
-               dev_info(&st->i2c->dev, "%s IT=%u err=%d\n",
-                        __func__, cm_it_tbl[i].ms, ret);
-       return ret;
-}
-
-static int cm_interpolate(int x1, int x2, int x3, int y1, int *y2, int y3)
-{
-       int dividend;
-       int divisor;
-
-       /* y2 = ((x2 - x1)(y3 - y1)/(x3 - x1)) + y1 */
-       divisor = (x3 - x1);
-       if (!divisor)
-               return -EINVAL;
-
-       dividend = (x2 - x1) * (y3 - y1);
-       *y2 = (dividend / divisor) + y1;
-       return 0;
-}
-
-static void cm_report_init(struct cm_state *st)
-{
-       st->ts = 0;
-       st->report = st->report_n;
-}
-
-static int cm_rd(struct iio_dev *indio_dev)
-{
-       struct cm_state *st = iio_priv(indio_dev);
-       u16 step;
-       u32 lux;
+       u16 hw;
        s64 ts;
-       s64 ts_elapsed;
-       bool t_min = false;
-       unsigned long calc;
        int ret;
 
        if (st->hw_change) {
@@ -614,552 +245,123 @@ static int cm_rd(struct iio_dev *indio_dev)
                return 0;
        }
 
-       ret = cm_i2c_rd(st, &step);
+       ret = cm_i2c_rd(st, &hw);
        if (ret)
                return ret;
 
-       calc = step;
-       calc *= st->mult;
-       calc /= CM_RESOLUTION_DIVISOR;
-       lux = calc;
-       cm_interpolate(st->lux_uc_lo, lux, st->lux_uc_hi,
-                      st->lux_c_lo, &lux, st->lux_c_hi);
-       if ((step == 0xFFFF) && (st->scale_i < (st->it_i_hi - 1))) {
-               /* too many photons - need to decrease resolution */
-               ret = cm_it_wr(st, cm_it_tbl[st->scale_i + 1].ms);
-               if (!ret)
-                       cm_delay(st, true);
-       } else if ((lux <= CM_LIGHT_DIVISOR) && (st->scale_i >
-                                                (st->it_i_lo + 1))) {
-               /* not enough photons - need to increase resolution */
-               ret = cm_it_wr(st, cm_it_tbl[st->scale_i - 1].ms);
-               if (!ret)
-                       cm_delay(st, true);
-       } else if (st->hw_sync) {
-               /* adjust queue time to max(polling delay, HW IT) */
-               st->hw_sync = false;
-               cm_delay(st, false);
-       }
-       if (lux > (st->light + st->lux_thr_hi)) {
-               st->report = st->report_n;
-       } else if (st->light > st->lux_thr_lo) {
-               if (lux < (st->light - st->lux_thr_lo))
-                       st->report = st->report_n;
-       }
        ts = cm_get_time_ns(st);
-       ts_elapsed = ts - st->ts;
-       if (ts_elapsed >= st->poll_delay_ms * 1000000)
-               t_min = true;
-       if (st->dbg & CM_DBG_SPEW_LIGHT_POLL)
+       if (st->sts & NVS_STS_SPEW_DATA)
                dev_info(&st->i2c->dev,
-                        "poll light %d %lld  diff: %d %lldns  hw=%hu\n",
-                        lux, ts, lux - st->light, ts_elapsed, step);
-       if ((st->report && t_min) || ((st->scale_val == 1) &&
-                                     !st->scale_val2)) {
-               /* report if:
-                * - st->report && time since last report >= polling delay
-                * - in calibration mode (scale == 1)
-                */
-               if (st->report)
-                       st->report--;
-               if (!(st->dbg & CM_DBG_VAL_LIGHT))
-                       st->light = lux;
-               st->ts = ts;
-               cm_buf_push(indio_dev, st);
-       }
-       return ret;
+                        "poll light hw %hu %lld  diff=%d %lldns  index=%u\n",
+                        hw, ts, hw - st->light.hw, ts - st->light.timestamp,
+                        st->light.nld_i);
+       st->light.hw = hw;
+       st->light.timestamp = ts;
+       nvs_light_read(&st->light);
+       if (st->light.nld_i_change)
+               cm_cmd_wr(st);
+       return 0;
 }
 
-static void cm_read(struct iio_dev *indio_dev)
+static void cm_read(struct cm_state *st)
 {
-       struct cm_state *st = iio_priv(indio_dev);
-
-       mutex_lock(&indio_dev->mlock);
-       if (st->enable) {
-               cm_rd(indio_dev);
+       st->nvs->mutex_lock(st->nvs_data);
+       if (st->enabled) {
+               cm_rd(st);
                schedule_delayed_work(&st->dw,
-                                     msecs_to_jiffies(st->queue_delay_ms));
+                                   msecs_to_jiffies(st->light.poll_delay_ms));
        }
-       mutex_unlock(&indio_dev->mlock);
+       st->nvs->mutex_unlock(st->nvs_data);
 }
 
 static void cm_work(struct work_struct *ws)
 {
        struct cm_state *st = container_of((struct delayed_work *)ws,
                                           struct cm_state, dw);
-       struct iio_dev *indio_dev = iio_priv_to_dev(st);
 
-       cm_read(indio_dev);
+       cm_read(st);
 }
 
-static int cm_disable(struct iio_dev *indio_dev)
+static int cm_disable(struct cm_state *st)
 {
-       struct cm_state *st = iio_priv(indio_dev);
        int ret;
 
-       if (!(iio_scan_mask_query(indio_dev, indio_dev->buffer,
-                                 CM_SCAN_LIGHT)))
-               st->dbg &= ~CM_DBG_VAL_LIGHT;
        cancel_delayed_work(&st->dw);
        ret = cm_pm(st, false);
        if (!ret)
-               st->enable = 0;
+               st->enabled = 0;
        return ret;
 }
 
-static int cm_enable(struct iio_dev *indio_dev)
+static int cm_enable(void *client, int snsr_id, int enable)
 {
-       struct cm_state *st = iio_priv(indio_dev);
-       int ret = -EINVAL;
+       struct cm_state *st = (struct cm_state *)client;
+       unsigned int ms;
+       int ret;
+
+       if (enable < 0)
+               return st->enabled;
 
-       if (iio_scan_mask_query(indio_dev, indio_dev->buffer, CM_SCAN_LIGHT)) {
+       if (enable) {
                ret = cm_pm(st, true);
                if (!ret) {
-                       ret = cm_it_wr(st, 0);
+                       nvs_light_enable(&st->light);
+                       ret = cm_cmd_wr(st);
                        if (ret) {
-                               cm_disable(indio_dev);
+                               cm_disable(st);
                        } else {
-                               cm_delay(st, true);
-                               cm_report_init(st);
-                               st->enable = 1;
+                               st->enabled = 1;
+                               ms = st->light.poll_delay_ms;
                                schedule_delayed_work(&st->dw,
-                                        msecs_to_jiffies(st->queue_delay_ms));
+                                                     msecs_to_jiffies(ms));
                        }
                }
+       } else {
+               ret = cm_disable(st);
        }
        return ret;
 }
 
-static ssize_t cm_attr_store(struct device *dev,
-                            struct device_attribute *attr,
-                            const char *buf, size_t count)
+static int cm_batch(void *client, int snsr_id, int flags,
+                   unsigned int period, unsigned int timeout)
 {
-       struct iio_dev *indio_dev = dev_get_drvdata(dev);
-       struct cm_state *st = iio_priv(indio_dev);
-       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
-       const char *msg;
-       unsigned int new;
-       unsigned int old = 0;
-       int ret;
+       struct cm_state *st = (struct cm_state *)client;
 
-       ret = kstrtouint(buf, 10, &new);
-       if (ret)
+       if (timeout)
+               /* timeout not supported (no HW FIFO) */
                return -EINVAL;
 
-       mutex_lock(&indio_dev->mlock);
-       if (st->shutdown || st->suspend) {
-               mutex_unlock(&indio_dev->mlock);
-               return -EPERM;
-       }
-
-       switch (this_attr->address) {
-       case CM_ATTR_ENABLE:
-               msg = "ATTR_ENABLE";
-               if (st->enable)
-                       old = 1;
-               if (new)
-                       ret = cm_enable(indio_dev);
-               else
-                       ret = cm_disable(indio_dev);
-               break;
-
-       case CM_ATTR_THRESH_LO:
-               msg = "ATTR_THRESH_LO";
-               old = st->lux_thr_lo;
-               st->lux_thr_lo = new;
-               break;
-
-       case CM_ATTR_THRESH_HI:
-               msg = "ATTR_THRESH_HI";
-               old = st->lux_thr_hi;
-               st->lux_thr_hi = new;
-               break;
-
-       default:
-               msg = "ATTR_UNKNOWN";
-               ret = -EINVAL;
-       }
-
-       cm_report_init(st);
-       mutex_unlock(&indio_dev->mlock);
-       cm_read(indio_dev);
-       if (st->dbg & CM_DBG_SPEW_MSG) {
-               if (ret)
-                       dev_err(&st->i2c->dev, "%s %s %d->%d ERR=%d\n",
-                               __func__, msg, old, new, ret);
-               else
-                       dev_info(&st->i2c->dev, "%s %s %d->%d\n",
-                                __func__, msg, old, new);
-       }
-       if (ret)
-               return ret;
-
-       return count;
-}
-
-static ssize_t cm_attr_show(struct device *dev, struct device_attribute *attr,
-                           char *buf)
-{
-       struct iio_dev *indio_dev = dev_get_drvdata(dev);
-       struct cm_state *st = iio_priv(indio_dev);
-       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
-
-       switch (this_attr->address) {
-       case CM_ATTR_ENABLE:
-               return sprintf(buf, "%x\n", st->enable);
-
-       case CM_ATTR_PART:
-               return sprintf(buf, "%s light\n", CM_NAME);
-
-       case CM_ATTR_VENDOR:
-               return sprintf(buf, "%s\n", CM_VENDOR);
-
-       case CM_ATTR_VERSION:
-               return sprintf(buf, "%u\n", CM_LIGHT_VERSION);
-
-       case CM_ATTR_MILLIAMP:
-               return sprintf(buf, "%s\n", CM_LIGHT_MILLIAMP);
-
-       case CM_ATTR_THRESH_LO:
-               return sprintf(buf, "%u\n", st->lux_thr_lo);
-
-       case CM_ATTR_THRESH_HI:
-               return sprintf(buf, "%u\n", st->lux_thr_hi);
-
-       default:
-               return -EINVAL;
-       }
-
-       return -EINVAL;
-}
-
-static ssize_t cm_data_store(struct device *dev,
-                            struct device_attribute *attr,
-                            const char *buf, size_t count)
-{
-       struct cm_state *st = iio_priv(dev_get_drvdata(dev));
-       unsigned int info;
-       int ret;
-
-       ret = kstrtouint(buf, 10, &info);
-       if (ret)
-               return -EINVAL;
-
-       if (info >= CM_INFO_LIMIT_MAX)
-               return -EINVAL;
-
-       st->info = info;
-       switch (info) {
-       case CM_INFO_DATA:
-               st->dbg = 0;
-               break;
-
-       case CM_INFO_DBG:
-               st->dbg ^= CM_DBG_SPEW_MSG;
-               break;
-
-       case CM_INFO_LIGHT_SPEW:
-               st->dbg ^= CM_DBG_SPEW_LIGHT;
-               break;
-
-       case CM_INFO_LIGHT_POLL_SPEW:
-               st->dbg ^= CM_DBG_SPEW_LIGHT_POLL;
-               break;
-
-       default:
-               break;
-       }
-
-       return count;
+       st->light.delay_us = period;
+       return 0;
 }
 
-static ssize_t cm_data_show(struct device *dev,
-                           struct device_attribute *attr, char *buf)
+static int cm_regs(void *client, int snsr_id, char *buf)
 {
-       struct cm_state *st = iio_priv(dev_get_drvdata(dev));
-       enum CM_INFO info;
+       struct cm_state *st = (struct cm_state *)client;
        ssize_t t;
 
-       info = st->info;
-       st->info = CM_INFO_DATA;
-       switch (info) {
-       case CM_INFO_DATA:
-               t = sprintf(buf, "LIGHT: %d  ts: %lld\n",
-                           st->light, st->ts);
-               return t;
-
-       case CM_INFO_VER:
-               return sprintf(buf, "version=%u\n", CM_VERSION_DRIVER);
-
-       case CM_INFO_ERRS:
-               t = sprintf(buf, "error count=%u\n", st->errs);
-               st->errs = 0;
-               return t;
-
-       case CM_INFO_REGS:
-               t = sprintf(buf, "registers:\n");
-               t += sprintf(buf + t, "0x20=%#2x\n", st->cmd1);
-               t += sprintf(buf + t, "0x22=%#2x\n", st->cmd2);
-               return t;
-
-       case CM_INFO_DBG:
-               return sprintf(buf, "debug spew=%x\n",
-                              !!(st->dbg & CM_DBG_SPEW_MSG));
-
-       case CM_INFO_LIGHT_SPEW:
-               return sprintf(buf, "lux_ts spew=%x\n",
-                              !!(st->dbg & CM_DBG_SPEW_LIGHT));
-
-       case CM_INFO_LIGHT_POLL_SPEW:
-               return sprintf(buf, "lux_poll_ts spew=%x\n",
-                              !!(st->dbg & CM_DBG_SPEW_LIGHT_POLL));
-
-       default:
-               break;
-       }
-
-       return -EINVAL;
-}
-
-static IIO_DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
-                      cm_attr_show, cm_attr_store, CM_ATTR_ENABLE);
-static IIO_DEVICE_ATTR(illuminance_part, S_IRUGO,
-                      cm_attr_show, NULL, CM_ATTR_PART);
-static IIO_DEVICE_ATTR(illuminance_vendor, S_IRUGO,
-                      cm_attr_show, NULL, CM_ATTR_VENDOR);
-static IIO_DEVICE_ATTR(illuminance_version, S_IRUGO,
-                      cm_attr_show, NULL, CM_ATTR_VERSION);
-static IIO_DEVICE_ATTR(illuminance_milliamp, S_IRUGO,
-                      cm_attr_show, NULL, CM_ATTR_MILLIAMP);
-static IIO_DEVICE_ATTR(illuminance_thresh_rising_value,
-                      S_IRUGO | S_IWUSR | S_IWGRP,
-                      cm_attr_show, cm_attr_store, CM_ATTR_THRESH_HI);
-static IIO_DEVICE_ATTR(illuminance_thresh_falling_value,
-                      S_IRUGO | S_IWUSR | S_IWGRP,
-                      cm_attr_show, cm_attr_store, CM_ATTR_THRESH_LO);
-static DEVICE_ATTR(data, S_IRUGO | S_IWUSR | S_IWGRP,
-                  cm_data_show, cm_data_store);
-
-static struct attribute *cm_attrs[] = {
-       &dev_attr_data.attr,
-       &iio_dev_attr_enable.dev_attr.attr,
-       &iio_dev_attr_illuminance_part.dev_attr.attr,
-       &iio_dev_attr_illuminance_vendor.dev_attr.attr,
-       &iio_dev_attr_illuminance_version.dev_attr.attr,
-       &iio_dev_attr_illuminance_milliamp.dev_attr.attr,
-       &iio_dev_attr_illuminance_thresh_rising_value.dev_attr.attr,
-       &iio_dev_attr_illuminance_thresh_falling_value.dev_attr.attr,
-       NULL
-};
-
-static struct attribute_group cm_attr_group = {
-       .name = CM_NAME,
-       .attrs = cm_attrs
-};
-
-static int cm_read_raw(struct iio_dev *indio_dev,
-                      struct iio_chan_spec const *chan,
-                      int *val, int *val2, long mask)
-{
-       struct cm_state *st = iio_priv(indio_dev);
-       int ret = 0;
-
-       switch (mask) {
-       case IIO_CHAN_INFO_RAW:
-               *val = st->light;
-               return IIO_VAL_INT;
-
-       case IIO_CHAN_INFO_SAMP_FREQ:
-               if (st->enable)
-                       *val = st->poll_delay_ms * 1000; /* ms => us */
-               else
-                       *val = cm_it_tbl[st->it_i_hi - 1].ms * 1000;
-               return IIO_VAL_INT;
-
-       case IIO_CHAN_INFO_SCALE:
-               *val = st->scale_val;
-               *val2 = st->scale_val2;
-               return IIO_VAL_INT_PLUS_MICRO;
-
-       case IIO_CHAN_INFO_OFFSET:
-               *val = st->offset_val;
-               *val2 = st->offset_val2;
-               return IIO_VAL_INT_PLUS_MICRO;
-
-       case IIO_CHAN_INFO_PEAK:
-               *val = CM_LIGHT_MAX_RANGE_IVAL;
-               *val2 = CM_LIGHT_MAX_RANGE_MICRO;
-               return IIO_VAL_INT_PLUS_MICRO;
-
-       case IIO_CHAN_INFO_PEAK_SCALE:
-               *val = CM_LIGHT_RESOLUTION_IVAL;
-               *val2 = CM_LIGHT_RESOLUTION_MICRO;
-               return IIO_VAL_INT_PLUS_MICRO;
-
-       default:
-               return -EINVAL;
-       }
-
-       return ret;
-}
-
-static int cm_write_raw(struct iio_dev *indio_dev,
-                       struct iio_chan_spec const *chan,
-                       int val, int val2, long mask)
-{
-       struct cm_state *st = iio_priv(indio_dev);
-       char *msg;
-       int old = 0;
-       int old2 = 0;
-       int ret = 0;
-
-       mutex_lock(&indio_dev->mlock);
-       if (st->shutdown || st->suspend) {
-               mutex_unlock(&indio_dev->mlock);
-               return -EPERM;
-       }
-
-       switch (mask) {
-       case IIO_CHAN_INFO_SAMP_FREQ:
-               msg = "IIO_CHAN_INFO_SAMP_FREQ";
-               old = st->poll_delay_ms * 1000; /* ms => us*/
-               st->poll_delay_ms = (unsigned int)(val / 1000); /* us => ms */
-               cm_delay(st, false);
-               break;
-
-       case IIO_CHAN_INFO_SCALE:
-               msg = "IIO_CHAN_INFO_SCALE";
-               old = st->scale_val;
-               old2 = st->scale_val2;
-               st->scale_val = val;
-               st->scale_val2 = val2;
-               break;
-
-       case IIO_CHAN_INFO_OFFSET:
-               msg = "IIO_CHAN_INFO_SCALE";
-               old = st->offset_val;
-               old2 = st->offset_val2;
-               st->offset_val = val;
-               st->offset_val2 = val2;
-               break;
-
-       case IIO_CHAN_INFO_RAW:
-               msg = "IIO_CHAN_INFO_RAW";
-               old = st->light;
-               st->light = val;
-               st->ts = cm_get_time_ns(st);
-               st->dbg |= CM_DBG_VAL_LIGHT;
-               cm_buf_push(indio_dev, st);
-               break;
-
-       default:
-               msg = "IIO_CHAN_INFO_UNKNOWN";
-               ret = -EINVAL;
-       }
-
-       cm_report_init(st);
-       mutex_unlock(&indio_dev->mlock);
-       cm_read(indio_dev);
-       if (st->dbg & CM_DBG_SPEW_MSG) {
-               if (ret)
-                       dev_err(&st->i2c->dev, "%s c=%d %d:%d->%d:%d ERR=%d\n",
-                               msg, chan->scan_index,
-                               old, old2, val, val2, ret);
-               else
-                       dev_info(&st->i2c->dev, "%s %s chan=%d %d:%d->%d:%d\n",
-                                __func__, msg, chan->scan_index,
-                                old, old2, val, val2);
-       }
-       return ret;
-}
-
-static const struct iio_info cm_iio_info = {
-       .driver_module = THIS_MODULE,
-       .attrs = &cm_attr_group,
-       .read_raw = &cm_read_raw,
-       .write_raw = &cm_write_raw,
-};
-
-static const struct iio_chan_spec cm_channels[] = {
-       {
-               .type                   = IIO_LIGHT,
-               .scan_index             = CM_SCAN_LIGHT,
-               .scan_type              = IIO_ST('u', 32, 32, 0),
-               .info_mask              = BIT(IIO_CHAN_INFO_RAW) |
-                                         BIT(IIO_CHAN_INFO_SAMP_FREQ) |
-                                         BIT(IIO_CHAN_INFO_PEAK) |
-                                         BIT(IIO_CHAN_INFO_PEAK_SCALE) |
-                                         BIT(IIO_CHAN_INFO_SCALE) |
-                                         BIT(IIO_CHAN_INFO_OFFSET),
-               .info_mask_separate     = BIT(IIO_CHAN_INFO_RAW) |
-                                         BIT(IIO_CHAN_INFO_SAMP_FREQ) |
-                                         BIT(IIO_CHAN_INFO_PEAK) |
-                                         BIT(IIO_CHAN_INFO_PEAK_SCALE) |
-                                         BIT(IIO_CHAN_INFO_SCALE) |
-                                         BIT(IIO_CHAN_INFO_OFFSET),
-       },
-       IIO_CHAN_SOFT_TIMESTAMP(CM_SCAN_TIMESTAMP)
-};
-
-static int cm_buffer_preenable(struct iio_dev *indio_dev)
-{
-       struct cm_state *st = iio_priv(indio_dev);
-
-       if (st->shutdown || st->suspend)
-               return -EINVAL;
-
-       return iio_sw_buffer_preenable(indio_dev);
+       t = sprintf(buf, "registers:\n");
+       t += sprintf(buf + t, "0x20=%#2x\n", st->cmd1);
+       t += sprintf(buf + t, "0x22=%#2x\n", st->cmd2);
+       return t;
 }
 
-static int cm_buffer_postenable(struct iio_dev *indio_dev)
-{
-       int ret;
-
-       ret = cm_enable(indio_dev);
-       /* never return > 0 to IIO buffer engine */
-       if (ret > 0)
-               ret = 0;
-       return ret;
-}
-
-static const struct iio_buffer_setup_ops cm_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 = &cm_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 = &cm_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 struct iio_trigger_ops cm_trigger_ops = {
-       .owner = THIS_MODULE,
+static struct nvs_fn_dev cm_fn_dev = {
+       .enable                         = cm_enable,
+       .batch                          = cm_batch,
+       .regs                           = cm_regs,
 };
 
 static int cm_suspend(struct device *dev)
 {
        struct i2c_client *client = to_i2c_client(dev);
-       struct iio_dev *indio_dev = i2c_get_clientdata(client);
-       struct cm_state *st = iio_priv(indio_dev);
+       struct cm_state *st = i2c_get_clientdata(client);
        int ret = 0;
 
-       mutex_lock(&indio_dev->mlock);
-       if (st->enable)
-               ret = cm_disable(indio_dev);
-       st->suspend = true;
-       mutex_unlock(&indio_dev->mlock);
-       if (st->dbg & CM_DBG_SPEW_MSG)
+       st->sts |= NVS_STS_SUSPEND;
+       if (st->nvs && st->nvs_data)
+               ret = st->nvs->suspend(st->nvs_data);
+       if (st->sts & NVS_STS_SPEW_MSG)
                dev_info(&client->dev, "%s\n", __func__);
        return ret;
 }
@@ -1167,150 +369,125 @@ static int cm_suspend(struct device *dev)
 static int cm_resume(struct device *dev)
 {
        struct i2c_client *client = to_i2c_client(dev);
-       struct iio_dev *indio_dev = i2c_get_clientdata(client);
-       struct cm_state *st = iio_priv(indio_dev);
+       struct cm_state *st = i2c_get_clientdata(client);
+       int ret = 0;
 
-       st->suspend = false;
-       if (st->dbg & CM_DBG_SPEW_MSG)
+       if (st->nvs && st->nvs_data)
+               ret = st->nvs->resume(st->nvs_data);
+       st->sts &= ~NVS_STS_SUSPEND;
+       if (st->sts & NVS_STS_SPEW_MSG)
                dev_info(&client->dev, "%s\n", __func__);
-       return 0;
+       return ret;
 }
 
 static SIMPLE_DEV_PM_OPS(cm_pm_ops, cm_suspend, cm_resume);
 
 static void cm_shutdown(struct i2c_client *client)
 {
-       struct iio_dev *indio_dev = i2c_get_clientdata(client);
-       struct cm_state *st = iio_priv(indio_dev);
-
-       mutex_lock(&indio_dev->mlock);
-       if (st->enable)
-               cm_disable(indio_dev);
-       st->shutdown = true;
-       mutex_unlock(&indio_dev->mlock);
-       if (st->dbg & CM_DBG_SPEW_MSG)
+       struct cm_state *st = i2c_get_clientdata(client);
+
+       st->sts |= NVS_STS_SHUTDOWN;
+       if (st->nvs && st->nvs_data)
+               st->nvs->shutdown(st->nvs_data);
+       if (st->sts & NVS_STS_SPEW_MSG)
                dev_info(&client->dev, "%s\n", __func__);
 }
 
 static int cm_remove(struct i2c_client *client)
 {
-       struct iio_dev *indio_dev = i2c_get_clientdata(client);
-       struct cm_state *st = iio_priv(indio_dev);
+       struct cm_state *st = i2c_get_clientdata(client);
 
        if (st != NULL) {
                cm_shutdown(client);
-               if (indio_dev->dev.devt)
-                       iio_device_unregister(indio_dev);
-               if (st->trig != NULL) {
-                       iio_trigger_unregister(st->trig);
-                       iio_trigger_free(st->trig);
-               }
-               if (indio_dev->buffer != NULL) {
-                       iio_buffer_unregister(indio_dev);
-                       iio_kfifo_free(indio_dev->buffer);
-               }
+               if (st->nvs && st->nvs_data)
+                       st->nvs->remove(st->nvs_data);
                if (st->dw.wq)
                        destroy_workqueue(st->dw.wq);
                cm_pm_exit(st);
-               iio_device_free(indio_dev);
        }
        dev_info(&client->dev, "%s\n", __func__);
        return 0;
 }
 
-static int cm_of_dt(struct i2c_client *client, struct cm_state *st)
+static struct sensor_cfg cm_cfg_dflt = {
+       .name                   = NVS_LIGHT_STRING,
+       .ch_n                   = ARRAY_SIZE(iio_chan_spec_nvs_light),
+       .ch_inf                 = &iio_chan_spec_nvs_light,
+       .part                   = CM_NAME,
+       .vendor                 = CM_VENDOR,
+       .version                = CM_LIGHT_VERSION,
+       .max_range              = {
+               .ival           = CM_LIGHT_MAX_RANGE_IVAL,
+               .fval           = CM_LIGHT_MAX_RANGE_MICRO,
+       },
+       .resolution             = {
+               .ival           = CM_LIGHT_RESOLUTION_IVAL,
+               .fval           = CM_LIGHT_RESOLUTION_MICRO,
+       },
+       .milliamp               = {
+               .ival           = CM_LIGHT_MILLIAMP_IVAL,
+               .fval           = CM_LIGHT_MILLIAMP_MICRO,
+       },
+       .delay_us_min           = CM_POLL_DLY_MS_MIN * 1000,
+       .delay_us_max           = CM_POLL_DLY_MS_MAX * 1000,
+       .scale                  = {
+               .ival           = CM_LIGHT_SCALE_IVAL,
+               .fval           = CM_LIGHT_SCALE_MICRO,
+       },
+       .thresh_lo              = CM_LIGHT_THRESHOLD_LO,
+       .thresh_hi              = CM_LIGHT_THRESHOLD_HI,
+};
+
+static int cm_of_dt(struct cm_state *st, struct device_node *dn)
 {
-       struct device_node *dn = client->dev.of_node;
-       unsigned int val;
        unsigned int i;
 
-       /* default NVS ALS programmable parameters */
-       st->scale_val = CM_LIGHT_SCALE_IVAL;
-       st->scale_val2 = CM_LIGHT_SCALE_MICRO;
-       st->offset_val = CM_LIGHT_OFFSET_IVAL;
-       st->offset_val2 = CM_LIGHT_OFFSET_MICRO;
-       st->lux_thr_lo = (1000000 / st->scale_val2) * CM_THRESHOLD_LUX_DFLT;
-       st->lux_thr_hi = (1000000 / st->scale_val2) * CM_THRESHOLD_LUX_DFLT;
-       st->it_i_lo = 0;
-       st->it_i_hi = ARRAY_SIZE(cm_it_tbl);
+       /* default NVS programmable parameters */
+       memcpy(&st->cfg, &cm_cfg_dflt, sizeof(st->cfg));
+       st->light.cfg = &st->cfg;
+       st->light.hw_mask = 0xFFFF;
+       st->light.nld_tbl = cm_nld_tbl;
        /* device tree parameters */
-       if (client->dev.of_node) {
-               /* common NVS programmable parameters */
+       if (dn)
+               /* common NVS IIO programmable parameters */
                st->iio_ts_en = of_property_read_bool(dn, "iio_timestamps");
-               of_property_read_u32(dn, "report_count", &st->report_n);
-               /* common NVS ALS programmable parameters */
-               of_property_read_s32(dn, "light_uncalibrated_lo",
-                                    &st->lux_uc_lo);
-               of_property_read_s32(dn, "light_uncalibrated_hi",
-                                    &st->lux_uc_hi);
-               of_property_read_s32(dn, "light_calibrated_lo",
-                                    &st->lux_c_lo);
-               of_property_read_s32(dn, "light_calibrated_hi",
-                                    &st->lux_c_hi);
-               of_property_read_s32(dn, "light_scale_val",
-                                    &st->scale_val);
-               of_property_read_s32(dn, "light_scale_val2",
-                                    &st->scale_val2);
-               of_property_read_s32(dn, "light_offset_val",
-                                    &st->offset_val);
-               of_property_read_s32(dn, "light_offset_val2",
-                                    &st->offset_val2);
-               of_property_read_u32(dn, "light_threshold_lo",
-                                    &st->lux_thr_lo);
-               of_property_read_u32(dn, "light_threshold_hi",
-                                    &st->lux_thr_hi);
-               /* this device supports these programmable parameters */
-               if (!of_property_read_u32(dn, "light_integration_time_ms_lo",
-                                        &val)) {
-                       for (i = ARRAY_SIZE(cm_it_tbl); i > 1; i--) {
-                               if (val <= cm_it_tbl[i - 1].ms)
-                                       break;
-                       }
-                       st->it_i_hi = i;
-               }
-               if (!of_property_read_u32(dn, "light_integration_time_ms_hi",
-                                        &val)) {
-                       for (i = 0; i < ARRAY_SIZE(cm_it_tbl) - 1; i++) {
-                               if (val >= cm_it_tbl[i].ms)
-                                       break;
-                       }
-                       st->it_i_lo = i;
-               }
-               if (st->it_i_hi < st->it_i_lo) {
-                       dev_err(&client->dev,
-                               "%s light_integration_time_ms_ ERR\n",
-                               __func__);
-                       st->it_i_lo = 0;
-                       st->it_i_hi = ARRAY_SIZE(cm_it_tbl);
-               }
-       }
-       if (!st->report_n)
-               st->report_n = CM_REPORT_N;
+       /* common NVS parameters */
+       nvs_of_dt(dn, &st->cfg, NULL);
+       /* this device supports these programmable parameters */
+       if (nvs_light_of_dt(&st->light, dn, NULL)) {
+               st->light.nld_i_lo = 0;
+               st->light.nld_i_hi = ARRAY_SIZE(cm_nld_tbl) - 1;
+       }
+       i = st->light.nld_i_lo;
+       st->cfg.resolution.ival = cm_nld_tbl[i].resolution.ival;
+       st->cfg.resolution.fval = cm_nld_tbl[i].resolution.fval;
+       i = st->light.nld_i_hi;
+       st->cfg.max_range.ival = cm_nld_tbl[i].max_range.ival;
+       st->cfg.max_range.fval = cm_nld_tbl[i].max_range.fval;
+       st->cfg.delay_us_min = cm_nld_tbl[i].delay_min_ms * 1000;
        return 0;
 }
 
 static int cm_probe(struct i2c_client *client,
                    const struct i2c_device_id *id)
 {
-       struct iio_dev *indio_dev;
        struct cm_state *st;
        int ret;
 
        dev_info(&client->dev, "%s\n", __func__);
-       indio_dev = iio_device_alloc(sizeof(*st));
-       if (indio_dev == NULL) {
-               dev_err(&client->dev, "%s iio_device_alloc ERR\n", __func__);
+       st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
+       if (st == NULL) {
+               dev_err(&client->dev, "%s devm_kzalloc ERR\n", __func__);
                return -ENOMEM;
        }
 
-       st = iio_priv(indio_dev);
+       i2c_set_clientdata(client, st);
        st->i2c = client;
-       i2c_set_clientdata(client, indio_dev);
-       ret = cm_of_dt(client, st);
+       ret = cm_of_dt(st, client->dev.of_node);
        if (ret) {
                dev_err(&client->dev, "%s _of_dt ERR\n", __func__);
                ret = -ENODEV;
-               goto cm_probe_err;
+               goto cm_probe_exit;
        }
 
        ret = cm_pm_init(st);
@@ -1320,64 +497,29 @@ static int cm_probe(struct i2c_client *client,
                goto cm_probe_exit;
        }
 
-       indio_dev->buffer = iio_kfifo_allocate(indio_dev);
-       if (!indio_dev->buffer) {
-               dev_err(&client->dev, "%s iio_kfifo_allocate ERR\n", __func__);
-               ret = -ENOMEM;
-               goto cm_probe_err;
+       cm_fn_dev.errs = &st->errs;
+       cm_fn_dev.sts = &st->sts;
+       st->nvs = nvs_iio();
+       if (st->nvs == NULL) {
+               dev_err(&client->dev, "%s nvs_iio ERR\n", __func__);
+               ret = -ENODEV;
+               goto cm_probe_exit;
        }
 
-       indio_dev->buffer->scan_timestamp = true;
-       indio_dev->modes = INDIO_DIRECT_MODE;
-       indio_dev->currentmode = INDIO_DIRECT_MODE;
-       indio_dev->dev.parent = &client->dev;
-       indio_dev->channels = cm_channels;
-       indio_dev->num_channels = ARRAY_SIZE(cm_channels);
-       indio_dev->name = CM_NAME;
-       indio_dev->info = &cm_iio_info;
-       indio_dev->setup_ops = &cm_buffer_setup_ops;
-       ret = iio_buffer_register(indio_dev, indio_dev->channels,
-                                 indio_dev->num_channels);
+       ret = st->nvs->probe(&st->nvs_data, st, &client->dev,
+                            &cm_fn_dev, &st->cfg);
        if (ret) {
-               dev_err(&client->dev, "%s iio_buffer_register ERR\n",
-                       __func__);
-               goto cm_probe_err;
+               dev_err(&client->dev, "%s nvs_probe ERR\n", __func__);
+               ret = -ENODEV;
+               goto cm_probe_exit;
        }
 
+       st->light.nvs_data = st->nvs_data;
+       st->light.handler = st->nvs->handler;
        INIT_DELAYED_WORK(&st->dw, cm_work);
-       st->trig = iio_trigger_alloc("%s-dev%d",
-                                    indio_dev->name, indio_dev->id);
-       if (st->trig == NULL) {
-               dev_err(&client->dev, "%s iio_allocate_trigger ERR\n",
-                       __func__);
-               ret = -ENOMEM;
-               goto cm_probe_err;
-       }
-
-       st->trig->dev.parent = &st->i2c->dev;
-       st->trig->ops = &cm_trigger_ops;
-       ret = iio_trigger_register(st->trig);
-       if (ret) {
-               dev_err(&client->dev, "%s iio_trigger_register ERR\n",
-                       __func__);
-               ret = -ENOMEM;
-               goto cm_probe_err;
-       }
-
-       indio_dev->trig = st->trig;
-       indio_dev->modes |= INDIO_BUFFER_TRIGGERED;
-       ret = iio_device_register(indio_dev);
-       if (ret) {
-               dev_err(&client->dev, "%s iio_device_register ERR\n",
-                       __func__);
-               goto cm_probe_err;
-       }
-
        dev_info(&client->dev, "%s done\n", __func__);
        return 0;
 
-cm_probe_err:
-       dev_err(&client->dev, "%s ERR %d\n", __func__, ret);
 cm_probe_exit:
        cm_remove(client);
        return ret;
index 5f69769..50fd007 100644 (file)
  */
 
 /* 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 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.
- */
-/* NVS light/proximity drivers have two calibration mechanisms:
- * Method 1 (preferred):
- * This method uses interpolation and requires a low and high uncalibrated
- * value along with the corresponding low and high calibrated values.  The
- * uncalibrated values are what is read from the sensor in the steps below.
- * The corresponding calibrated values are what the correct value should be.
- * All values are programmed into the device tree settings.
- * 1. Read scale sysfs attribute.  This value will need to be written back.
- * 2. Disable device.
- * 3. Write 1 to the scale sysfs attribute.
- * 4. Enable device.
- * 5. The NVS HAL will announce in the log that calibration mode is enabled and
- *    display the data along with the scale and offset parameters applied.
- * 6. Write the scale value read in step 1 back to the scale sysfs attribute.
- * 7. Put the device into a state where the data read is a low value.
- * 8. Note the values displayed in the log.  Separately measure the actual
- *    value.  The value from the sensor will be the uncalibrated value and the
- *    separately measured value will be the calibrated value for the current
- *    state (low or high values).
- * 9. Put the device into a state where the data read is a high value.
- * 10. Repeat step 8.
- * 11. Enter the values in the device tree settings for the device.  Both
- *     calibrated and uncalibrated values will be the values before scale and
- *     offset are applied.
- *     For example, a light sensor has the following device tree parameters:
- *     light_uncalibrated_lo
- *     light_calibrated_lo
- *     light_uncalibrated_hi
- *     light_calibrated_hi
- *     The proximity sensor parameters are:
- *     proximity_uncalibrated_lo
- *     proximity_calibrated_lo
- *     proximity_uncalibrated_hi
- *     proximity_calibrated_hi
- *
- * Method 2:
- * 1. Disable device.
- * 2. Write 1 to the scale sysfs attribute.
- * 3. Enable device.
- * 4. The NVS HAL will announce in the log that calibration mode is enabled and
- *    display the data along with the scale and offset parameters applied.
- * 5. Write to scale and offset sysfs attributes as needed to get the data
- *    modified as desired.
- * 6. Disabling the device disables calibration mode.
- * 7. Set the new scale and offset parameters in the device tree:
- *    <IIO channel>_scale_val
- *    <IIO channel>_scale_val2
- *    <IIO channel>_offset_val
- *    <IIO channel>_offset_val2
- *    The values are in IIO IIO_VAL_INT_PLUS_MICRO format.
- */
+/* See nvs_iio.c and nvs.h for documentation */
+/* See nvs_light.c and nvs_light.h for documentation */
 
 
 #include <linux/i2c.h>
 #include <linux/workqueue.h>
 #include <linux/interrupt.h>
 #include <linux/of.h>
-#include <linux/iio/iio.h>
-#include <linux/iio/sysfs.h>
-#include <linux/iio/buffer.h>
-#include <linux/iio/kfifo_buf.h>
-#include <linux/iio/trigger.h>
+#include <linux/nvs.h>
+#include <linux/nvs_light.h>
 
-#define CM_VERSION_DRIVER              (103)
 #define CM_VENDOR                      "Capella Microsystems, Inc."
 #define CM_NAME                                "cm3218x"
 #define CM_NAME_CM3218                 "cm3218"
 #define CM_NAME_CM32181                        "cm32181"
-/* setting _REPORT_N to 2 causes an extra reading after crossing the threshold
- * allowing a more accurate settled reported value.
- */
-#define CM_REPORT_N                    (2)
+#define CM_DEVID_CM3218                        (0x01)
+#define CM_DEVID_CM32181               (0x02)
+#define CM_HW_DELAY_MS                 (10)
+#define CM_ALS_SM_DFLT                 (0x01)
+#define CM_ALS_PERS_DFLT               (0x00)
+#define CM_ALS_PSM_DFLT                        (0x07)
 #define CM_LIGHT_VERSION               (1)
 #define CM_LIGHT_MAX_RANGE_IVAL                (119156)
 #define CM_LIGHT_MAX_RANGE_MICRO       (0)
 #define CM_LIGHT_RESOLUTION_IVAL       (0)
-#define CM_LIGHT_RESOLUTION_MICRO      (1000)
+#define CM_LIGHT_RESOLUTION_MICRO      (167000)
+#define CM_LIGHT_MILLIAMP_IVAL         (0)
+#define CM_LIGHT_MILLIAMP_MICRO                (3000)
 #define CM_LIGHT_SCALE_IVAL            (0)
-#define CM_LIGHT_SCALE_MICRO           (1000)
+#define CM_LIGHT_SCALE_MICRO           (10000)
 #define CM_LIGHT_OFFSET_IVAL           (0)
 #define CM_LIGHT_OFFSET_MICRO          (0)
-#define CM_LIGHT_MILLIAMP              "0.003"
-#define CM_DEVID_CM3218                        (0x01)
-#define CM_DEVID_CM32181               (0x02)
-#define CM_HW_DELAY_MS                 (10)
-#define CM_POLL_DELAY_MS_DFLT          (2000)
-#define CM_THRESHOLD_LUX_DFLT          (50)
-#define CM_ALS_SM_DFLT                 (0x01)
-#define CM_ALS_PERS_DFLT               (0x00)
-#define CM_ALS_PSM_DFLT                        (0x07)
+#define CM_LIGHT_THRESHOLD_LO          (100)
+#define CM_LIGHT_THRESHOLD_HI          (100)
+#define CM_POLL_DLY_MS_MIN             (800)
+#define CM_POLL_DLY_MS_MAX             (4000)
 /* HW registers */
 #define CM_REG_CFG                     (0x00)
 #define CM_REG_CFG_ALS_SM              (11)
 #define CM_REG_ALS_IF                  (0x06)
 #define CM_REG_ALS_IF_L                        (15)
 #define CM_REG_ALS_IF_H                        (14)
-/* _buf_push expects this scan order */
-#define CM_SCAN_LIGHT                  (0)
-#define CM_SCAN_TIMESTAMP              (1)
-/* debug flags */
-#define CM_DBG_SPEW_MSG                        (1 << 0)
-#define CM_DBG_SPEW_LIGHT              (1 << 1)
-#define CM_DBG_SPEW_LIGHT_POLL         (1 << 2)
-#define CM_DBG_IRQ                     (1 << 3)
-#define CM_DBG_VAL_LIGHT               (1 << 4)
-
-enum CM_ATTR {
-       CM_ATTR_PART,
-       CM_ATTR_VENDOR,
-       CM_ATTR_VERSION,
-       CM_ATTR_MILLIAMP,
-       CM_ATTR_ENABLE,
-       CM_ATTR_THRESH_LO,
-       CM_ATTR_THRESH_HI,
-};
 
-enum CM_INFO {
-       CM_INFO_DATA = 0,
-       CM_INFO_VER,
-       CM_INFO_ERRS,
-       CM_INFO_REGS,
-       CM_INFO_DBG,
-       CM_INFO_LIGHT_SPEW,
-       CM_INFO_LIGHT_POLL_SPEW,
-       CM_INFO_DBG_IRQ,
-       CM_INFO_LIMIT_MAX,
-};
 
 /* regulator names in order of powering on */
 static char *cm_vregs[] = {
@@ -198,19 +83,13 @@ static unsigned short cm_i2c_addrs[] = {
        0x48,
 };
 
-struct cm_it {                         /* integration time */
-       unsigned int ms;                /* time ms */
-       unsigned int resolution;        /* lux/bit to be scaled by val2 */
-       u8 als_it;                      /* FD_IT HW */
-};
-
-static struct cm_it cm_it_tbl[] = {
-       { 800, 5000, 0x03 },            /* 0.005 lux / LSb */
-       { 400, 10000, 0x02 },           /* 0.010 lux / LSb */
-       { 200, 21000, 0x01 },           /* 0.021 lux / LSb */
-       { 100, 42000, 0x00 },           /* 0.042 lux / LSb */
-       { 50, 84000, 0x08 },            /* 0.084 lux / LSb */
-       { 25, 167000, 0x0C }            /* 0.168 lux / LSb */
+static struct nvs_light_dynamic cm_nld_tbl[] = {
+       {{0, 5000},   {327,   675000}, {0, 3000}, 800, 0x00C0},
+       {{0, 10000},  {655,   350000}, {0, 3000}, 400, 0x0080},
+       {{0, 21000},  {13762, 350000}, {0, 3000}, 200, 0x0040},
+       {{0, 42000},  {27524, 700000}, {0, 3000}, 100, 0x0000},
+       {{0, 84000},  {55049, 400000}, {0, 3000}, 50,  0x0200},
+       {{0, 167000}, {10944, 345000}, {0, 3000}, 25,  0x0300}
 };
 
 static unsigned int cm_psm_ms_tbl[] = {
@@ -222,43 +101,22 @@ static unsigned int cm_psm_ms_tbl[] = {
 
 struct cm_state {
        struct i2c_client *i2c;
-       struct iio_trigger *trig;
+       struct nvs_fn_if *nvs;
+       void *nvs_data;
+       struct sensor_cfg cfg;
        struct delayed_work dw;
        struct regulator_bulk_data vreg[ARRAY_SIZE(cm_vregs)];
-       unsigned int info;              /* info data to return */
-       unsigned int dbg;               /* debug flags */
+       struct nvs_light light;
+       struct nvs_light_dynamic nld_tbl[ARRAY_SIZE(cm_nld_tbl)];
+       unsigned int sts;               /* debug flags */
        unsigned int errs;              /* error count */
-       unsigned int enable;            /* enable status */
-       unsigned int poll_delay_ms;     /* requested sampling delay (ms) */
-       unsigned int queue_delay_ms;    /* workqueue delay time (ms) */
-       unsigned int scale_i;           /* index into HW IT settings table */
-       int scale_val;                  /* user scale val */
-       int scale_val2;                 /* user scale val2 */
-       int offset_val;                 /* user offset val */
-       int offset_val2;                /* user offset val2 */
-       int lux_uc_lo;                  /* interpolation x1 uncalibrated lo */
-       int lux_uc_hi;                  /* interpolation x3 uncalibrated hi */
-       int lux_c_lo;                   /* interpolation y1 calibrated lo */
-       int lux_c_hi;                   /* interpolation y3 calibrated hi */
-       unsigned int report;            /* used to report first valid sample */
-       unsigned int report_n;          /* this many on-change data reports */
-       unsigned int lux_thr_lo;        /* report when new lux below this */
-       unsigned int lux_thr_hi;        /* report when new lux above this */
-       unsigned int it_i_lo;           /* integration time index low limit */
-       unsigned int it_i_hi;           /* integration time index high limit */
-       unsigned int psm_ms;            /* additional IT for PSM */
+       unsigned int enabled;           /* enable status */
        bool iio_ts_en;                 /* use IIO timestamps */
-       bool shutdown;                  /* shutdown active flag */
-       bool suspend;                   /* suspend active flag */
        bool hw_change;                 /* HW changed so drop first sample */
-       bool hw_sync;                   /* queue time match HW sample time */
-       const char *part;               /* part name */
        u16 i2c_addr;                   /* I2C address */
        u8 dev_id;                      /* device ID */
        u16 als_cfg;                    /* ALS register 0 defaults */
        u16 als_psm;                    /* ALS Power Save Mode */
-       u32 light;                      /* sample data */
-       s64 ts;                         /* sample data timestamp */
 };
 
 
@@ -324,144 +182,32 @@ static int cm_i2c_wr(struct cm_state *st, u8 reg, u16 val)
        return 0;
 }
 
-static int cm_vreg_dis(struct cm_state *st, unsigned int i)
-{
-       int ret = 0;
-
-       if (st->vreg[i].ret && (st->vreg[i].consumer != NULL)) {
-               ret = regulator_disable(st->vreg[i].consumer);
-               if (ret)
-                       dev_err(&st->i2c->dev, "%s %s ERR\n",
-                               __func__, st->vreg[i].supply);
-               else
-                       st->vreg[i].ret = 0;
-                       dev_dbg(&st->i2c->dev, "%s %s\n",
-                               __func__, st->vreg[i].supply);
-       }
-       return ret;
-}
-
-static int cm_vreg_dis_all(struct cm_state *st)
-{
-       unsigned int i;
-       int ret = 0;
-
-       for (i = ARRAY_SIZE(cm_vregs); i > 0; i--)
-               ret |= cm_vreg_dis(st, (i - 1));
-       return ret;
-}
-
-static int cm_vreg_en(struct cm_state *st, unsigned int i)
-{
-       int ret = 0;
-
-       if ((!st->vreg[i].ret) && (st->vreg[i].consumer != NULL)) {
-               ret = regulator_enable(st->vreg[i].consumer);
-               if (ret) {
-                       dev_err(&st->i2c->dev, "%s %s ERR\n",
-                               __func__, st->vreg[i].supply);
-               } else {
-                       st->vreg[i].ret = 1;
-                       dev_dbg(&st->i2c->dev, "%s %s\n",
-                               __func__, st->vreg[i].supply);
-                       ret = 1; /* flag regulator state change */
-               }
-       }
-       return ret;
-}
-
-static int cm_vreg_en_all(struct cm_state *st)
-{
-       unsigned int i;
-       int ret = 0;
-
-       for (i = 0; i < ARRAY_SIZE(cm_vregs); i++)
-               ret |= cm_vreg_en(st, i);
-       return ret;
-}
-
-static void cm_vreg_exit(struct cm_state *st)
-{
-       unsigned int i;
-
-       for (i = 0; i < ARRAY_SIZE(cm_vregs); i++) {
-               if (st->vreg[i].consumer != NULL) {
-                       devm_regulator_put(st->vreg[i].consumer);
-                       st->vreg[i].consumer = NULL;
-                       dev_dbg(&st->i2c->dev, "%s %s\n",
-                               __func__, st->vreg[i].supply);
-               }
-       }
-}
-
-static int cm_vreg_init(struct cm_state *st)
-{
-       unsigned int i;
-       int ret = 0;
-
-       for (i = 0; i < ARRAY_SIZE(cm_vregs); i++) {
-               st->vreg[i].supply = cm_vregs[i];
-               st->vreg[i].ret = 0;
-               st->vreg[i].consumer = devm_regulator_get(&st->i2c->dev,
-                                                         st->vreg[i].supply);
-               if (IS_ERR(st->vreg[i].consumer)) {
-                       ret |= PTR_ERR(st->vreg[i].consumer);
-                       dev_err(&st->i2c->dev, "%s ret %d for %s\n",
-                               __func__, ret, st->vreg[i].supply);
-                       st->vreg[i].consumer = NULL;
-               } else {
-                       dev_dbg(&st->i2c->dev, "%s %s\n",
-                               __func__, st->vreg[i].supply);
-               }
-       }
-       return ret;
-}
-
-static int cm_vreg_sts(struct cm_state *st)
-{
-       unsigned int i;
-       int ret = 0;
-
-       for (i = 0; i < ARRAY_SIZE(cm_vregs); i++) {
-               if (st->vreg[i].consumer != NULL)
-                       break;
-       }
-       if (i < ARRAY_SIZE(cm_vregs)) {
-               /* ret == number of regulators on */
-               for (i = 0; i < ARRAY_SIZE(cm_vregs); i++) {
-                       if (st->vreg[i].ret)
-                               ret++;
-               }
-       } else {
-               /* no regulator support (can assume always on) */
-               ret = -EINVAL;
-       }
-       return ret;
-}
-
 static int cm_pm(struct cm_state *st, bool enable)
 {
-       int ret = 0;
+       int ret;
 
        if (enable) {
-               ret = cm_vreg_en_all(st);
+               ret = nvs_vregs_enable(&st->i2c->dev, st->vreg,
+                                      ARRAY_SIZE(cm_vregs));
                if (ret) {
                        mdelay(CM_HW_DELAY_MS);
                        if (st->dev_id == CM_DEVID_CM32181)
                                cm_i2c_wr(st, CM_REG_PSM, st->als_psm);
                }
        } else {
-               ret = cm_vreg_sts(st);
+               ret = nvs_vregs_sts(st->vreg, ARRAY_SIZE(cm_vregs));
                if ((ret < 0) || (ret == ARRAY_SIZE(cm_vregs))) {
                        ret = cm_i2c_wr(st, CM_REG_CFG,
                                        1 << CM_REG_CFG_ALS_SD);
                } else if (ret > 0) {
-                       cm_vreg_en_all(st);
+                       nvs_vregs_enable(&st->i2c->dev, st->vreg,
+                                        ARRAY_SIZE(cm_vregs));
                        mdelay(CM_HW_DELAY_MS);
                        ret = cm_i2c_wr(st, CM_REG_CFG,
                                        1 << CM_REG_CFG_ALS_SD);
                }
-               ret |= cm_vreg_dis_all(st);
+               ret |= nvs_vregs_disable(&st->i2c->dev, st->vreg,
+                                        ARRAY_SIZE(cm_vregs));
        }
        if (ret > 0)
                ret = 0;
@@ -469,7 +215,7 @@ static int cm_pm(struct cm_state *st, bool enable)
                dev_err(&st->i2c->dev, "%s pwr=%x ERR=%d\n",
                        __func__, enable, ret);
        } else {
-               if (st->dbg & CM_DBG_SPEW_MSG)
+               if (st->sts & NVS_STS_SPEW_MSG)
                        dev_info(&st->i2c->dev, "%s pwr=%x\n",
                                 __func__, enable);
        }
@@ -479,224 +225,53 @@ static int cm_pm(struct cm_state *st, bool enable)
 static void cm_pm_exit(struct cm_state *st)
 {
        cm_pm(st, false);
-       cm_vreg_exit(st);
+       nvs_vregs_exit(&st->i2c->dev, st->vreg, ARRAY_SIZE(cm_vregs));
 }
 
 static int cm_pm_init(struct cm_state *st)
 {
        int ret;
 
-       st->enable = 0;
-       st->poll_delay_ms = CM_POLL_DELAY_MS_DFLT;
-       cm_vreg_init(st);
+       st->enabled = 0;
+       nvs_vregs_init(&st->i2c->dev,
+                      st->vreg, ARRAY_SIZE(cm_vregs), cm_vregs);
        ret = cm_pm(st, true);
        return ret;
 }
 
-static unsigned int cm_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 void cm_buf_push(struct iio_dev *indio_dev, struct cm_state *st)
-{
-       unsigned char buf[16];
-       unsigned int n;
-       unsigned int i;
-       unsigned int bytes = 0;
-
-       if (!iio_buffer_enabled(indio_dev))
-               return;
-
-       if (iio_scan_mask_query(indio_dev, indio_dev->buffer, CM_SCAN_LIGHT)) {
-               n = sizeof(st->light);
-               i = cm_buf_index(n, &bytes);
-               memcpy(&buf[i], &st->light, n);
-               if (st->dbg & CM_DBG_SPEW_LIGHT)
-                       dev_info(&st->i2c->dev, "light %u %lld\n",
-                                st->light, st->ts);
-       }
-       if (indio_dev->buffer->scan_timestamp) {
-               n = sizeof(st->ts);
-               i = cm_buf_index(n, &bytes);
-               memcpy(&buf[i], &st->ts, n);
-       }
-       iio_push_to_buffers(indio_dev, buf);
-}
-
-static int cm_cmd_wr(struct cm_state *st, int als_it, bool irq_en)
+static int cm_cmd_wr(struct cm_state *st, bool irq_en)
 {
        u16 als_cfg;
-       int ret;
-
-       als_cfg = st->als_cfg;
-       als_cfg |= als_it << CM_REG_CFG_ALS_IT;
-       if (irq_en && st->i2c->irq)
-               als_cfg |= (1 << CM_REG_CFG_ALS_INT_EN);
-       ret = cm_i2c_wr(st, CM_REG_CFG, als_cfg);
-       if (st->dbg & CM_DBG_SPEW_MSG)
-               dev_info(&st->i2c->dev, "%s als_cfg=%hx\n",
-                        __func__, als_cfg);
-       return ret;
-}
-
-static int cm_it_wr(struct cm_state *st, unsigned int ms)
-{
-       unsigned int i;
-       int ret;
-
-       /* get the HW settings for integration time (IT) ms */
-       for (i = st->it_i_lo; i < st->it_i_hi; i++) {
-               if (ms >= cm_it_tbl[i].ms + st->psm_ms)
-                       break;
-       }
-       if (i >= st->it_i_hi)
-               i = (st->it_i_hi - 1);
-       ret = cm_cmd_wr(st, cm_it_tbl[i].als_it, false);
-       if (!ret) {
-               st->hw_change = true;
-               st->scale_i = i;
-       }
-       if (st->dbg & CM_DBG_SPEW_MSG)
-               dev_info(&st->i2c->dev, "%s IT=%ums + %u psm_ms  err=%d\n",
-                        __func__, cm_it_tbl[i].ms, st->psm_ms, ret);
-       return ret;
-}
-
-static void cm_delay(struct cm_state *st, bool hw_sync)
-{
-       unsigned int ms;
-
-       if (hw_sync)
-               st->hw_sync = true;
-       ms = cm_it_tbl[st->scale_i].ms + st->psm_ms;
-       if ((ms < st->poll_delay_ms) && !hw_sync)
-               st->queue_delay_ms = st->poll_delay_ms;
-       else
-               /* we're either outside the HW integration time (IT) window and
-                * want to get within the window as fast as HW allows us
-                * (hw_sync = true)
-                * OR
-                * HW IT is not as fast as requested polling time
-                */
-               st->queue_delay_ms = ms;
-       if (st->dbg & CM_DBG_SPEW_MSG)
-               dev_info(&st->i2c->dev, "%s queue_delay_ms=%u\n",
-                        __func__, st->queue_delay_ms);
-}
-
-static int cm_interpolate(int x1, int x2, int x3, int y1, int *y2, int y3)
-{
-       int dividend;
-       int divisor;
-
-       /* y2 = ((x2 - x1)(y3 - y1)/(x3 - x1)) + y1 */
-       divisor = (x3 - x1);
-       if (!divisor)
-               return -EINVAL;
-
-       dividend = (x2 - x1) * (y3 - y1);
-       *y2 = (dividend / divisor) + y1;
-       return 0;
-}
-
-static void cm_report_init(struct cm_state *st)
-{
-       st->ts = 0;
-       st->report = st->report_n;
-}
-
-static int cm_step(struct cm_state *st, u16 step, u32 lux, bool irq_en)
-{
-       u64 calc;
-       int thr_lo;
-       int thr_hi;
        int ret = 0;
 
-       /* lux lo threshold to HW value */
-       thr_lo = lux - st->lux_thr_lo;
-       /* get the uncalibrated value */
-       cm_interpolate(st->lux_c_lo, thr_lo, st->lux_c_hi,
-                      st->lux_uc_lo, &thr_lo, st->lux_uc_hi);
-       if (thr_lo < 0)
-               thr_lo = 0;
-       /* convert to HW value */
-       calc = thr_lo;
-       if (st->scale_val2)
-               calc *= st->scale_val2;
-       if (cm_it_tbl[st->scale_i].resolution)
-               do_div(calc, cm_it_tbl[st->scale_i].resolution);
-       thr_lo = calc;
-       /* lux hi threshold to HW value */
-       thr_hi = lux + st->lux_thr_hi;
-       /* get the uncalibrated value */
-       cm_interpolate(st->lux_c_lo, thr_hi, st->lux_c_hi,
-                      st->lux_uc_lo, &thr_hi, st->lux_uc_hi);
-       /* convert to HW value */
-       calc = thr_hi;
-       if (st->scale_val2)
-               calc *= st->scale_val2;
-       if (cm_it_tbl[st->scale_i].resolution)
-               do_div(calc, cm_it_tbl[st->scale_i].resolution);
-       thr_hi = calc;
-       if (thr_hi > 0xFFFF)
-               thr_hi = 0xFFFF;
-       if (st->dbg & CM_DBG_SPEW_MSG)
-               dev_info(&st->i2c->dev, "%s lo=%d step=%hu hi=%d\n",
-                        __func__, thr_lo, step, thr_hi);
-       /* adjust resolution if need to make room for HW thresholds */
-       if ((step > (0xFFFF - thr_hi)) && (st->scale_i < (st->it_i_hi - 1))) {
-               /* too many photons - need to decrease resolution */
-               ret = cm_it_wr(st, cm_it_tbl[st->scale_i + 1].ms);
-               if (!ret)
-                       cm_delay(st, true);
-               return ret;
-       } else if ((step < thr_lo) && (st->scale_i > (st->it_i_lo + 1))) {
-               /* not enough photons - need to increase resolution */
-               ret = cm_it_wr(st, cm_it_tbl[st->scale_i - 1].ms);
-               if (!ret)
-                       cm_delay(st, true);
-               return ret;
-       } else if (st->hw_sync) {
-               /* adjust queue time to max(polling delay, HW IT) */
-               st->hw_sync = false;
-               cm_delay(st, false);
-       }
-
-       if (irq_en && st->i2c->irq && (st->lux_thr_lo || st->lux_thr_hi)) {
-               ret = cm_i2c_wr(st, CM_REG_WL, thr_lo);
-               ret |= cm_i2c_wr(st, CM_REG_WH, thr_hi);
+       als_cfg = st->als_cfg;
+       als_cfg |= cm_nld_tbl[st->light.nld_i].driver_data;
+       if (irq_en && st->i2c->irq) {
+               ret = cm_i2c_wr(st, CM_REG_WL, st->light.hw_thresh_lo);
+               ret |= cm_i2c_wr(st, CM_REG_WH, st->light.hw_thresh_hi);
                if (!ret) {
-                       ret = cm_cmd_wr(st,
-                                       cm_it_tbl[st->scale_i].als_it, true);
-                       if (!ret)
-                               ret = 1; /* flag IRQ enabled */
+                       als_cfg |= (1 << CM_REG_CFG_ALS_INT_EN);
+                       ret = RET_HW_UPDATE; /* flag IRQ enabled */
                }
        }
+       ret |= cm_i2c_wr(st, CM_REG_CFG, als_cfg);
+       if (ret >= 0)
+               st->hw_change = true;
+       if (st->sts & NVS_STS_SPEW_MSG)
+               dev_info(&st->i2c->dev, "%s als_cfg=%hx\n",
+                        __func__, als_cfg);
        return ret;
 }
 
-static int cm_rd(struct iio_dev *indio_dev)
+static int cm_rd(struct cm_state *st)
 {
-       struct cm_state *st = iio_priv(indio_dev);
        u16 sts;
-       u16 step;
-       u32 lux;
-       u64 calc;
+       u16 hw;
        s64 ts;
-       s64 ts_elapsed;
-       bool t_min = false;
        int ret;
 
        /* spec is vague so one of these should clear the IRQ */
-       ret = cm_i2c_rd(st, CM_REG_ALS, &step);
+       ret = cm_i2c_rd(st, CM_REG_ALS, &hw);
        ret |= cm_i2c_rd(st, CM_REG_ALS_IF, &sts);
        if (ret)
                return ret;
@@ -707,608 +282,214 @@ static int cm_rd(struct iio_dev *indio_dev)
                return 0;
        }
 
-       calc = cm_it_tbl[st->scale_i].resolution;
-       calc *= step;
-       if (st->scale_val2)
-               do_div(calc, st->scale_val2);
-       lux = calc;
-       /* get calibrated value */
-       cm_interpolate(st->lux_uc_lo, lux, st->lux_uc_hi,
-                      st->lux_c_lo, &lux, st->lux_c_hi);
-       if (sts & ((1 << CM_REG_ALS_IF_L) | (1 << CM_REG_ALS_IF_H))) {
-               st->report = st->report_n;
-       } else {
-               if (lux > (st->light + st->lux_thr_hi)) {
-                       st->report = st->report_n;
-               } else if (st->light > st->lux_thr_lo) {
-                       if (lux < (st->light - st->lux_thr_lo))
-                               st->report = st->report_n;
-               }
-       }
-       /* calculate elapsed time for allowed report rate */
        ts = cm_get_time_ns(st);
-       ts_elapsed = ts - st->ts;
-       if (ts_elapsed >= st->poll_delay_ms * 1000000)
-               t_min = true;
-       if (st->dbg & CM_DBG_SPEW_LIGHT_POLL)
+       if (st->sts & NVS_STS_SPEW_DATA)
                dev_info(&st->i2c->dev,
-                        "poll light %d %lld  diff: %d %lldns  hw=%hu\n",
-                        lux, ts, lux - st->light, ts_elapsed, step);
-       if ((st->report && t_min) || ((st->scale_val == 1) &&
-                                     !st->scale_val2)) {
-               /* report if:
-                * - st->report && time since last report >= polling delay
-                * - in calibration mode (scale == 1)
-                */
-               if (st->report)
-                       st->report--;
-               if (!(st->dbg & CM_DBG_VAL_LIGHT))
-                       st->light = lux;
-               st->ts = ts;
-               cm_buf_push(indio_dev, st);
-               if (st->report)
-                       ret = cm_step(st, step, lux, false);
-               else
-                       ret = cm_step(st, step, lux, true);
-       } else if (t_min && !st->report) {
-               ret = cm_step(st, step, lux, true);
-       } else {
-               /* data changes are happening faster than allowed to report
-                * so we poll for the next data at an allowed rate.
-                */
-               ret = cm_step(st, step, lux, false);
+                        "poll light hw %hu %lld  diff=%d %lldns  index=%u\n",
+                        hw, ts, hw - st->light.hw, ts - st->light.timestamp,
+                        st->light.nld_i);
+       st->light.hw = hw;
+       st->light.timestamp = ts;
+       ret = nvs_light_read(&st->light);
+       switch (ret) {
+       case RET_POLL_NEXT:
+               if (st->light.nld_i_change)
+                       ret = cm_cmd_wr(st, false);
+               break;
+
+       case RET_NO_CHANGE:
+               if (st->i2c->irq)
+                       ret = RET_HW_UPDATE;
+               break;
+
+       case RET_HW_UPDATE:
+               ret = cm_cmd_wr(st, true);
+               break;
+
+       default:
+               break;
        }
+
        return ret;
 }
 
-static void cm_read(struct iio_dev *indio_dev)
+static void cm_read(struct cm_state *st)
 {
-       struct cm_state *st = iio_priv(indio_dev);
        int ret;
 
-       mutex_lock(&indio_dev->mlock);
-       if (st->enable) {
-               ret = cm_rd(indio_dev);
-               if (ret < 1)
+       st->nvs->mutex_lock(st->nvs_data);
+       if (st->enabled) {
+               ret = cm_rd(st);
+               if (ret < RET_HW_UPDATE)
                        schedule_delayed_work(&st->dw,
-                                        msecs_to_jiffies(st->queue_delay_ms));
+                                   msecs_to_jiffies(st->light.poll_delay_ms));
        }
-       mutex_unlock(&indio_dev->mlock);
+       st->nvs->mutex_unlock(st->nvs_data);
 }
 
 static void cm_work(struct work_struct *ws)
 {
        struct cm_state *st = container_of((struct delayed_work *)ws,
                                           struct cm_state, dw);
-       struct iio_dev *indio_dev = iio_priv_to_dev(st);
 
-       cm_read(indio_dev);
+       cm_read(st);
 }
 
 static irqreturn_t cm_irq_thread(int irq, void *dev_id)
 {
        struct cm_state *st = (struct cm_state *)dev_id;
-       struct iio_dev *indio_dev = iio_priv_to_dev(st);
 
-       if (st->dbg & CM_DBG_IRQ)
+       if (st->sts & NVS_STS_SPEW_IRQ)
                dev_info(&st->i2c->dev, "%s\n", __func__);
-       cm_read(indio_dev);
+       cm_read(st);
        return IRQ_HANDLED;
 }
 
-static int cm_disable(struct iio_dev *indio_dev)
+static int cm_disable(struct cm_state *st)
 {
-       struct cm_state *st = iio_priv(indio_dev);
        int ret;
 
-       if (!(iio_scan_mask_query(indio_dev, indio_dev->buffer,
-                                 CM_SCAN_LIGHT)))
-               st->dbg &= ~CM_DBG_VAL_LIGHT;
        cancel_delayed_work(&st->dw);
        ret = cm_pm(st, false);
        if (!ret)
-               st->enable = 0;
+               st->enabled = 0;
        return ret;
 }
 
-static int cm_enable(struct iio_dev *indio_dev)
+static int cm_enable(void *client, int snsr_id, int enable)
 {
-       struct cm_state *st = iio_priv(indio_dev);
-       int ret = -EINVAL;
+       struct cm_state *st = (struct cm_state *)client;
+       unsigned int ms;
+       int ret;
+
+       if (enable < 0)
+               return st->enabled;
 
-       if (iio_scan_mask_query(indio_dev, indio_dev->buffer, CM_SCAN_LIGHT)) {
+       if (enable) {
                ret = cm_pm(st, true);
                if (!ret) {
-                       ret = cm_it_wr(st, 0);
+                       nvs_light_enable(&st->light);
+                       ret = cm_cmd_wr(st, false);
                        if (ret) {
-                               cm_disable(indio_dev);
+                               cm_disable(st);
                        } else {
-                               cm_delay(st, true);
-                               cm_report_init(st);
-                               st->enable = 1;
+                               st->enabled = enable;
+                               ms = st->light.poll_delay_ms;
                                schedule_delayed_work(&st->dw,
-                                        msecs_to_jiffies(st->queue_delay_ms));
+                                                     msecs_to_jiffies(ms));
                        }
                }
+       } else {
+               ret = cm_disable(st);
        }
        return ret;
 }
 
-static ssize_t cm_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 cm_state *st = iio_priv(indio_dev);
-       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
-       const char *msg;
-       unsigned int new;
-       unsigned int old = 0;
-       int ret;
-
-       ret = kstrtouint(buf, 10, &new);
-       if (ret)
-               return -EINVAL;
-
-       mutex_lock(&indio_dev->mlock);
-       if (st->shutdown || st->suspend) {
-               mutex_unlock(&indio_dev->mlock);
-               return -EPERM;
-       }
-
-       switch (this_attr->address) {
-       case CM_ATTR_ENABLE:
-               msg = "ATTR_ENABLE";
-               if (st->enable)
-                       old = 1;
-               if (new)
-                       ret = cm_enable(indio_dev);
-               else
-                       ret = cm_disable(indio_dev);
-               break;
-
-       case CM_ATTR_THRESH_LO:
-               msg = "ATTR_THRESH_LO";
-               old = st->lux_thr_lo;
-               st->lux_thr_lo = new;
-               break;
-
-       case CM_ATTR_THRESH_HI:
-               msg = "ATTR_THRESH_HI";
-               old = st->lux_thr_hi;
-               st->lux_thr_hi = new;
-               break;
-
-       default:
-               msg = "ATTR_UNKNOWN";
-               ret = -EINVAL;
-       }
-
-       cm_report_init(st);
-       mutex_unlock(&indio_dev->mlock);
-       cm_read(indio_dev);
-       if (st->dbg & CM_DBG_SPEW_MSG) {
-               if (ret)
-                       dev_err(&st->i2c->dev, "%s %s %d->%d ERR=%d\n",
-                               __func__, msg, old, new, ret);
-               else
-                       dev_info(&st->i2c->dev, "%s %s %d->%d\n",
-                                __func__, msg, old, new);
-       }
-       if (ret)
-               return ret;
-
-       return count;
-}
-
-static ssize_t cm_attr_show(struct device *dev, struct device_attribute *attr,
-                           char *buf)
+static int cm_batch(void *client, int snsr_id, int flags,
+                   unsigned int period, unsigned int timeout)
 {
-       struct iio_dev *indio_dev = dev_get_drvdata(dev);
-       struct cm_state *st = iio_priv(indio_dev);
-       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
-
-       switch (this_attr->address) {
-       case CM_ATTR_ENABLE:
-               return sprintf(buf, "%x\n", st->enable);
-
-       case CM_ATTR_PART:
-               return sprintf(buf, "%s light\n", st->part);
-
-       case CM_ATTR_VENDOR:
-               return sprintf(buf, "%s\n", CM_VENDOR);
-
-       case CM_ATTR_VERSION:
-               return sprintf(buf, "%u\n", CM_LIGHT_VERSION);
+       struct cm_state *st = (struct cm_state *)client;
 
-       case CM_ATTR_MILLIAMP:
-               return sprintf(buf, "%s\n", CM_LIGHT_MILLIAMP);
-
-       case CM_ATTR_THRESH_LO:
-               return sprintf(buf, "%u\n", st->lux_thr_lo);
-
-       case CM_ATTR_THRESH_HI:
-               return sprintf(buf, "%u\n", st->lux_thr_hi);
-
-       default:
+       if (timeout)
+               /* timeout not supported (no HW FIFO) */
                return -EINVAL;
-       }
 
-       return -EINVAL;
-}
-
-static ssize_t cm_data_store(struct device *dev,
-                            struct device_attribute *attr,
-                            const char *buf, size_t count)
-{
-       struct cm_state *st = iio_priv(dev_get_drvdata(dev));
-       unsigned int info;
-       int ret;
-
-       ret = kstrtouint(buf, 10, &info);
-       if (ret)
-               return -EINVAL;
-
-       if (info >= CM_INFO_LIMIT_MAX)
-               return -EINVAL;
-
-       st->info = info;
-       switch (info) {
-       case CM_INFO_DATA:
-               st->dbg = 0;
-               break;
-
-       case CM_INFO_DBG:
-               st->dbg ^= CM_DBG_SPEW_MSG;
-               break;
-
-       case CM_INFO_LIGHT_SPEW:
-               st->dbg ^= CM_DBG_SPEW_LIGHT;
-               break;
-
-       case CM_INFO_LIGHT_POLL_SPEW:
-               st->dbg ^= CM_DBG_SPEW_LIGHT_POLL;
-               break;
-
-       case CM_INFO_DBG_IRQ:
-               st->dbg ^= CM_DBG_IRQ;
-               break;
-
-       default:
-               break;
-       }
-
-       return count;
+       st->light.delay_us = period;
+       return 0;
 }
 
-static ssize_t cm_data_show(struct device *dev,
-                           struct device_attribute *attr, char *buf)
+static int cm_regs(void *client, int snsr_id, char *buf)
 {
-       struct cm_state *st = iio_priv(dev_get_drvdata(dev));
-       enum CM_INFO info;
+       struct cm_state *st = (struct cm_state *)client;
        ssize_t t;
        u16 val;
        u8 i;
        int ret;
 
-       info = st->info;
-       st->info = CM_INFO_DATA;
-       switch (info) {
-       case CM_INFO_DATA:
-               t = sprintf(buf, "LIGHT: %d  ts: %lld\n",
-                           st->light, st->ts);
-               return t;
-
-       case CM_INFO_VER:
-               return sprintf(buf, "version=%u\n", CM_VERSION_DRIVER);
-
-       case CM_INFO_ERRS:
-               t = sprintf(buf, "error count=%u\n", st->errs);
-               st->errs = 0;
-               return t;
-
-       case CM_INFO_REGS:
-               t = sprintf(buf, "registers:\n");
-               for (i = 0; i <= CM_REG_ALS_IF; i++) {
-                       ret = cm_i2c_rd(st, i, &val);
-                       if (!ret)
-                               t += sprintf(buf + t, "%#2x=%#4x\n", i, val);
-               }
-               return t;
-
-       case CM_INFO_DBG:
-               return sprintf(buf, "debug spew=%x\n",
-                              st->dbg & CM_DBG_SPEW_MSG);
-
-       case CM_INFO_LIGHT_SPEW:
-               return sprintf(buf, "lux_ts spew=%x\n",
-                              !!(st->dbg & CM_DBG_SPEW_LIGHT));
-
-       case CM_INFO_LIGHT_POLL_SPEW:
-               return sprintf(buf, "lux_poll_ts spew=%x\n",
-                              !!(st->dbg & CM_DBG_SPEW_LIGHT_POLL));
-
-       case CM_INFO_DBG_IRQ:
-               return sprintf(buf, "debug IRQ=%x\n",
-                              !!(st->dbg & CM_DBG_IRQ));
-
-       default:
-               break;
+       t = sprintf(buf, "registers:\n");
+       for (i = 0; i <= CM_REG_ALS_IF; i++) {
+               ret = cm_i2c_rd(st, i, &val);
+               if (!ret)
+                       t += sprintf(buf + t, "%#2x=%#4x\n", i, val);
        }
-
-       return -EINVAL;
+       return t;
 }
 
-static IIO_DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
-                      cm_attr_show, cm_attr_store, CM_ATTR_ENABLE);
-static IIO_DEVICE_ATTR(illuminance_part, S_IRUGO,
-                      cm_attr_show, NULL, CM_ATTR_PART);
-static IIO_DEVICE_ATTR(illuminance_vendor, S_IRUGO,
-                      cm_attr_show, NULL, CM_ATTR_VENDOR);
-static IIO_DEVICE_ATTR(illuminance_version, S_IRUGO,
-                      cm_attr_show, NULL, CM_ATTR_VERSION);
-static IIO_DEVICE_ATTR(illuminance_milliamp, S_IRUGO,
-                      cm_attr_show, NULL, CM_ATTR_MILLIAMP);
-static IIO_DEVICE_ATTR(illuminance_thresh_rising_value,
-                      S_IRUGO | S_IWUSR | S_IWGRP,
-                      cm_attr_show, cm_attr_store, CM_ATTR_THRESH_HI);
-static IIO_DEVICE_ATTR(illuminance_thresh_falling_value,
-                      S_IRUGO | S_IWUSR | S_IWGRP,
-                      cm_attr_show, cm_attr_store, CM_ATTR_THRESH_LO);
-static DEVICE_ATTR(data, S_IRUGO | S_IWUSR | S_IWGRP,
-                  cm_data_show, cm_data_store);
-
-static struct attribute *cm_attrs[] = {
-       &dev_attr_data.attr,
-       &iio_dev_attr_enable.dev_attr.attr,
-       &iio_dev_attr_illuminance_part.dev_attr.attr,
-       &iio_dev_attr_illuminance_vendor.dev_attr.attr,
-       &iio_dev_attr_illuminance_version.dev_attr.attr,
-       &iio_dev_attr_illuminance_milliamp.dev_attr.attr,
-       &iio_dev_attr_illuminance_thresh_rising_value.dev_attr.attr,
-       &iio_dev_attr_illuminance_thresh_falling_value.dev_attr.attr,
-       NULL
-};
-
-static struct attribute_group cm_attr_group = {
-       .name = CM_NAME,
-       .attrs = cm_attrs
+static struct nvs_fn_dev cm_fn_dev = {
+       .enable                         = cm_enable,
+       .batch                          = cm_batch,
+       .regs                           = cm_regs,
 };
 
-static int cm_read_raw(struct iio_dev *indio_dev,
-                      struct iio_chan_spec const *chan,
-                      int *val, int *val2, long mask)
+static int cm_suspend(struct device *dev)
 {
-       struct cm_state *st = iio_priv(indio_dev);
+       struct i2c_client *client = to_i2c_client(dev);
+       struct cm_state *st = i2c_get_clientdata(client);
        int ret = 0;
 
-       switch (mask) {
-       case IIO_CHAN_INFO_RAW:
-               *val = st->light;
-               return IIO_VAL_INT;
-
-       case IIO_CHAN_INFO_SAMP_FREQ:
-               if (st->enable)
-                       *val = st->poll_delay_ms * 1000; /* ms => us */
-               else
-                       *val = (cm_it_tbl[st->it_i_hi - 1].ms +
-                               st->psm_ms) * 1000;
-               return IIO_VAL_INT;
-
-       case IIO_CHAN_INFO_SCALE:
-               *val = st->scale_val;
-               *val2 = st->scale_val2;
-               return IIO_VAL_INT_PLUS_MICRO;
-
-       case IIO_CHAN_INFO_OFFSET:
-               *val = st->offset_val;
-               *val2 = st->offset_val2;
-               return IIO_VAL_INT_PLUS_MICRO;
-
-       case IIO_CHAN_INFO_PEAK:
-               *val = CM_LIGHT_MAX_RANGE_IVAL;
-               *val2 = CM_LIGHT_MAX_RANGE_MICRO;
-               return IIO_VAL_INT_PLUS_MICRO;
-
-       case IIO_CHAN_INFO_PEAK_SCALE:
-               *val = CM_LIGHT_RESOLUTION_IVAL;
-               *val2 = CM_LIGHT_RESOLUTION_MICRO;
-               return IIO_VAL_INT_PLUS_MICRO;
-
-       default:
-               return -EINVAL;
-       }
-
+       st->sts |= NVS_STS_SUSPEND;
+       if (st->nvs && st->nvs_data)
+               ret = st->nvs->suspend(st->nvs_data);
+       if (st->sts & NVS_STS_SPEW_MSG)
+               dev_info(&client->dev, "%s\n", __func__);
        return ret;
 }
 
-static int cm_write_raw(struct iio_dev *indio_dev,
-                       struct iio_chan_spec const *chan,
-                       int val, int val2, long mask)
+static int cm_resume(struct device *dev)
 {
-       struct cm_state *st = iio_priv(indio_dev);
-       char *msg;
-       int old = 0;
-       int old2 = 0;
+       struct i2c_client *client = to_i2c_client(dev);
+       struct cm_state *st = i2c_get_clientdata(client);
        int ret = 0;
 
-       mutex_lock(&indio_dev->mlock);
-       if (st->shutdown || st->suspend) {
-               mutex_unlock(&indio_dev->mlock);
-               return -EPERM;
-       }
-
-       switch (mask) {
-       case IIO_CHAN_INFO_SAMP_FREQ:
-               msg = "IIO_CHAN_INFO_SAMP_FREQ";
-               old = st->poll_delay_ms * 1000; /* ms => us*/
-               st->poll_delay_ms = (unsigned int)(val / 1000); /* us => ms */
-               cm_delay(st, false);
-               break;
-
-       case IIO_CHAN_INFO_SCALE:
-               msg = "IIO_CHAN_INFO_SCALE";
-               old = st->scale_val;
-               old2 = st->scale_val2;
-               st->scale_val = val;
-               st->scale_val2 = val2;
-               break;
-
-       case IIO_CHAN_INFO_OFFSET:
-               msg = "IIO_CHAN_INFO_SCALE";
-               old = st->offset_val;
-               old2 = st->offset_val2;
-               st->offset_val = val;
-               st->offset_val2 = val2;
-               break;
-
-       case IIO_CHAN_INFO_RAW:
-               msg = "IIO_CHAN_INFO_RAW";
-               old = st->light;
-               st->light = val;
-               st->ts = cm_get_time_ns(st);
-               st->dbg |= CM_DBG_VAL_LIGHT;
-               cm_buf_push(indio_dev, st);
-               break;
-
-       default:
-               msg = "IIO_CHAN_INFO_UNKNOWN";
-               ret = -EINVAL;
-       }
-
-       cm_report_init(st);
-       mutex_unlock(&indio_dev->mlock);
-       cm_read(indio_dev);
-       if (st->dbg & CM_DBG_SPEW_MSG) {
-               if (ret)
-                       dev_err(&st->i2c->dev, "%s c=%d %d:%d->%d:%d ERR=%d\n",
-                               msg, chan->scan_index,
-                               old, old2, val, val2, ret);
-               else
-                       dev_info(&st->i2c->dev, "%s %s chan=%d %d:%d->%d:%d\n",
-                                __func__, msg, chan->scan_index,
-                                old, old2, val, val2);
-       }
-       return ret;
-}
-
-static const struct iio_info cm_iio_info = {
-       .driver_module = THIS_MODULE,
-       .attrs = &cm_attr_group,
-       .read_raw = &cm_read_raw,
-       .write_raw = &cm_write_raw,
-};
-
-static const struct iio_chan_spec cm_channels[] = {
-       {
-               .type                   = IIO_LIGHT,
-               .scan_index             = CM_SCAN_LIGHT,
-               .scan_type              = IIO_ST('u', 32, 32, 0),
-               .info_mask              = BIT(IIO_CHAN_INFO_RAW) |
-                                         BIT(IIO_CHAN_INFO_SAMP_FREQ) |
-                                         BIT(IIO_CHAN_INFO_PEAK) |
-                                         BIT(IIO_CHAN_INFO_PEAK_SCALE) |
-                                         BIT(IIO_CHAN_INFO_SCALE) |
-                                         BIT(IIO_CHAN_INFO_OFFSET),
-               .info_mask_separate     = BIT(IIO_CHAN_INFO_RAW) |
-                                         BIT(IIO_CHAN_INFO_SAMP_FREQ) |
-                                         BIT(IIO_CHAN_INFO_PEAK) |
-                                         BIT(IIO_CHAN_INFO_PEAK_SCALE) |
-                                         BIT(IIO_CHAN_INFO_SCALE) |
-                                         BIT(IIO_CHAN_INFO_OFFSET),
-       },
-       IIO_CHAN_SOFT_TIMESTAMP(CM_SCAN_TIMESTAMP)
-};
-
-static int cm_buffer_preenable(struct iio_dev *indio_dev)
-{
-       struct cm_state *st = iio_priv(indio_dev);
-
-       if (st->shutdown || st->suspend)
-               return -EINVAL;
-
-       return iio_sw_buffer_preenable(indio_dev);
-}
-
-static int cm_buffer_postenable(struct iio_dev *indio_dev)
-{
-       int ret;
-
-       ret = cm_enable(indio_dev);
-       /* never return > 0 to IIO buffer engine */
-       if (ret > 0)
-               ret = 0;
+       if (st->nvs && st->nvs_data)
+               ret = st->nvs->resume(st->nvs_data);
+       st->sts &= ~NVS_STS_SUSPEND;
+       if (st->sts & NVS_STS_SPEW_MSG)
+               dev_info(&client->dev, "%s\n", __func__);
        return ret;
 }
 
-static const struct iio_buffer_setup_ops cm_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 = &cm_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 = &cm_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 struct iio_trigger_ops cm_trigger_ops = {
-       .owner = THIS_MODULE,
-};
+static SIMPLE_DEV_PM_OPS(cm_pm_ops, cm_suspend, cm_resume);
 
-static int cm_suspend(struct device *dev)
+static void cm_shutdown(struct i2c_client *client)
 {
-       struct i2c_client *client = to_i2c_client(dev);
-       struct iio_dev *indio_dev = i2c_get_clientdata(client);
-       struct cm_state *st = iio_priv(indio_dev);
-       int ret = 0;
+       struct cm_state *st = i2c_get_clientdata(client);
 
-       mutex_lock(&indio_dev->mlock);
-       if (st->enable)
-               ret = cm_disable(indio_dev);
-       st->suspend = true;
-       mutex_unlock(&indio_dev->mlock);
-       if (st->dbg & CM_DBG_SPEW_MSG)
+       st->sts |= NVS_STS_SHUTDOWN;
+       if (st->nvs && st->nvs_data)
+               st->nvs->shutdown(st->nvs_data);
+       if (st->sts & NVS_STS_SPEW_MSG)
                dev_info(&client->dev, "%s\n", __func__);
-       return ret;
 }
 
-static int cm_resume(struct device *dev)
+static int cm_remove(struct i2c_client *client)
 {
-       struct i2c_client *client = to_i2c_client(dev);
-       struct iio_dev *indio_dev = i2c_get_clientdata(client);
-       struct cm_state *st = iio_priv(indio_dev);
+       struct cm_state *st = i2c_get_clientdata(client);
 
-       st->suspend = false;
-       if (st->dbg & CM_DBG_SPEW_MSG)
-               dev_info(&client->dev, "%s\n", __func__);
+       if (st != NULL) {
+               cm_shutdown(client);
+               if (st->nvs && st->nvs_data)
+                       st->nvs->remove(st->nvs_data);
+               if (st->dw.wq)
+                       destroy_workqueue(st->dw.wq);
+               cm_pm_exit(st);
+       }
+       dev_info(&client->dev, "%s\n", __func__);
        return 0;
 }
 
-static SIMPLE_DEV_PM_OPS(cm_pm_ops, cm_suspend, cm_resume);
-
-static int cm_id_dev(struct iio_dev *indio_dev, const char *name)
+static int cm_id_dev(struct cm_state *st, const char *name)
 {
-       struct cm_state *st = iio_priv(indio_dev);
        u16 val = 0;
+       unsigned int i;
        int ret = 0;
 
        if (!strcmp(name, CM_NAME_CM3218)) {
                st->dev_id = CM_DEVID_CM3218;
-               st->part = CM_NAME_CM3218;
+               st->cfg.part = CM_NAME_CM3218;
        } else if (!strcmp(name, CM_NAME_CM32181)) {
                st->dev_id = CM_DEVID_CM32181;
-               st->part = CM_NAME_CM32181;
+               st->cfg.part = CM_NAME_CM32181;
        }
        if (!st->dev_id) {
                ret = cm_i2c_rd(st, CM_REG_CFG, &val);
@@ -1317,29 +498,41 @@ static int cm_id_dev(struct iio_dev *indio_dev, const char *name)
                        st->als_cfg |= val;
                        if (val) {
                                st->dev_id = CM_DEVID_CM3218;
-                               st->part = CM_NAME_CM3218;
+                               st->cfg.part = CM_NAME_CM3218;
                        } else {
                                st->dev_id = CM_DEVID_CM32181;
-                               st->part = CM_NAME_CM32181;
+                               st->cfg.part = CM_NAME_CM32181;
                                cm_i2c_wr(st, CM_REG_PSM, st->als_psm);
                        }
                        dev_info(&st->i2c->dev, "%s found %s\n",
-                               __func__, st->part);
+                               __func__, st->cfg.part);
                }
        }
-       if (st->dev_id != CM_DEVID_CM32181) {
-               st->psm_ms = 0;
-               if (st->it_i_hi > 4)
-                       st->it_i_hi = 4;
-               if (st->it_i_lo > 4)
-                       st->it_i_lo = 4;
-       }
+       memcpy(&st->nld_tbl, &cm_nld_tbl, sizeof(st->nld_tbl));
+       if (st->dev_id == CM_DEVID_CM32181) {
+               if (st->als_psm & (1 << CM_REG_PSM_EN)) {
+                       for (i = 0; i < ARRAY_SIZE(cm_nld_tbl); i++)
+                               st->nld_tbl[i].delay_min_ms +=
+                                              cm_psm_ms_tbl[st->als_psm >> 1];
+               }
+       } else {
+               if (st->light.nld_i_hi > 4)
+                       st->light.nld_i_hi = 4;
+               if (st->light.nld_i_lo > 4)
+                       st->light.nld_i_lo = 4;
+       }
+       i = st->light.nld_i_lo;
+       st->cfg.resolution.ival = cm_nld_tbl[i].resolution.ival;
+       st->cfg.resolution.fval = cm_nld_tbl[i].resolution.fval;
+       i = st->light.nld_i_hi;
+       st->cfg.max_range.ival = cm_nld_tbl[i].max_range.ival;
+       st->cfg.max_range.fval = cm_nld_tbl[i].max_range.fval;
+       st->cfg.delay_us_min = cm_nld_tbl[i].delay_min_ms * 1000;
        return ret;
 }
 
-static int cm_id_i2c(struct iio_dev *indio_dev, const char *name)
+static int cm_id_i2c(struct cm_state *st, const char *name)
 {
-       struct cm_state *st = iio_priv(indio_dev);
        int i;
        int ret;
 
@@ -1350,12 +543,12 @@ static int cm_id_i2c(struct iio_dev *indio_dev, const char *name)
 
        if (i < ARRAY_SIZE(cm_i2c_addrs)) {
                st->i2c_addr = st->i2c->addr;
-               ret = cm_id_dev(indio_dev, name);
+               ret = cm_id_dev(st, name);
        } else {
                name = CM_NAME;
                for (i = 0; i < ARRAY_SIZE(cm_i2c_addrs); i++) {
                        st->i2c_addr = cm_i2c_addrs[i];
-                       ret = cm_id_dev(indio_dev, name);
+                       ret = cm_id_dev(st, name);
                        if (!ret)
                                break;
                }
@@ -1365,126 +558,66 @@ static int cm_id_i2c(struct iio_dev *indio_dev, const char *name)
        return ret;
 }
 
-static void cm_shutdown(struct i2c_client *client)
-{
-       struct iio_dev *indio_dev = i2c_get_clientdata(client);
-       struct cm_state *st = iio_priv(indio_dev);
-
-       mutex_lock(&indio_dev->mlock);
-       if (st->enable)
-               cm_disable(indio_dev);
-       st->shutdown = true;
-       mutex_unlock(&indio_dev->mlock);
-       if (st->dbg & CM_DBG_SPEW_MSG)
-               dev_info(&client->dev, "%s\n", __func__);
-}
-
-static int cm_remove(struct i2c_client *client)
-{
-       struct iio_dev *indio_dev = i2c_get_clientdata(client);
-       struct cm_state *st = iio_priv(indio_dev);
-
-       if (st != NULL) {
-               cm_shutdown(client);
-               if (indio_dev->dev.devt)
-                       iio_device_unregister(indio_dev);
-               if (st->trig != NULL) {
-                       if (client->irq)
-                               free_irq(client->irq, st);
-                       iio_trigger_unregister(st->trig);
-                       iio_trigger_free(st->trig);
-               }
-               if (indio_dev->buffer != NULL) {
-                       iio_buffer_unregister(indio_dev);
-                       iio_kfifo_free(indio_dev->buffer);
-               }
-               if (st->dw.wq)
-                       destroy_workqueue(st->dw.wq);
-               cm_pm_exit(st);
-               iio_device_free(indio_dev);
-       }
-       dev_info(&client->dev, "%s\n", __func__);
-       return 0;
-}
+static struct sensor_cfg cm_cfg_dflt = {
+       .name                   = NVS_LIGHT_STRING,
+       .ch_n                   = ARRAY_SIZE(iio_chan_spec_nvs_light),
+       .ch_inf                 = &iio_chan_spec_nvs_light,
+       .part                   = CM_NAME,
+       .vendor                 = CM_VENDOR,
+       .version                = CM_LIGHT_VERSION,
+       .max_range              = {
+               .ival           = CM_LIGHT_MAX_RANGE_IVAL,
+               .fval           = CM_LIGHT_MAX_RANGE_MICRO,
+       },
+       .resolution             = {
+               .ival           = CM_LIGHT_RESOLUTION_IVAL,
+               .fval           = CM_LIGHT_RESOLUTION_MICRO,
+       },
+       .milliamp               = {
+               .ival           = CM_LIGHT_MILLIAMP_IVAL,
+               .fval           = CM_LIGHT_MILLIAMP_MICRO,
+       },
+       .delay_us_min           = CM_POLL_DLY_MS_MIN * 1000,
+       .delay_us_max           = CM_POLL_DLY_MS_MAX * 1000,
+       .scale                  = {
+               .ival           = CM_LIGHT_SCALE_IVAL,
+               .fval           = CM_LIGHT_SCALE_MICRO,
+       },
+       .thresh_lo              = CM_LIGHT_THRESHOLD_LO,
+       .thresh_hi              = CM_LIGHT_THRESHOLD_HI,
+};
 
-static int cm_of_dt(struct i2c_client *client, struct cm_state *st)
+static int cm_of_dt(struct cm_state *st, struct device_node *dn)
 {
-       struct device_node *dn = client->dev.of_node;
        u16 als_sm;
        u16 als_pers;
-       unsigned int val;
-       unsigned int i;
 
        /* default device specific parameters */
        als_sm = CM_ALS_SM_DFLT;
        als_pers = CM_ALS_PERS_DFLT;
        st->als_psm = CM_ALS_PSM_DFLT;
        /* default NVS ALS programmable parameters */
-       st->scale_val = CM_LIGHT_SCALE_IVAL;
-       st->scale_val2 = CM_LIGHT_SCALE_MICRO;
-       st->offset_val = CM_LIGHT_OFFSET_IVAL;
-       st->offset_val2 = CM_LIGHT_OFFSET_MICRO;
-       st->lux_thr_lo = (1000000 / st->scale_val2) * CM_THRESHOLD_LUX_DFLT;
-       st->lux_thr_hi = (1000000 / st->scale_val2) * CM_THRESHOLD_LUX_DFLT;
-       st->it_i_lo = 0;
-       st->it_i_hi = ARRAY_SIZE(cm_it_tbl);
+       memcpy(&st->cfg, &cm_cfg_dflt, sizeof(st->cfg));
+       st->light.cfg = &st->cfg;
+       st->light.hw_mask = 0xFFFF;
+       st->light.nld_tbl = cm_nld_tbl;
        /* device tree parameters */
-       if (client->dev.of_node) {
+       if (dn) {
+               /* common NVS IIO programmable parameters */
+               st->iio_ts_en = of_property_read_bool(dn, "iio_timestamps");
                /* device specific parameters */
                of_property_read_u16(dn, "als_sm", &als_sm);
                of_property_read_u16(dn, "als_pers", &als_pers);
                of_property_read_u16(dn, "als_psm", &st->als_psm);
-               /* common NVS programmable parameters */
-               st->iio_ts_en = of_property_read_bool(dn, "iio_timestamps");
-               of_property_read_u32(dn, "report_count", &st->report_n);
-               /* common NVS ALS programmable parameters */
-               of_property_read_s32(dn, "light_uncalibrated_lo",
-                                    &st->lux_uc_lo);
-               of_property_read_s32(dn, "light_uncalibrated_hi",
-                                    &st->lux_uc_hi);
-               of_property_read_s32(dn, "light_calibrated_lo", &st->lux_c_lo);
-               of_property_read_s32(dn, "light_calibrated_hi", &st->lux_c_hi);
-               of_property_read_s32(dn, "light_scale_val", &st->scale_val);
-               of_property_read_s32(dn, "light_scale_val2", &st->scale_val2);
-               of_property_read_s32(dn, "light_offset_val", &st->offset_val);
-               of_property_read_s32(dn, "light_offset_val2",
-                                    &st->offset_val2);
-               of_property_read_u32(dn, "light_threshold_lo",
-                                    &st->lux_thr_lo);
-               of_property_read_u32(dn, "light_threshold_hi",
-                                    &st->lux_thr_hi);
-               /* this device supports these programmable parameters */
-               if (!of_property_read_u32(dn, "light_integration_time_ms_lo",
-                                        &val)) {
-                       for (i = ARRAY_SIZE(cm_it_tbl); i > 1; i--) {
-                               if (val <= cm_it_tbl[i - 1].ms)
-                                       break;
-                       }
-                       st->it_i_hi = i;
-               }
-               if (!of_property_read_u32(dn, "light_integration_time_ms_hi",
-                                        &val)) {
-                       for (i = 0; i < ARRAY_SIZE(cm_it_tbl) - 1; i++) {
-                               if (val >= cm_it_tbl[i].ms)
-                                       break;
-                       }
-                       st->it_i_lo = i;
-               }
-               if (st->it_i_hi < st->it_i_lo) {
-                       dev_err(&client->dev,
-                               "%s light_integration_time_ms_ ERR\n",
-                               __func__);
-                       st->it_i_lo = 0;
-                       st->it_i_hi = ARRAY_SIZE(cm_it_tbl);
-               }
        }
-       if (!st->report_n)
-               st->report_n = CM_REPORT_N;
+       /* common NVS parameters */
+       nvs_of_dt(dn, &st->cfg, NULL);
+       /* this device supports these programmable parameters */
+       if (nvs_light_of_dt(&st->light, dn, NULL)) {
+               st->light.nld_i_lo = 0;
+               st->light.nld_i_hi = ARRAY_SIZE(cm_nld_tbl) - 1;
+       }
        st->als_psm &= CM_REG_PSM_MASK;
-       if (st->als_psm & (1 << CM_REG_PSM_EN))
-               st->psm_ms = cm_psm_ms_tbl[st->als_psm >> 1];
-       else
-               st->psm_ms = 0;
        st->als_cfg = als_pers << CM_REG_CFG_ALS_PERS;
        st->als_cfg |= als_sm << CM_REG_CFG_ALS_SM;
        return 0;
@@ -1493,29 +626,27 @@ static int cm_of_dt(struct i2c_client *client, struct cm_state *st)
 static int cm_probe(struct i2c_client *client,
                    const struct i2c_device_id *id)
 {
-       struct iio_dev *indio_dev;
        struct cm_state *st;
        int ret;
 
        dev_info(&client->dev, "%s\n", __func__);
-       indio_dev = iio_device_alloc(sizeof(*st));
-       if (indio_dev == NULL) {
-               dev_err(&client->dev, "%s iio_device_alloc ERR\n", __func__);
+       st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
+       if (st == NULL) {
+               dev_err(&client->dev, "%s devm_kzalloc ERR\n", __func__);
                return -ENOMEM;
        }
 
-       st = iio_priv(indio_dev);
+       i2c_set_clientdata(client, st);
        st->i2c = client;
-       i2c_set_clientdata(client, indio_dev);
-       ret = cm_of_dt(client, st);
+       ret = cm_of_dt(st, client->dev.of_node);
        if (ret) {
                dev_err(&client->dev, "%s _of_dt ERR\n", __func__);
                ret = -ENODEV;
-               goto cm_probe_err;
+               goto cm_probe_exit;
        }
 
        cm_pm_init(st);
-       ret = cm_id_i2c(indio_dev, id->name);
+       ret = cm_id_i2c(st, id->name);
        if (ret) {
                dev_err(&client->dev, "%s _id_i2c ERR\n", __func__);
                ret = -ENODEV;
@@ -1523,30 +654,25 @@ static int cm_probe(struct i2c_client *client,
        }
 
        cm_pm(st, false);
-       indio_dev->buffer = iio_kfifo_allocate(indio_dev);
-       if (!indio_dev->buffer) {
-               dev_err(&client->dev, "%s iio_kfifo_allocate ERR\n", __func__);
-               ret = -ENOMEM;
-               goto cm_probe_err;
+       cm_fn_dev.errs = &st->errs;
+       cm_fn_dev.sts = &st->sts;
+       st->nvs = nvs_iio();
+       if (st->nvs == NULL) {
+               dev_err(&client->dev, "%s nvs_iio ERR\n", __func__);
+               ret = -ENODEV;
+               goto cm_probe_exit;
        }
 
-       indio_dev->buffer->scan_timestamp = true;
-       indio_dev->modes = INDIO_DIRECT_MODE;
-       indio_dev->currentmode = INDIO_DIRECT_MODE;
-       indio_dev->dev.parent = &client->dev;
-       indio_dev->channels = cm_channels;
-       indio_dev->num_channels = ARRAY_SIZE(cm_channels);
-       indio_dev->name = CM_NAME;
-       indio_dev->info = &cm_iio_info;
-       indio_dev->setup_ops = &cm_buffer_setup_ops;
-       ret = iio_buffer_register(indio_dev, indio_dev->channels,
-                                 indio_dev->num_channels);
+       ret = st->nvs->probe(&st->nvs_data, st, &client->dev,
+                            &cm_fn_dev, &st->cfg);
        if (ret) {
-               dev_err(&client->dev, "%s iio_buffer_register ERR\n",
-                       __func__);
-               goto cm_probe_err;
+               dev_err(&client->dev, "%s nvs_probe ERR\n", __func__);
+               ret = -ENODEV;
+               goto cm_probe_exit;
        }
 
+       st->light.nvs_data = st->nvs_data;
+       st->light.handler = st->nvs->handler;
        INIT_DELAYED_WORK(&st->dw, cm_work);
        if (client->irq) {
                ret = request_threaded_irq(client->irq, NULL, cm_irq_thread,
@@ -1556,43 +682,13 @@ static int cm_probe(struct i2c_client *client,
                        dev_err(&client->dev, "%s req_threaded_irq ERR %d\n",
                                __func__, ret);
                        ret = -ENOMEM;
-                       goto cm_probe_err;
+                       goto cm_probe_exit;
                }
        }
 
-       st->trig = iio_trigger_alloc("%s-dev%d",
-                                    indio_dev->name, indio_dev->id);
-       if (st->trig == NULL) {
-               dev_err(&client->dev, "%s iio_allocate_trigger ERR\n",
-                       __func__);
-               ret = -ENOMEM;
-               goto cm_probe_err;
-       }
-
-       st->trig->dev.parent = &st->i2c->dev;
-       st->trig->ops = &cm_trigger_ops;
-       ret = iio_trigger_register(st->trig);
-       if (ret) {
-               dev_err(&client->dev, "%s iio_trigger_register ERR\n",
-                       __func__);
-               ret = -ENOMEM;
-               goto cm_probe_err;
-       }
-
-       indio_dev->trig = st->trig;
-       indio_dev->modes |= INDIO_BUFFER_TRIGGERED;
-       ret = iio_device_register(indio_dev);
-       if (ret) {
-               dev_err(&client->dev, "%s iio_device_register ERR\n",
-                       __func__);
-               goto cm_probe_err;
-       }
-
        dev_info(&client->dev, "%s done\n", __func__);
        return 0;
 
-cm_probe_err:
-       dev_err(&client->dev, "%s ERR %d\n", __func__, ret);
 cm_probe_exit:
        cm_remove(client);
        return ret;
diff --git a/drivers/iio/light/nvs_isl2902x.c b/drivers/iio/light/nvs_isl2902x.c
new file mode 100644 (file)
index 0000000..48f3b28
--- /dev/null
@@ -0,0 +1,958 @@
+/* Copyright (c) 2014, 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 */
+/* See nvs_iio.c and nvs.h for documentation */
+/* See nvs_light.c and nvs_light.h for documentation */
+/* See nvs_proximity.c and nvs_proximity.h for documentation */
+
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/ktime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/nvs.h>
+#include <linux/nvs_light.h>
+#include <linux/nvs_proximity.h>
+
+
+#define ISL_VENDOR                     "InterSil"
+#define ISL_NAME                       "isl2902x"
+#define ISL_NAME_ISL29028              "isl29028"
+#define ISL_NAME_ISL29029              "isl29029"
+#define ISL_DEVID_ISL29028             (0x28)
+#define ISL_DEVID_ISL29029             (0x29)
+#define ISL_HW_DELAY_MS                        (1)
+#define ISL_POLL_DLY_MS_DFLT           (2000)
+#define ISL_POLL_DLY_MS_MIN            (100)
+#define ISL_POLL_DLY_MS_MAX            (60000)
+#define ISL_CFG_DFLT                   (0x00)
+#define ISL_INT_DFLT                   (0x00)
+/* light defines */
+#define ISL_LIGHT_THRESHOLD_DFLT       (50)
+#define ISL_LIGHT_VERSION              (1)
+#define ISL_LIGHT_MAX_RANGE_IVAL       (14323)
+#define ISL_LIGHT_MAX_RANGE_MICRO      (0)
+#define ISL_LIGHT_RESOLUTION_IVAL      (0)
+#define ISL_LIGHT_RESOLUTION_MICRO     (14000)
+#define ISL_LIGHT_MILLIAMP_IVAL                (0)
+#define ISL_LIGHT_MILLIAMP_MICRO       (96000)
+#define ISL_LIGHT_SCALE_IVAL           (0)
+#define ISL_LIGHT_SCALE_MICRO          (1000)
+/* proximity defines */
+#define ISL_PROX_THRESHOLD_LO          (20)
+#define ISL_PROX_THRESHOLD_HI          (100)
+#define ISL_PROX_VERSION               (1)
+/* setting max_range and resolution to 1.0 = binary proximity */
+#define ISL_PROX_MAX_RANGE_IVAL                (1)
+#define ISL_PROX_MAX_RANGE_MICRO       (0)
+#define ISL_PROX_RESOLUTION_IVAL       (1)
+#define ISL_PROX_RESOLUTION_MICRO      (0)
+#define ISL_PROX_MILLIAMP_IVAL         (0)
+#define ISL_PROX_MILLIAMP_MICRO                (80000)
+#define ISL_PROX_SCALE_IVAL            (0)
+#define ISL_PROX_SCALE_MICRO           (0)
+/* HW registers */
+#define ISL_REG_CFG                    (0x01)
+#define ISL_REG_CFG_POR                        (0x00)
+#define ISL_REG_CFG_ALSIR_MODE         (0)
+#define ISL_REG_CFG_ALS_RANGE          (1)
+#define ISL_REG_CFG_ALS_EN             (2)
+#define ISL_REG_CFG_PROX_DR            (3)
+#define ISL_REG_CFG_PROX_SLP           (4)
+#define ISL_REG_CFG_PROX_EN            (7)
+#define ISL_REG_INT                    (0x02)
+#define ISL_REG_INT_INT_CTRL           (0)
+#define ISL_REG_INT_ALS_PRST           (1)
+#define ISL_REG_INT_ALS_FLAG           (3)
+#define ISL_REG_INT_PROX_PRST          (5)
+#define ISL_REG_INT_PROX_FLAG          (7)
+#define ISL_REG_PROX_LT                        (0x03)
+#define ISL_REG_PROX_HT                        (0x04)
+#define ISL_REG_ALSIR_TH1              (0x05)
+#define ISL_REG_ALSIR_TH2              (0x06)
+#define ISL_REG_ALSIR_TH2_POR          (0xF0) /* used to ID device */
+#define ISL_REG_ALSIR_TH3              (0x07)
+#define ISL_REG_PROX_DATA              (0x08)
+#define ISL_REG_ALSIR_DT1              (0x09)
+#define ISL_REG_ALSIR_DT2              (0x0A)
+#define ISL_REG_TEST1                  (0x0E)
+#define ISL_REG_TEST2                  (0x0F)
+/* devices */
+#define ISL_DEV_LIGHT                  (0)
+#define ISL_DEV_PROX                   (1)
+#define ISL_DEV_N                      (2)
+
+
+/* regulator names in order of powering on */
+static char *isl_vregs[] = {
+       "vdd",
+};
+
+static unsigned short isl_i2c_addrs[] = {
+       0x44,
+       0x45,
+};
+
+static struct nvs_light_dynamic isl_nld_tbl[] = {
+       {{0, 32600},  {1334, 970000}, {0, 96000}, 112, 0x00},
+       {{0, 522000}, {2137, 590000}, {0, 96000}, 112, 0x02}
+};
+
+struct isl_state {
+       struct i2c_client *i2c;
+       struct nvs_fn_if *nvs;
+       void *nvs_data[ISL_DEV_N];
+       struct sensor_cfg cfg[ISL_DEV_N];
+       struct delayed_work dw;
+       struct regulator_bulk_data vreg[ARRAY_SIZE(isl_vregs)];
+       struct nvs_light light;
+       struct nvs_proximity prox;
+       unsigned int dev[ISL_DEV_N];
+       unsigned int snsr_n;            /* number of sensors */
+       unsigned int sts;               /* status flags */
+       unsigned int errs;              /* error count */
+       unsigned int enabled;           /* enable status */
+       bool iio_ts_en;                 /* use IIO timestamps */
+       bool irq_dis;                   /* interrupt host disable flag */
+       u16 i2c_addr;                   /* I2C address */
+       u8 dev_id;                      /* device ID */
+       u8 reg_cfg;                     /* configuration register default */
+       u8 reg_int;                     /* interrupt register default */
+       u8 rc_cfg;                      /* cache of main configuration */
+};
+
+
+static s64 isl_get_time_ns(struct isl_state *st)
+{
+       struct timespec ts;
+
+       if (st->iio_ts_en)
+               return iio_get_time_ns();
+
+       ktime_get_ts(&ts);
+       return timespec_to_ns(&ts);
+}
+
+static void isl_err(struct isl_state *st)
+{
+       st->errs++;
+       if (!st->errs)
+               st->errs--;
+}
+
+static int isl_i2c_read(struct isl_state *st, u8 reg, u16 len, u8 *val)
+{
+       struct i2c_msg msg[2];
+
+       msg[0].addr = st->i2c_addr;
+       msg[0].flags = 0;
+       msg[0].len = 1;
+       msg[0].buf = &reg;
+       msg[1].addr = st->i2c_addr;
+       msg[1].flags = I2C_M_RD;
+       msg[1].len = len;
+       msg[1].buf = val;
+       if (i2c_transfer(st->i2c->adapter, msg, 2) != 2) {
+               isl_err(st);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static int isl_i2c_rd(struct isl_state *st, u8 reg, u8 *val)
+{
+       return isl_i2c_read(st, reg, 1, val);
+}
+
+static int isl_i2c_write(struct isl_state *st, u16 len, u8 *buf)
+{
+       struct i2c_msg msg;
+       int ret = -ENODEV;
+
+       if (st->i2c_addr) {
+               msg.addr = st->i2c_addr;
+               msg.flags = 0;
+               msg.len = len;
+               msg.buf = buf;
+               if (i2c_transfer(st->i2c->adapter, &msg, 1) == 1) {
+                       ret = 0;
+               } else {
+                       isl_err(st);
+                       ret = -EIO;
+               }
+       }
+
+       return ret;
+}
+
+static int isl_i2c_wr(struct isl_state *st, u8 reg, u8 val)
+{
+       u8 buf[2];
+
+       buf[0] = reg;
+       buf[1] = val;
+       return isl_i2c_write(st, sizeof(buf), buf);
+}
+
+static int isl_reset_sw(struct isl_state *st)
+{
+       /* Note that the SW reset doesn't set registers to a POR state */
+       int ret;
+
+       ret = isl_i2c_wr(st, ISL_REG_CFG, 0);
+       if (ret)
+               return ret;
+       else
+               st->rc_cfg = 0;
+
+       ret |= isl_i2c_wr(st, ISL_REG_TEST2, 0x29);
+       ret |= isl_i2c_wr(st, ISL_REG_TEST1, 0);
+       ret |= isl_i2c_wr(st, ISL_REG_TEST2, 0);
+       if (!ret) {
+               mdelay(ISL_HW_DELAY_MS);
+               st->rc_cfg = ISL_REG_CFG_POR;
+       }
+       return ret;
+}
+
+static int isl_pm(struct isl_state *st, bool enable)
+{
+       int ret = 0;
+
+       if (enable) {
+               nvs_vregs_enable(&st->i2c->dev, st->vreg,
+                                ARRAY_SIZE(isl_vregs));
+               if (ret)
+                       mdelay(ISL_HW_DELAY_MS);
+               ret = isl_reset_sw(st);
+       } else {
+               ret = nvs_vregs_sts(st->vreg, ARRAY_SIZE(isl_vregs));
+               if ((ret < 0) || (ret == ARRAY_SIZE(isl_vregs))) {
+                       ret = isl_i2c_wr(st, ISL_REG_CFG, 0);
+               } else if (ret > 0) {
+                       nvs_vregs_enable(&st->i2c->dev, st->vreg,
+                                        ARRAY_SIZE(isl_vregs));
+                       mdelay(ISL_HW_DELAY_MS);
+                       ret = isl_i2c_wr(st, ISL_REG_CFG, 0);
+               }
+               ret |= nvs_vregs_disable(&st->i2c->dev, st->vreg,
+                                        ARRAY_SIZE(isl_vregs));
+       }
+       if (ret > 0)
+               ret = 0;
+       if (ret) {
+               dev_err(&st->i2c->dev, "%s pwr=%x ERR=%d\n",
+                       __func__, enable, ret);
+       } else {
+               if (st->sts & NVS_STS_SPEW_MSG)
+                       dev_info(&st->i2c->dev, "%s pwr=%x\n",
+                                __func__, enable);
+       }
+       return ret;
+}
+
+static void isl_pm_exit(struct isl_state *st)
+{
+       isl_pm(st, false);
+       nvs_vregs_exit(&st->i2c->dev, st->vreg, ARRAY_SIZE(isl_vregs));
+}
+
+static int isl_pm_init(struct isl_state *st)
+{
+       int ret;
+
+       st->enabled = 0;
+       nvs_vregs_init(&st->i2c->dev,
+                      st->vreg, ARRAY_SIZE(isl_vregs), isl_vregs);
+       ret = isl_pm(st, true);
+       return ret;
+}
+
+static void isl_disable_irq(struct isl_state *st)
+{
+       if (!st->irq_dis) {
+               disable_irq_nosync(st->i2c->irq);
+               st->irq_dis = true;
+               if (st->sts & NVS_STS_SPEW_MSG)
+                       dev_info(&st->i2c->dev, "%s IRQ disabled\n", __func__);
+       }
+}
+
+static void isl_enable_irq(struct isl_state *st)
+{
+       if (st->irq_dis) {
+               enable_irq(st->i2c->irq);
+               st->irq_dis = false;
+               if (st->sts & NVS_STS_SPEW_MSG)
+                       dev_info(&st->i2c->dev, "%s IRQ enabled\n", __func__);
+       }
+}
+
+static int isl_cmd_wr(struct isl_state *st, unsigned int enable, bool irq_en)
+{
+       u8 reg_cfg = st->reg_cfg;
+       int ret;
+       int ret_t = 0;
+
+       if (st->i2c->irq && !irq_en) {
+               isl_disable_irq(st);
+               /* clear possible IRQ */
+               ret_t = isl_i2c_wr(st, ISL_REG_INT, st->reg_int);
+       }
+       if (enable & (1 << ISL_DEV_LIGHT))
+               reg_cfg |= (1 << ISL_REG_CFG_ALS_EN);
+       if (enable & (1 << ISL_DEV_PROX))
+               reg_cfg |= (1 << ISL_REG_CFG_PROX_EN);
+       if (reg_cfg != st->rc_cfg) {
+               ret = isl_i2c_wr(st, ISL_REG_CFG, reg_cfg);
+               if (ret)
+                       ret_t |= ret;
+               else
+                       st->rc_cfg = reg_cfg;
+               if (st->sts & NVS_STS_SPEW_MSG)
+                       dev_info(&st->i2c->dev, "%s reg_cfg=%hhx err=%d\n",
+                                __func__, reg_cfg, ret);
+       }
+       if (st->i2c->irq && irq_en) {
+               /* clear possible IRQ */
+               ret_t |= isl_i2c_wr(st, ISL_REG_INT, st->reg_int);
+               if (!ret_t)
+                       ret_t = 1; /* flag IRQ enabled */
+               isl_enable_irq(st);
+       }
+       return ret_t;
+}
+
+static int isl_thr_wr(struct isl_state *st, bool als, u16 thr_lo, u16 thr_hi)
+{
+       u8 buf[4];
+       u16 len;
+       u16 thr_le;
+       int ret = 0;
+
+       if (st->i2c->irq) {
+               if (als) {
+                       buf[0] = ISL_REG_ALSIR_TH1;
+                       thr_le = cpu_to_le16(thr_lo);
+                       buf[1] = thr_le & 0xFF;
+                       buf[2] = (thr_le >> 8) & 0x0F;
+                       thr_le = cpu_to_le16(thr_hi);
+                       buf[2] |= thr_le << 4;
+                       buf[3] = thr_le >> 4;
+                       len = 4;
+               } else {
+                       buf[0] = ISL_REG_PROX_LT;
+                       buf[1] = thr_lo;
+                       buf[2] = thr_hi;
+                       len = 3;
+               }
+               ret = isl_i2c_write(st, len, buf);
+               if (st->sts & NVS_STS_SPEW_MSG)
+                       dev_info(&st->i2c->dev,
+                                "%s reg=%hhx lo=%hd hi=%hd ret=%d\n",
+                                __func__, buf[0], thr_lo, thr_hi, ret);
+       }
+       return ret;
+}
+
+static int isl_rd_light(struct isl_state *st, s64 ts)
+{
+       u16 hw;
+       int ret;
+
+       ret = isl_i2c_read(st, ISL_REG_ALSIR_DT1, 2, (u8 *)&hw);
+       if (ret)
+               return ret;
+
+       hw = le16_to_cpu(hw);
+       if (st->sts & NVS_STS_SPEW_DATA)
+               dev_info(&st->i2c->dev,
+                        "poll light hw %hu %lld  diff=%d %lldns  index=%u\n",
+                        hw, ts, hw - st->light.hw, ts - st->light.timestamp,
+                        st->light.nld_i);
+       st->light.hw = hw;
+       st->light.timestamp = ts;
+       ret = nvs_light_read(&st->light);
+       if (ret < RET_HW_UPDATE)
+               /* either poll or nothing to do */
+               return ret;
+
+       ret = isl_thr_wr(st, true,
+                        st->light.hw_thresh_lo, st->light.hw_thresh_hi);
+       return ret;
+}
+
+static int isl_rd_prox(struct isl_state *st, s64 ts)
+{
+       u8 hw;
+       int ret;
+
+       ret = isl_i2c_rd(st, ISL_REG_PROX_DATA, &hw);
+       if (ret)
+               return ret;
+
+       if (st->sts & NVS_STS_SPEW_DATA)
+               dev_info(&st->i2c->dev,
+                        "poll proximity hw %hu %lld  diff=%d %lldns\n",
+                        hw, ts, hw - st->prox.hw, ts - st->prox.timestamp);
+       st->prox.hw = hw;
+       st->prox.timestamp = ts;
+       ret = nvs_proximity_read(&st->prox);
+       if (ret < RET_HW_UPDATE)
+               /* either poll or nothing to do */
+               return ret;
+
+       ret = isl_thr_wr(st, false,
+                        st->prox.hw_thresh_lo, st->prox.hw_thresh_hi);
+       return ret;
+}
+
+static int isl_en(struct isl_state *st, unsigned int enable)
+{
+       if (enable & (1 << ISL_DEV_LIGHT))
+               nvs_light_enable(&st->light);
+       if (enable & (1 << ISL_DEV_PROX))
+               nvs_proximity_enable(&st->prox);
+       return isl_cmd_wr(st, enable, false);
+}
+
+static int isl_rd(struct isl_state *st)
+{
+       s64 ts;
+       int ret = 0;
+
+       ts = isl_get_time_ns(st);
+       if (st->enabled & (1 << ISL_DEV_PROX))
+               ret |= isl_rd_prox(st, ts);
+       if (st->enabled & (1 << ISL_DEV_LIGHT))
+               ret |= isl_rd_light(st, ts);
+       if (ret < 0)
+               /* poll if error or more reporting */
+               ret = isl_cmd_wr(st, st->enabled, false);
+       else
+               ret = isl_cmd_wr(st, st->enabled, true);
+       return ret;
+}
+
+static unsigned int isl_polldelay(struct isl_state *st)
+{
+       unsigned int poll_delay_ms = ISL_POLL_DLY_MS_DFLT;
+
+       if (st->enabled & (1 << ISL_DEV_LIGHT))
+               poll_delay_ms = st->light.poll_delay_ms;
+       if (st->enabled & (1 << ISL_DEV_PROX)) {
+               if (poll_delay_ms > st->prox.poll_delay_ms)
+                       poll_delay_ms = st->prox.poll_delay_ms;
+       }
+       return poll_delay_ms;
+}
+
+static void isl_read(struct isl_state *st)
+{
+       unsigned int i;
+       int ret;
+
+       for (i = 0; i < st->snsr_n; i++)
+               st->nvs->mutex_lock(st->nvs_data[i]);
+       if (st->enabled) {
+               ret = isl_rd(st);
+               if (ret < 1)
+                       schedule_delayed_work(&st->dw,
+                                         msecs_to_jiffies(isl_polldelay(st)));
+       }
+       for (i = 0; i < st->snsr_n; i++)
+               st->nvs->mutex_unlock(st->nvs_data[i]);
+}
+
+static void isl_work(struct work_struct *ws)
+{
+       struct isl_state *st = container_of((struct delayed_work *)ws,
+                                           struct isl_state, dw);
+
+       isl_read(st);
+}
+
+static irqreturn_t isl_irq_thread(int irq, void *dev_id)
+{
+       struct isl_state *st = (struct isl_state *)dev_id;
+
+       if (st->sts & NVS_STS_SPEW_IRQ)
+               dev_info(&st->i2c->dev, "%s\n", __func__);
+       isl_read(st);
+       return IRQ_HANDLED;
+}
+
+static int isl_disable(struct isl_state *st, int snsr_id)
+{
+       bool disable = true;
+       int ret = 0;
+
+       if (snsr_id >= 0) {
+               if (st->enabled & ~(1 << snsr_id)) {
+                       st->enabled &= ~(1 << snsr_id);
+                       disable = false;
+               }
+       }
+       if (disable) {
+               if (st->i2c->irq)
+                       isl_disable_irq(st);
+               cancel_delayed_work(&st->dw);
+               ret |= isl_pm(st, false);
+               if (!ret)
+                       st->enabled = 0;
+       }
+       return ret;
+}
+
+static int isl_enable(void *client, int snsr_id, int enable)
+{
+       struct isl_state *st = (struct isl_state *)client;
+       int ret;
+
+       if (enable < 0)
+               return st->enabled & (1 << snsr_id);
+
+       if (enable) {
+               enable = st->enabled | (1 << snsr_id);
+               ret = isl_pm(st, true);
+               if (!ret) {
+                       ret = isl_en(st, enable);
+                       if (ret < 0) {
+                               isl_disable(st, snsr_id);
+                       } else {
+                               st->enabled = enable;
+                               schedule_delayed_work(&st->dw,
+                                       msecs_to_jiffies(ISL_POLL_DLY_MS_MIN));
+                       }
+               }
+       } else {
+               ret = isl_disable(st, snsr_id);
+       }
+       return ret;
+}
+
+static int isl_batch(void *client, int snsr_id, int flags,
+                    unsigned int period, unsigned int timeout)
+{
+       struct isl_state *st = (struct isl_state *)client;
+
+       if (timeout)
+               /* timeout not supported (no HW FIFO) */
+               return -EINVAL;
+
+       if (snsr_id == ISL_DEV_LIGHT)
+               st->light.delay_us = period;
+       else if (snsr_id == ISL_DEV_PROX)
+               st->prox.delay_us = period;
+       return 0;
+}
+
+static int isl_regs(void *client, int snsr_id, char *buf)
+{
+       struct isl_state *st = (struct isl_state *)client;
+       ssize_t t;
+       u8 val;
+       u8 i;
+       int ret;
+
+       t = sprintf(buf, "registers:\n");
+       for (i = 0; i <= ISL_REG_TEST2; i++) {
+               ret = isl_i2c_rd(st, i, &val);
+               if (!ret)
+                       t += sprintf(buf + t, "0x%hhx=0x%hhx\n",
+                                    i, val);
+       }
+       return t;
+}
+
+static struct nvs_fn_dev isl_fn_dev = {
+       .enable                         = isl_enable,
+       .batch                          = isl_batch,
+       .regs                           = isl_regs,
+};
+
+static int isl_suspend(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct isl_state *st = i2c_get_clientdata(client);
+       unsigned int i;
+       int ret = 0;
+
+       st->sts |= NVS_STS_SUSPEND;
+       for (i = 0; i < st->snsr_n; i++) {
+               if (st->nvs && st->nvs_data[i])
+                       ret |= st->nvs->suspend(st->nvs_data[i]);
+       }
+       if (st->sts & NVS_STS_SPEW_MSG)
+               dev_info(&client->dev, "%s\n", __func__);
+       return ret;
+}
+
+static int isl_resume(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct isl_state *st = i2c_get_clientdata(client);
+       unsigned int i;
+       int ret = 0;
+
+       for (i = 0; i < st->snsr_n; i++) {
+               if (st->nvs && st->nvs_data[i])
+                       ret |= st->nvs->resume(st->nvs_data[i]);
+       }
+       st->sts &= ~NVS_STS_SUSPEND;
+       if (st->sts & NVS_STS_SPEW_MSG)
+               dev_info(&client->dev, "%s\n", __func__);
+       return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(isl_pm_ops, isl_suspend, isl_resume);
+
+static void isl_shutdown(struct i2c_client *client)
+{
+       struct isl_state *st = i2c_get_clientdata(client);
+       unsigned int i;
+
+       st->sts |= NVS_STS_SHUTDOWN;
+       for (i = 0; i < st->snsr_n; i++) {
+               if (st->nvs && st->nvs_data[i])
+                       st->nvs->shutdown(st->nvs_data[i]);
+       }
+       if (st->sts & NVS_STS_SPEW_MSG)
+               dev_info(&client->dev, "%s\n", __func__);
+}
+
+static int isl_remove(struct i2c_client *client)
+{
+       struct isl_state *st = i2c_get_clientdata(client);
+       unsigned int i;
+
+       if (st != NULL) {
+               isl_shutdown(client);
+               if (st->nvs) {
+                       for (i = 0; i < st->snsr_n; i++) {
+                               if (st->nvs_data[i])
+                                       st->nvs->remove(st->nvs_data[i]);
+                       }
+               }
+               if (st->dw.wq)
+                       destroy_workqueue(st->dw.wq);
+               isl_pm_exit(st);
+       }
+       dev_info(&client->dev, "%s\n", __func__);
+       return 0;
+}
+
+static void isl_id_part(struct isl_state *st, const char *part)
+{
+       unsigned int i;
+
+       for (i = 0; i < ISL_DEV_N; i++)
+               st->cfg[i].part = part;
+}
+
+static int isl_id_dev(struct isl_state *st, const char *name)
+{
+       u8 val;
+       int ret = 0;
+
+       if (!strcmp(name, ISL_NAME_ISL29029)) {
+               st->dev_id = ISL_DEVID_ISL29029;
+               isl_id_part(st, ISL_NAME_ISL29029);
+       } else if (!strcmp(name, ISL_NAME_ISL29028)) {
+               st->dev_id = ISL_DEVID_ISL29028;
+               isl_id_part(st, ISL_NAME_ISL29028);
+       } else if (!strcmp(name, ISL_NAME)) {
+               /* There is no way to auto-detect the device since the
+                * register space is exactly the same. We'll just confirm
+                * that our device exists and default to the ISL29029.
+                */
+               st->dev_id = ISL_DEVID_ISL29029;
+               isl_id_part(st, ISL_NAME_ISL29029);
+               ret = isl_reset_sw(st);
+               ret |= isl_i2c_rd(st, ISL_REG_ALSIR_TH2, &val);
+               if (ret)
+                       return -ENODEV;
+
+               /* There is no way to confirm the device because it has a
+                * memory "feature" when off and the SW reset doesn't set
+                * registers to the POR state.  All we can do is confirm an
+                * I2C response.
+                * if (val != ISL_REG_ALSIR_TH2_POR)
+                *      return -ENODEV;
+                */
+       }
+
+       return ret;
+}
+
+static int isl_id_i2c(struct isl_state *st, const char *name)
+{
+       int i;
+       int ret;
+
+       for (i = 0; i < ARRAY_SIZE(isl_i2c_addrs); i++) {
+               if (st->i2c->addr == isl_i2c_addrs[i])
+                       break;
+       }
+
+       if (i < ARRAY_SIZE(isl_i2c_addrs)) {
+               st->i2c_addr = st->i2c->addr;
+               ret = isl_id_dev(st, name);
+       } else {
+               name = ISL_NAME;
+               for (i = 0; i < ARRAY_SIZE(isl_i2c_addrs); i++) {
+                       st->i2c_addr = isl_i2c_addrs[i];
+                       ret = isl_id_dev(st, name);
+                       if (!ret)
+                               break;
+               }
+       }
+       if (ret)
+               st->i2c_addr = 0;
+       return ret;
+}
+
+struct sensor_cfg isl_cfg_dflt[] = {
+       {
+               .name                   = NVS_LIGHT_STRING,
+               .snsr_id                = ISL_DEV_LIGHT,
+               .ch_n                   = ARRAY_SIZE(iio_chan_spec_nvs_light),
+               .ch_inf                 = &iio_chan_spec_nvs_light,
+               .part                   = ISL_NAME,
+               .vendor                 = ISL_VENDOR,
+               .version                = ISL_LIGHT_VERSION,
+               .max_range              = {
+                       .ival           = ISL_LIGHT_MAX_RANGE_IVAL,
+                       .fval           = ISL_LIGHT_MAX_RANGE_MICRO,
+               },
+               .resolution             = {
+                       .ival           = ISL_LIGHT_RESOLUTION_IVAL,
+                       .fval           = ISL_LIGHT_RESOLUTION_MICRO,
+               },
+               .milliamp               = {
+                       .ival           = ISL_LIGHT_MILLIAMP_IVAL,
+                       .fval           = ISL_LIGHT_MILLIAMP_MICRO,
+               },
+               .delay_us_min           = ISL_POLL_DLY_MS_MIN * 1000,
+               .delay_us_max           = ISL_POLL_DLY_MS_MAX * 1000,
+               .scale                  = {
+                       .ival           = ISL_LIGHT_SCALE_IVAL,
+                       .fval           = ISL_LIGHT_SCALE_MICRO,
+               },
+               .thresh_lo              = ISL_LIGHT_THRESHOLD_DFLT,
+               .thresh_hi              = ISL_LIGHT_THRESHOLD_DFLT,
+       },
+       {
+               .name                   = NVS_PROXIMITY_STRING,
+               .snsr_id                = ISL_DEV_PROX,
+               .ch_n                = ARRAY_SIZE(iio_chan_spec_nvs_proximity),
+               .ch_inf                 = &iio_chan_spec_nvs_proximity,
+               .part                   = ISL_NAME,
+               .vendor                 = ISL_VENDOR,
+               .version                = ISL_PROX_VERSION,
+               .max_range              = {
+                       .ival           = ISL_PROX_MAX_RANGE_IVAL,
+                       .fval           = ISL_PROX_MAX_RANGE_MICRO,
+               },
+               .resolution             = {
+                       .ival           = ISL_PROX_RESOLUTION_IVAL,
+                       .fval           = ISL_PROX_RESOLUTION_MICRO,
+               },
+               .milliamp               = {
+                       .ival           = ISL_PROX_MILLIAMP_IVAL,
+                       .fval           = ISL_PROX_MILLIAMP_MICRO,
+               },
+               .delay_us_min           = ISL_POLL_DLY_MS_MIN * 1000,
+               .delay_us_max           = ISL_POLL_DLY_MS_MAX * 1000,
+               .scale                  = {
+                       .ival           = ISL_PROX_SCALE_IVAL,
+                       .fval           = ISL_PROX_SCALE_MICRO,
+               },
+               .thresh_lo              = ISL_PROX_THRESHOLD_LO,
+               .thresh_hi              = ISL_PROX_THRESHOLD_HI,
+       },
+};
+
+static int isl_of_dt(struct isl_state *st, struct device_node *dn)
+{
+       unsigned int i;
+
+       for (i = 0; i < ISL_DEV_N; i++)
+               memcpy(&st->cfg[i], &isl_cfg_dflt[i],
+                      sizeof(struct sensor_cfg));
+       st->light.hw_mask = 0x0FFF;
+       st->light.nld_tbl = isl_nld_tbl;
+       st->prox.hw_mask = 0x00FF;
+       /* default device specific parameters */
+       st->reg_cfg = ISL_CFG_DFLT;
+       st->reg_int = ISL_INT_DFLT;
+       /* device tree parameters */
+       if (dn) {
+               /* common NVS programmable parameters */
+               st->iio_ts_en = of_property_read_bool(dn, "iio_timestamps");
+               /* device specific parameters */
+               of_property_read_u8(dn, "configure_reg", &st->reg_cfg);
+               of_property_read_u8(dn, "interrupt_reg", &st->reg_int);
+       }
+       /* common NVS parameters */
+       for (i = 0; i < ISL_DEV_N; i++)
+               nvs_of_dt(dn, &st->cfg[i], NULL);
+       /* this device supports these programmable parameters */
+       if (nvs_light_of_dt(&st->light, dn, NULL)) {
+               st->light.nld_i_lo = 0;
+               st->light.nld_i_hi = ARRAY_SIZE(isl_nld_tbl) - 1;
+       }
+       i = st->light.nld_i_lo;
+       st->cfg[ISL_DEV_LIGHT].resolution.ival =
+                                               isl_nld_tbl[i].resolution.ival;
+       st->cfg[ISL_DEV_LIGHT].resolution.fval =
+                                               isl_nld_tbl[i].resolution.fval;
+       i = st->light.nld_i_hi;
+       st->cfg[ISL_DEV_LIGHT].max_range.ival = isl_nld_tbl[i].max_range.ival;
+       st->cfg[ISL_DEV_LIGHT].max_range.fval = isl_nld_tbl[i].max_range.fval;
+       st->cfg[ISL_DEV_LIGHT].delay_us_min =
+                                           isl_nld_tbl[i].delay_min_ms * 1000;
+       return 0;
+}
+
+static int isl_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+       struct isl_state *st;
+       unsigned long irqflags;
+       unsigned int n;
+       unsigned int i;
+       int ret;
+
+       dev_info(&client->dev, "%s\n", __func__);
+       st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
+       if (st == NULL) {
+               dev_err(&client->dev, "%s devm_kzalloc ERR\n", __func__);
+               return -ENOMEM;
+       }
+
+       i2c_set_clientdata(client, st);
+       st->i2c = client;
+       ret = isl_of_dt(st, client->dev.of_node);
+       if (ret) {
+               dev_err(&client->dev, "%s _of_dt ERR\n", __func__);
+               ret = -ENODEV;
+               goto isl_probe_exit;
+       }
+
+       isl_pm_init(st);
+       ret = isl_id_i2c(st, id->name);
+       if (ret) {
+               dev_err(&client->dev, "%s _id_i2c ERR\n", __func__);
+               ret = -ENODEV;
+               goto isl_probe_exit;
+       }
+
+       isl_pm(st, false);
+       isl_fn_dev.sts = &st->sts;
+       isl_fn_dev.errs = &st->errs;
+       st->nvs = nvs_iio();
+       if (st->nvs == NULL) {
+               ret = -ENODEV;
+               goto isl_probe_exit;
+       }
+
+       n = 0;
+       for (i = 0; i < ISL_DEV_N; i++) {
+               ret = st->nvs->probe(&st->nvs_data[n], st, &client->dev,
+                                    &isl_fn_dev, &st->cfg[i]);
+               if (!ret) {
+                       st->dev[n] = st->cfg[i].snsr_id;
+                       if (st->dev[n] == ISL_DEV_LIGHT) {
+                               st->light.cfg = &st->cfg[i];
+                               st->light.nvs_data = st->nvs_data[n];
+                               st->light.handler = st->nvs->handler;
+                       } else if (st->dev[n] == ISL_DEV_PROX) {
+                               st->prox.cfg = &st->cfg[i];
+                               st->prox.nvs_data = st->nvs_data[n];
+                               st->prox.handler = st->nvs->handler;
+                       }
+                       n++;
+               }
+       }
+       if (!n) {
+               dev_err(&client->dev, "%s nvs_probe ERR\n", __func__);
+               ret = -ENODEV;
+               goto isl_probe_exit;
+       }
+
+       st->snsr_n = n;
+       INIT_DELAYED_WORK(&st->dw, isl_work);
+       if (client->irq) {
+               irqflags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
+               irqflags |= IRQF_NO_SUSPEND; /* for proximity */
+               ret = request_threaded_irq(client->irq, NULL, isl_irq_thread,
+                                          irqflags, ISL_NAME, st);
+               if (ret) {
+                       dev_err(&client->dev, "%s req_threaded_irq ERR %d\n",
+                               __func__, ret);
+                       ret = -ENOMEM;
+                       goto isl_probe_exit;
+               }
+       }
+
+       dev_info(&client->dev, "%s done\n", __func__);
+       return 0;
+
+isl_probe_exit:
+       isl_remove(client);
+       return ret;
+}
+
+static const struct i2c_device_id isl_i2c_device_id[] = {
+       { ISL_NAME, 0 },
+       { ISL_NAME_ISL29028, 0 },
+       { ISL_NAME_ISL29029, 0 },
+       {}
+};
+
+MODULE_DEVICE_TABLE(i2c, isl_i2c_device_id);
+
+static const struct of_device_id isl_of_match[] = {
+       { .compatible = "intersil,isl2902x", },
+       { .compatible = "intersil,isl29028", },
+       { .compatible = "intersil,isl29029", },
+       {},
+};
+
+MODULE_DEVICE_TABLE(of, isl_of_match);
+
+static struct i2c_driver isl_driver = {
+       .class          = I2C_CLASS_HWMON,
+       .probe          = isl_probe,
+       .remove         = isl_remove,
+       .shutdown       = isl_shutdown,
+       .driver = {
+               .name           = ISL_NAME,
+               .owner          = THIS_MODULE,
+               .of_match_table = of_match_ptr(isl_of_match),
+               .pm             = &isl_pm_ops,
+       },
+       .id_table       = isl_i2c_device_id,
+};
+module_i2c_driver(isl_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ISL2902x driver");
+MODULE_AUTHOR("NVIDIA Corporation");
index 1281452..6bd7417 100644 (file)
  */
 
 /* 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 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.
- */
-/* NVS light/proximity drivers have two calibration mechanisms:
- * Method 1 (preferred):
- * This method uses interpolation and requires a low and high uncalibrated
- * value along with the corresponding low and high calibrated values.  The
- * uncalibrated values are what is read from the sensor in the steps below.
- * The corresponding calibrated values are what the correct value should be.
- * All values are programmed into the device tree settings.
- * 1. Read scale sysfs attribute.  This value will need to be written back.
- * 2. Disable device.
- * 3. Write 1 to the scale sysfs attribute.
- * 4. Enable device.
- * 5. The NVS HAL will announce in the log that calibration mode is enabled and
- *    display the data along with the scale and offset parameters applied.
- * 6. Write the scale value read in step 1 back to the scale sysfs attribute.
- * 7. Put the device into a state where the data read is a low value.
- * 8. Note the values displayed in the log.  Separately measure the actual
- *    value.  The value from the sensor will be the uncalibrated value and the
- *    separately measured value will be the calibrated value for the current
- *    state (low or high values).
- * 9. Put the device into a state where the data read is a high value.
- * 10. Repeat step 8.
- * 11. Enter the values in the device tree settings for the device.  Both
- *     calibrated and uncalibrated values will be the values before scale and
- *     offset are applied.
- *     For example, a light sensor has the following device tree parameters:
- *     light_uncalibrated_lo
- *     light_calibrated_lo
- *     light_uncalibrated_hi
- *     light_calibrated_hi
- *     The proximity sensor parameters are:
- *     proximity_uncalibrated_lo
- *     proximity_calibrated_lo
- *     proximity_uncalibrated_hi
- *     proximity_calibrated_hi
- *
- * Method 2:
- * 1. Disable device.
- * 2. Write 1 to the scale sysfs attribute.
- * 3. Enable device.
- * 4. The NVS HAL will announce in the log that calibration mode is enabled and
- *    display the data along with the scale and offset parameters applied.
- * 5. Write to scale and offset sysfs attributes as needed to get the data
- *    modified as desired.
- * 6. Disabling the device disables calibration mode.
- * 7. Set the new scale and offset parameters in the device tree:
- *    <IIO channel>_scale_val
- *    <IIO channel>_scale_val2
- *    <IIO channel>_offset_val
- *    <IIO channel>_offset_val2
- *    The values are in IIO IIO_VAL_INT_PLUS_MICRO format.
- */
+/* See nvs_iio.c and nvs.h for documentation */
+/* See nvs_light.c and nvs_light.h for documentation */
+
 
-/* This driver was developed without a part available so there are a few
- * TODOs:
- * - see if this driver works - start with st->hw_it = true
- * - dynamic integration time (hw integration time == false)
- * - dynamic range and resolution
- */
 #include <linux/i2c.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/regulator/consumer.h>
 #include <linux/workqueue.h>
 #include <linux/of.h>
-#include <linux/iio/iio.h>
-#include <linux/iio/sysfs.h>
-#include <linux/iio/buffer.h>
-#include <linux/iio/kfifo_buf.h>
-#include <linux/iio/trigger.h>
+#include <linux/nvs.h>
+#include <linux/nvs_light.h>
 
-#define JSA_VERSION_DRIVER             (101)
 #define JSA_VENDOR                     "SolteamOpto"
 #define JSA_NAME                       "jsa1127"
-/* setting _REPORT_N to 2 causes an extra reading after crossing the threshold
- * allowing a more accurate settled reported value.
- */
-#define JSA_REPORT_N                   (2)
 #define JSA_LIGHT_VERSION              (1)
+#define JSA_LIGHT_MAX_RANGE_IVAL       (109000)
 #define JSA_LIGHT_MAX_RANGE_MICRO      (0)
 #define JSA_LIGHT_RESOLUTION_IVAL      (0)
-#define JSA_LIGHT_RESOLUTION_MICRO     (10000)
+#define JSA_LIGHT_RESOLUTION_MICRO     (210000)
+#define JSA_LIGHT_MILLIAMP_IVAL                (0)
+#define JSA_LIGHT_MILLIAMP_MICRO       (90000)
 #define JSA_LIGHT_SCALE_IVAL           (0)
 #define JSA_LIGHT_SCALE_MICRO          (10000)
 #define JSA_LIGHT_OFFSET_IVAL          (0)
 #define JSA_LIGHT_OFFSET_MICRO         (0)
-#define JSA_LIGHT_MILLIAMP             "0.09"
-#define JSA_POLL_DELAY_MS_DFLT         (2000)
-#define JSA_THRESHOLD_LUX_DFLT         (5000) /* lux change outside 100 */
+#define JSA_LIGHT_THRESHOLD_LO         (100)
+#define JSA_LIGHT_THRESHOLD_HI         (100)
+#define JSA_POLL_DLY_MS_MIN            (800)
+#define JSA_POLL_DLY_MS_MAX            (4000)
 /* HW registers */
 #define JSA_CMD_SHUTDOWN               (0x80)
 #define JSA_CMD_EN_CONT                        (0x0C)
 #define JSA_CMD_MASK                   (0x3F)
 #define JSA_VAL_VALID                  (15)
 #define JSA_HW_DELAY_MS                        (60)
-/* _buf_push expects this scan order */
-#define JSA_SCAN_LIGHT                 0
-#define JSA_SCAN_TIMESTAMP             1
-/* debug flags */
-#define JSA_DBG_SPEW_MSG               (1 << 0)
-#define JSA_DBG_SPEW_LIGHT             (1 << 1)
-#define JSA_DBG_SPEW_LIGHT_POLL                (1 << 2)
-#define JSA_DBG_VAL_LIGHT              (1 << 3)
-
-enum JSA_ATTR {
-       JSA_ATTR_PART,
-       JSA_ATTR_VENDOR,
-       JSA_ATTR_VERSION,
-       JSA_ATTR_MILLIAMP,
-       JSA_ATTR_ENABLE,
-       JSA_ATTR_THRESH_LO,
-       JSA_ATTR_THRESH_HI,
-};
 
-enum JSA_INFO {
-       JSA_INFO_DATA = 0,
-       JSA_INFO_VER,
-       JSA_INFO_ERRS,
-       JSA_INFO_REGS,
-       JSA_INFO_DBG,
-       JSA_INFO_LIGHT_SPEW,
-       JSA_INFO_LIGHT_POLL_SPEW,
-       JSA_INFO_LIMIT_MAX,
-};
 
 /* regulator names in order of powering on */
 static char *jsa_vregs[] = {
@@ -183,58 +66,30 @@ static unsigned short jsa_i2c_addrs[] = {
        0x44,
 };
 
-struct jsa_it {                                /* integration time */
-       unsigned int ms;                /* time ms */
-       unsigned int resolution;        /* resolution */
-       unsigned int range;             /* range */
-};
-
-static struct jsa_it jsa_it_tbl[] = {  /* lux_lo    lux_hi */
-       { 800, 210000, 6500 },          /* 0.21 <-> 6500    */
-       { 400, 420000, 13000 },         /* 0.42 <-> 13000   */
-       { 300, 560000, 18000 },         /* 0.56 <-> 18000   */
-       { 200, 830000, 27000 },         /* 0.83 <-> 27000   */
-       { 100, 1670000, 54000 },        /* 1.67 <-> 54000   */
-       { 50, 3330000, 109000 }         /* 3.33 <-> 109000  */
-                                       /* note: lux_lo = resolution */
+static struct nvs_light_dynamic jsa_nld_tbl[] = {
+       {{0, 210000}, {6500,   0}, {0, 90000}, 800, 0},
+       {{0, 420000}, {13000,  0}, {0, 90000}, 400, 0},
+       {{0, 560000}, {18000,  0}, {0, 90000}, 300, 0},
+       {{0, 830000}, {27000,  0}, {0, 90000}, 200, 0},
+       {{1, 670000}, {54000,  0}, {0, 90000}, 100, 0},
+       {{3, 330000}, {109000, 0}, {0, 90000}, 50,  0}
 };
 
 struct jsa_state {
        struct i2c_client *i2c;
-       struct mutex mutex_client;
-       struct iio_trigger *trig;
+       struct nvs_fn_if *nvs;
+       void *nvs_data;
+       struct sensor_cfg cfg;
        struct delayed_work dw;
        struct regulator_bulk_data vreg[ARRAY_SIZE(jsa_vregs)];
-       unsigned int info;              /* info data to return */
-       unsigned int dbg;               /* debug flags */
+       struct nvs_light light;
+       unsigned int sts;               /* debug flags */
        unsigned int errs;              /* error count */
-       unsigned int enable;            /* enable status */
-       unsigned int poll_delay_ms;     /* requested sampling delay (ms) */
-       unsigned int queue_delay_ms;    /* workqueue delay time (ms) */
-       unsigned int scale_i;           /* index into HW IT settings table */
-       int scale_val;                  /* user scale val */
-       int scale_val2;                 /* user scale val2 */
-       int offset_val;                 /* user offset val */
-       int offset_val2;                /* user offset val2 */
-       int lux_uc_lo;                  /* interpolation x1 uncalibrated lo */
-       int lux_uc_hi;                  /* interpolation x3 uncalibrated hi */
-       int lux_c_lo;                   /* interpolation y1 calibrated lo */
-       int lux_c_hi;                   /* interpolation y3 calibrated hi */
-       unsigned int report;            /* used to report first valid sample */
-       unsigned int report_n;          /* this many on-change data reports */
-       unsigned int lux_thr_lo;        /* report when new lux below this */
-       unsigned int lux_thr_hi;        /* report when new lux above this */
-       unsigned int it_i_lo;           /* integration time index low limit */
-       unsigned int it_i_hi;           /* integration time index high limit */
-       unsigned long mult;             /* used to calc lux from HW values */
+       unsigned int enabled;           /* enable status */
        bool iio_ts_en;                 /* use IIO timestamps */
-       bool shutdown;                  /* shutdown active flag */
-       bool suspend;                   /* suspend active flag */
        bool hw_it;                     /* HW defined integration time */
        u16 i2c_addr;                   /* I2C address */
        u8 rc_cmd;                      /* store for register dump */
-       u32 light;                      /* sample data */
-       s64 ts;                         /* sample data timestamp */
 };
 
 
@@ -265,14 +120,12 @@ static int jsa_i2c_rd(struct jsa_state *st, u16 *val)
        msg.flags = I2C_M_RD;
        msg.len = 2;
        msg.buf = (__u8 *)val;
-       mutex_lock(&st->mutex_client);
        if (i2c_transfer(st->i2c->adapter, &msg, 1) == 1) {
                *val = le16_to_cpup(val);
        } else {
                jsa_err(st);
                ret = -EIO;
        }
-       mutex_unlock(&st->mutex_client);
        return ret;
 }
 
@@ -288,154 +141,40 @@ static int jsa_i2c_wr(struct jsa_state *st, u8 val)
                msg.flags = 0;
                msg.len = sizeof(buf);
                msg.buf = buf;
-               mutex_lock(&st->mutex_client);
                if (i2c_transfer(st->i2c->adapter, &msg, 1) == 1) {
                        st->rc_cmd = val;
                } else {
                        jsa_err(st);
                        ret = -EIO;
                }
-               mutex_unlock(&st->mutex_client);
-               if (st->dbg & JSA_DBG_SPEW_MSG)
+               if (st->sts & NVS_STS_SPEW_MSG)
                        dev_info(&st->i2c->dev, "%s=%hhx err=%d\n",
                                 __func__, val, ret);
        }
        return ret;
 }
 
-static int jsa_vreg_dis(struct jsa_state *st, unsigned int i)
-{
-       int ret = 0;
-
-       if (st->vreg[i].ret && (st->vreg[i].consumer != NULL)) {
-               ret = regulator_disable(st->vreg[i].consumer);
-               if (ret)
-                       dev_err(&st->i2c->dev, "%s %s ERR\n",
-                               __func__, st->vreg[i].supply);
-               else
-                       st->vreg[i].ret = 0;
-                       dev_dbg(&st->i2c->dev, "%s %s\n",
-                               __func__, st->vreg[i].supply);
-       }
-       return ret;
-}
-
-static int jsa_vreg_dis_all(struct jsa_state *st)
-{
-       unsigned int i;
-       int ret = 0;
-
-       for (i = ARRAY_SIZE(jsa_vregs); i > 0; i--)
-               ret |= jsa_vreg_dis(st, (i - 1));
-       return ret;
-}
-
-static int jsa_vreg_en(struct jsa_state *st, unsigned int i)
-{
-       int ret = 0;
-
-       if ((!st->vreg[i].ret) && (st->vreg[i].consumer != NULL)) {
-               ret = regulator_enable(st->vreg[i].consumer);
-               if (ret) {
-                       dev_err(&st->i2c->dev, "%s %s ERR\n",
-                               __func__, st->vreg[i].supply);
-               } else {
-                       st->vreg[i].ret = 1;
-                       dev_dbg(&st->i2c->dev, "%s %s\n",
-                               __func__, st->vreg[i].supply);
-                       ret = 1; /* flag regulator state change */
-               }
-       }
-       return ret;
-}
-
-static int jsa_vreg_en_all(struct jsa_state *st)
-{
-       unsigned int i;
-       int ret = 0;
-
-       for (i = 0; i < ARRAY_SIZE(jsa_vregs); i++)
-               ret |= jsa_vreg_en(st, i);
-       return ret;
-}
-
-static void jsa_vreg_exit(struct jsa_state *st)
-{
-       unsigned int i;
-
-       for (i = 0; i < ARRAY_SIZE(jsa_vregs); i++) {
-               if (st->vreg[i].consumer != NULL) {
-                       devm_regulator_put(st->vreg[i].consumer);
-                       st->vreg[i].consumer = NULL;
-                       dev_dbg(&st->i2c->dev, "%s %s\n",
-                               __func__, st->vreg[i].supply);
-               }
-       }
-}
-
-static int jsa_vreg_init(struct jsa_state *st)
-{
-       unsigned int i;
-       int ret = 0;
-
-       for (i = 0; i < ARRAY_SIZE(jsa_vregs); i++) {
-               st->vreg[i].supply = jsa_vregs[i];
-               st->vreg[i].ret = 0;
-               st->vreg[i].consumer = devm_regulator_get(&st->i2c->dev,
-                                                         st->vreg[i].supply);
-               if (IS_ERR(st->vreg[i].consumer)) {
-                       ret |= PTR_ERR(st->vreg[i].consumer);
-                       dev_err(&st->i2c->dev, "%s ret %d for %s\n",
-                               __func__, ret, st->vreg[i].supply);
-                       st->vreg[i].consumer = NULL;
-               } else {
-                       dev_dbg(&st->i2c->dev, "%s %s\n",
-                               __func__, st->vreg[i].supply);
-               }
-       }
-       return ret;
-}
-
-static int jsa_vreg_sts(struct jsa_state *st)
-{
-       unsigned int i;
-       int ret = 0;
-
-       for (i = 0; i < ARRAY_SIZE(jsa_vregs); i++) {
-               if (st->vreg[i].consumer != NULL)
-                       break;
-       }
-       if (i < ARRAY_SIZE(jsa_vregs)) {
-               /* ret == number of regulators on */
-               for (i = 0; i < ARRAY_SIZE(jsa_vregs); i++) {
-                       if (st->vreg[i].ret)
-                               ret++;
-               }
-       } else {
-               /* no regulator support (can assume always on) */
-               ret = -EINVAL;
-       }
-       return ret;
-}
-
 static int jsa_pm(struct jsa_state *st, bool enable)
 {
        int ret = 0;
 
        if (enable) {
-               ret = jsa_vreg_en_all(st);
+               ret = nvs_vregs_enable(&st->i2c->dev, st->vreg,
+                                      ARRAY_SIZE(jsa_vregs));
                if (ret)
                        mdelay(JSA_HW_DELAY_MS);
        } else {
-               ret = jsa_vreg_sts(st);
+               ret = nvs_vregs_sts(st->vreg, ARRAY_SIZE(jsa_vregs));
                if ((ret < 0) || (ret == ARRAY_SIZE(jsa_vregs))) {
                        ret = jsa_i2c_wr(st, JSA_CMD_SHUTDOWN);
                } else if (ret > 0) {
-                       jsa_vreg_en_all(st);
+                       ret = nvs_vregs_enable(&st->i2c->dev, st->vreg,
+                                              ARRAY_SIZE(jsa_vregs));
                        mdelay(JSA_HW_DELAY_MS);
                        ret = jsa_i2c_wr(st, JSA_CMD_SHUTDOWN);
                }
-               ret |= jsa_vreg_dis_all(st);
+               ret |= nvs_vregs_disable(&st->i2c->dev, st->vreg,
+                                        ARRAY_SIZE(jsa_vregs));
        }
        if (ret > 0)
                ret = 0;
@@ -443,7 +182,7 @@ static int jsa_pm(struct jsa_state *st, bool enable)
                dev_err(&st->i2c->dev, "%s pwr=%x ERR=%d\n",
                        __func__, enable, ret);
        } else {
-               if (st->dbg & JSA_DBG_SPEW_MSG)
+               if (st->sts & NVS_STS_SPEW_MSG)
                        dev_info(&st->i2c->dev, "%s pwr=%x\n",
                                 __func__, enable);
        }
@@ -453,58 +192,20 @@ static int jsa_pm(struct jsa_state *st, bool enable)
 static void jsa_pm_exit(struct jsa_state *st)
 {
        jsa_pm(st, false);
-       jsa_vreg_exit(st);
+       nvs_vregs_exit(&st->i2c->dev, st->vreg, ARRAY_SIZE(jsa_vregs));
 }
 
 static int jsa_pm_init(struct jsa_state *st)
 {
        int ret;
 
-       st->enable = 0;
-       st->poll_delay_ms = JSA_POLL_DELAY_MS_DFLT;
-       jsa_vreg_init(st);
+       st->enabled = 0;
+       nvs_vregs_init(&st->i2c->dev,
+                      st->vreg, ARRAY_SIZE(jsa_vregs), jsa_vregs);
        ret = jsa_pm(st, true);
        return ret;
 }
 
-static unsigned int jsa_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 void jsa_buf_push(struct iio_dev *indio_dev, struct jsa_state *st)
-{
-       unsigned char buf[16];
-       unsigned int n;
-       unsigned int i;
-       unsigned int bytes = 0;
-
-       if (!iio_buffer_enabled(indio_dev))
-               return;
-
-       if (iio_scan_mask_query(indio_dev, indio_dev->buffer, JSA_SCAN_LIGHT)) {
-               n = sizeof(st->light);
-               i = jsa_buf_index(n, &bytes);
-               memcpy(&buf[i], &st->light, n);
-               if (st->dbg & JSA_DBG_SPEW_LIGHT)
-                       dev_info(&st->i2c->dev, "light %u %lld\n",
-                                st->light, st->ts);
-       }
-       if (indio_dev->buffer->scan_timestamp) {
-               n = sizeof(st->ts);
-               i = jsa_buf_index(n, &bytes);
-               memcpy(&buf[i], &st->ts, n);
-       }
-       iio_push_to_buffers(indio_dev, buf);
-}
-
 static int jsa_start(struct jsa_state *st)
 {
        int ret;
@@ -522,61 +223,21 @@ static unsigned int jsa_sleep(struct jsa_state *st)
 {
        unsigned int ms;
 
-       if (st->poll_delay_ms > st->queue_delay_ms) {
+       ms = st->light.delay_us * 1000;
+       if (ms > st->light.poll_delay_ms) {
                jsa_i2c_wr(st, JSA_CMD_SHUTDOWN);
-               ms = st->poll_delay_ms - st->queue_delay_ms;
+               ms -= st->light.poll_delay_ms;
        } else {
                jsa_start(st);
-               ms = st->queue_delay_ms;
+               ms = st->light.poll_delay_ms;
        }
        return ms;
 }
 
-static void jsa_delay(struct jsa_state *st)
+static int jsa_rd(struct jsa_state *st)
 {
-       st->queue_delay_ms = jsa_it_tbl[st->scale_i].ms;
-       if (st->dbg & JSA_DBG_SPEW_MSG)
-               dev_info(&st->i2c->dev, "%s queue_delay_ms=%u\n",
-                        __func__, st->queue_delay_ms);
-}
-
-static int jsa_interpolate(int x1, int x2, int x3, int y1, int *y2, int y3)
-{
-       int dividend;
-       int divisor;
-
-       if (x2 < x1) {
-               /* y2 = x2 * y1 / x1 */
-               if (x1 == 0)
-                       return -EINVAL;
-               *y2 = x2 * y1 / x1;
-       } else {
-               /* y2 = ((x2 - x1)(y3 - y1)/(x3 - x1)) + y1 */
-               divisor = (x3 - x1);
-               if (!divisor)
-                       return -EINVAL;
-
-               dividend = (x2 - x1) * (y3 - y1);
-               *y2 = (dividend / divisor) + y1;
-       }
-       return 0;
-}
-
-static void jsa_report_init(struct jsa_state *st)
-{
-       st->ts = 0;
-       st->report = st->report_n;
-}
-
-static int jsa_rd(struct iio_dev *indio_dev)
-{
-       struct jsa_state *st = iio_priv(indio_dev);
        s64 ts;
-       s64 ts_elapsed;
-       u64 calc;
-       u32 lux;
        u16 hw;
-       bool t_min = false;
        int ret;
 
        ret = jsa_i2c_rd(st, &hw);
@@ -588,63 +249,32 @@ static int jsa_rd(struct iio_dev *indio_dev)
                return -EINVAL;
 
        hw &= ~(1 << JSA_VAL_VALID);
-       calc = jsa_it_tbl[st->scale_i].resolution;
-       calc *= hw;
-       if (st->scale_val2)
-               do_div(calc, st->scale_val2);
-       lux = calc;
-       jsa_interpolate(st->lux_uc_lo, lux, st->lux_uc_hi,
-                      st->lux_c_lo, &lux, st->lux_c_hi);
-       if (lux > (st->light + st->lux_thr_hi)) {
-               st->report = st->report_n;
-       } else if (st->light > st->lux_thr_lo) {
-               if (lux < (st->light - st->lux_thr_lo))
-                       st->report = st->report_n;
-       }
        ts = jsa_get_time_ns(st);
-       ts_elapsed = ts - st->ts;
-       if (ts_elapsed >= st->poll_delay_ms * 1000000)
-               t_min = true;
-       if (st->dbg & JSA_DBG_SPEW_LIGHT_POLL)
+       if (st->sts & NVS_STS_SPEW_DATA)
                dev_info(&st->i2c->dev,
-                        "poll light %d %lld  diff: %d %lldns  hw=%hu\n",
-                        lux, ts, lux - st->light, ts_elapsed, hw);
-       if ((st->report && t_min) || ((st->scale_val == 1) &&
-                                     !st->scale_val2)) {
-               /* report if:
-                * - st->report && time since last report >= polling delay
-                * - in calibration mode (scale == 1)
-                */
-               if (st->report)
-                       st->report--;
-               if (!(st->dbg & JSA_DBG_VAL_LIGHT))
-                       st->light = lux;
-               st->ts = ts;
-               jsa_buf_push(indio_dev, st);
-       }
+                        "poll light hw %hu %lld  diff=%d %lldns  index=%u\n",
+                        hw, ts, hw - st->light.hw, ts - st->light.timestamp,
+                        st->light.nld_i);
+       st->light.hw = hw;
+       st->light.timestamp = ts;
+       nvs_light_read(&st->light);
        return 0;
 }
 
-static void jsa_read(struct iio_dev *indio_dev)
+static void jsa_read(struct jsa_state *st)
 {
-       struct jsa_state *st = iio_priv(indio_dev);
-       unsigned int ms;
+       unsigned int ms = st->light.poll_delay_ms;
        int ret;
 
-       mutex_lock(&indio_dev->mlock);
-       if (st->enable) {
+       st->nvs->mutex_lock(st->nvs_data);
+       if (st->enabled) {
                if (st->hw_it) {
-                       jsa_rd(indio_dev);
-                       if (st->poll_delay_ms > st->queue_delay_ms)
-                               ms = st->poll_delay_ms;
-                       else
-                               ms = st->queue_delay_ms;
+                       jsa_rd(st);
                } else {
-                       ms = st->queue_delay_ms;
                        if (st->rc_cmd == JSA_CMD_START) {
                                ret = jsa_i2c_wr(st, JSA_CMD_STOP);
                                if (!ret) {
-                                       ret = jsa_rd(indio_dev);
+                                       ret = jsa_rd(st);
                                        if (ret)
                                                jsa_start(st);
                                        else
@@ -656,517 +286,148 @@ static void jsa_read(struct iio_dev *indio_dev)
                }
                schedule_delayed_work(&st->dw, msecs_to_jiffies(ms));
        }
-       mutex_unlock(&indio_dev->mlock);
+       st->nvs->mutex_unlock(st->nvs_data);
 }
 
 static void jsa_work(struct work_struct *ws)
 {
        struct jsa_state *st = container_of((struct delayed_work *)ws,
-                                          struct jsa_state, dw);
-       struct iio_dev *indio_dev = iio_priv_to_dev(st);
+                                           struct jsa_state, dw);
 
-       jsa_read(indio_dev);
+       jsa_read(st);
 }
 
-static int jsa_disable(struct iio_dev *indio_dev)
+static int jsa_disable(struct jsa_state *st)
 {
-       struct jsa_state *st = iio_priv(indio_dev);
        int ret;
 
-       if (!(iio_scan_mask_query(indio_dev, indio_dev->buffer,
-                                 JSA_SCAN_LIGHT)))
-               st->dbg &= ~JSA_DBG_VAL_LIGHT;
        cancel_delayed_work(&st->dw);
        ret = jsa_pm(st, false);
        if (!ret)
-               st->enable = 0;
+               st->enabled = 0;
        return ret;
 }
 
-static int jsa_enable(struct iio_dev *indio_dev)
+static int jsa_enable(void *client, int snsr_id, int enable)
 {
-       struct jsa_state *st = iio_priv(indio_dev);
-       int ret = -EINVAL;
+       struct jsa_state *st = (struct jsa_state *)client;
+       int ret;
+
+       if (enable < 0)
+               return st->enabled;
 
-       if (iio_scan_mask_query(indio_dev,
-                               indio_dev->buffer, JSA_SCAN_LIGHT)) {
+       if (enable) {
                ret = jsa_pm(st, true);
                if (!ret) {
+                       nvs_light_enable(&st->light);
                        ret = jsa_start(st);
                        if (ret) {
-                               jsa_disable(indio_dev);
+                               jsa_disable(st);
                        } else {
-                               jsa_delay(st);
-                               jsa_report_init(st);
-                               st->enable = 1;
+                               st->enabled = 1;
                                schedule_delayed_work(&st->dw,
-                                        msecs_to_jiffies(st->queue_delay_ms));
+                                             msecs_to_jiffies(st->light.
+                                                              poll_delay_ms));
                        }
                }
+       } else {
+               ret = jsa_disable(st);
        }
        return ret;
 }
 
-static ssize_t jsa_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 jsa_state *st = iio_priv(indio_dev);
-       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
-       const char *msg;
-       unsigned int new;
-       unsigned int old = 0;
-       int ret;
-
-       ret = kstrtouint(buf, 10, &new);
-       if (ret)
-               return -EINVAL;
-
-       mutex_lock(&indio_dev->mlock);
-       if (st->shutdown || st->suspend) {
-               mutex_unlock(&indio_dev->mlock);
-               return -EPERM;
-       }
-
-       switch (this_attr->address) {
-       case JSA_ATTR_ENABLE:
-               msg = "ATTR_ENABLE";
-               if (st->enable)
-                       old = 1;
-               if (new)
-                       ret = jsa_enable(indio_dev);
-               else
-                       ret = jsa_disable(indio_dev);
-               break;
-
-       case JSA_ATTR_THRESH_LO:
-               msg = "ATTR_THRESH_LO";
-               old = st->lux_thr_lo;
-               st->lux_thr_lo = new;
-               break;
-
-       case JSA_ATTR_THRESH_HI:
-               msg = "ATTR_THRESH_HI";
-               old = st->lux_thr_hi;
-               st->lux_thr_hi = new;
-               break;
-
-       default:
-               msg = "ATTR_UNKNOWN";
-               ret = -EINVAL;
-       }
-
-       jsa_report_init(st);
-       mutex_unlock(&indio_dev->mlock);
-       jsa_read(indio_dev);
-       if (st->dbg & JSA_DBG_SPEW_MSG) {
-               if (ret)
-                       dev_err(&st->i2c->dev, "%s %s %d->%d ERR=%d\n",
-                               __func__, msg, old, new, ret);
-               else
-                       dev_info(&st->i2c->dev, "%s %s %d->%d\n",
-                                __func__, msg, old, new);
-       }
-       if (ret)
-               return ret;
-
-       return count;
-}
-
-static ssize_t jsa_attr_show(struct device *dev, struct device_attribute *attr,
-                            char *buf)
+static int jsa_batch(void *client, int snsr_id, int flags,
+                    unsigned int period, unsigned int timeout)
 {
-       struct iio_dev *indio_dev = dev_get_drvdata(dev);
-       struct jsa_state *st = iio_priv(indio_dev);
-       struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
-
-       switch (this_attr->address) {
-       case JSA_ATTR_ENABLE:
-               return sprintf(buf, "%x\n", st->enable);
-
-       case JSA_ATTR_PART:
-               return sprintf(buf, "%s light\n", JSA_NAME);
-
-       case JSA_ATTR_VENDOR:
-               return sprintf(buf, "%s\n", JSA_VENDOR);
+       struct jsa_state *st = (struct jsa_state *)client;
 
-       case JSA_ATTR_VERSION:
-               return sprintf(buf, "%u\n", JSA_LIGHT_VERSION);
-
-       case JSA_ATTR_MILLIAMP:
-               return sprintf(buf, "%s\n", JSA_LIGHT_MILLIAMP);
-
-       case JSA_ATTR_THRESH_LO:
-               return sprintf(buf, "%u\n", st->lux_thr_lo);
-
-       case JSA_ATTR_THRESH_HI:
-               return sprintf(buf, "%u\n", st->lux_thr_hi);
-
-       default:
+       if (timeout)
+               /* timeout not supported (no HW FIFO) */
                return -EINVAL;
-       }
-
-       return -EINVAL;
-}
 
-static ssize_t jsa_data_store(struct device *dev,
-                             struct device_attribute *attr,
-                             const char *buf, size_t count)
-{
-       struct jsa_state *st = iio_priv(dev_get_drvdata(dev));
-       unsigned int info;
-       int ret;
-
-       ret = kstrtouint(buf, 10, &info);
-       if (ret)
-               return -EINVAL;
-
-       if (info >= JSA_INFO_LIMIT_MAX)
-               return -EINVAL;
-
-       st->info = info;
-       switch (info) {
-       case JSA_INFO_DATA:
-               st->dbg = 0;
-               break;
-
-       case JSA_INFO_DBG:
-               st->dbg ^= JSA_DBG_SPEW_MSG;
-               break;
-
-       case JSA_INFO_LIGHT_SPEW:
-               st->dbg ^= JSA_DBG_SPEW_LIGHT;
-               break;
-
-       case JSA_INFO_LIGHT_POLL_SPEW:
-               st->dbg ^= JSA_DBG_SPEW_LIGHT_POLL;
-               break;
-
-       default:
-               break;
-       }
-
-       return count;
+       st->light.delay_us = period;
+       return 0;
 }
 
-static ssize_t jsa_data_show(struct device *dev,
-                            struct device_attribute *attr, char *buf)
+static int jsa_regs(void *client, int snsr_id, char *buf)
 {
-       struct jsa_state *st = iio_priv(dev_get_drvdata(dev));
-       enum JSA_INFO info;
+       struct jsa_state *st = (struct jsa_state *)client;
        ssize_t t;
        u16 val;
        int ret;
 
-       info = st->info;
-       st->info = JSA_INFO_DATA;
-       switch (info) {
-       case JSA_INFO_DATA:
-               t = sprintf(buf, "LIGHT: %d  ts: %lld\n",
-                           st->light, st->ts);
-               return t;
-
-       case JSA_INFO_VER:
-               return sprintf(buf, "version=%u\n", JSA_VERSION_DRIVER);
-
-       case JSA_INFO_ERRS:
-               t = sprintf(buf, "error count=%u\n", st->errs);
-               st->errs = 0;
-               return t;
-
-       case JSA_INFO_REGS:
-               t = sprintf(buf, "registers:\n");
-               t += sprintf(buf + t, "CMD=%#2x\n", st->rc_cmd);
-               ret = jsa_i2c_rd(st, &val);
-               t += sprintf(buf + t, "VAL=%#4x  ERR=%d\n", val, ret);
-               return t;
-
-       case JSA_INFO_DBG:
-               return sprintf(buf, "debug spew=%x\n",
-                              !!(st->dbg & JSA_DBG_SPEW_MSG));
-
-       case JSA_INFO_LIGHT_SPEW:
-               return sprintf(buf, "lux_ts spew=%x\n",
-                              !!(st->dbg & JSA_DBG_SPEW_LIGHT));
-
-       case JSA_INFO_LIGHT_POLL_SPEW:
-               return sprintf(buf, "lux_poll_ts spew=%x\n",
-                              !!(st->dbg & JSA_DBG_SPEW_LIGHT_POLL));
-
-       default:
-               break;
-       }
-
-       return -EINVAL;
+       t = sprintf(buf, "registers:\n");
+       t += sprintf(buf + t, "CMD=%#2x\n", st->rc_cmd);
+       ret = jsa_i2c_rd(st, &val);
+       t += sprintf(buf + t, "VAL=%#4x  ERR=%d\n", val, ret);
+       return t;
 }
 
-static IIO_DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
-                      jsa_attr_show, jsa_attr_store, JSA_ATTR_ENABLE);
-static IIO_DEVICE_ATTR(illuminance_part, S_IRUGO,
-                      jsa_attr_show, NULL, JSA_ATTR_PART);
-static IIO_DEVICE_ATTR(illuminance_vendor, S_IRUGO,
-                      jsa_attr_show, NULL, JSA_ATTR_VENDOR);
-static IIO_DEVICE_ATTR(illuminance_version, S_IRUGO,
-                      jsa_attr_show, NULL, JSA_ATTR_VERSION);
-static IIO_DEVICE_ATTR(illuminance_milliamp, S_IRUGO,
-                      jsa_attr_show, NULL, JSA_ATTR_MILLIAMP);
-static IIO_DEVICE_ATTR(illuminance_thresh_rising_value,
-                      S_IRUGO | S_IWUSR | S_IWGRP,
-                      jsa_attr_show, jsa_attr_store, JSA_ATTR_THRESH_HI);
-static IIO_DEVICE_ATTR(illuminance_thresh_falling_value,
-                      S_IRUGO | S_IWUSR | S_IWGRP,
-                      jsa_attr_show, jsa_attr_store, JSA_ATTR_THRESH_LO);
-static DEVICE_ATTR(data, S_IRUGO | S_IWUSR | S_IWGRP,
-                  jsa_data_show, jsa_data_store);
-
-static struct attribute *jsa_attrs[] = {
-       &dev_attr_data.attr,
-       &iio_dev_attr_enable.dev_attr.attr,
-       &iio_dev_attr_illuminance_part.dev_attr.attr,
-       &iio_dev_attr_illuminance_vendor.dev_attr.attr,
-       &iio_dev_attr_illuminance_version.dev_attr.attr,
-       &iio_dev_attr_illuminance_milliamp.dev_attr.attr,
-       &iio_dev_attr_illuminance_thresh_rising_value.dev_attr.attr,
-       &iio_dev_attr_illuminance_thresh_falling_value.dev_attr.attr,
-       NULL
-};
-
-static struct attribute_group jsa_attr_group = {
-       .name = JSA_NAME,
-       .attrs = jsa_attrs
+static struct nvs_fn_dev jsa_fn_dev = {
+       .enable                         = jsa_enable,
+       .batch                          = jsa_batch,
+       .regs                           = jsa_regs,
 };
 
-static int jsa_read_raw(struct iio_dev *indio_dev,
-                       struct iio_chan_spec const *chan,
-                       int *val, int *val2, long mask)
+static int jsa_suspend(struct device *dev)
 {
-       struct jsa_state *st = iio_priv(indio_dev);
+       struct i2c_client *client = to_i2c_client(dev);
+       struct jsa_state *st = i2c_get_clientdata(client);
        int ret = 0;
 
-       switch (mask) {
-       case IIO_CHAN_INFO_RAW:
-               *val = st->light;
-               return IIO_VAL_INT;
-
-       case IIO_CHAN_INFO_SAMP_FREQ:
-               if (st->enable) {
-                       *val = st->poll_delay_ms * 1000; /* ms => us */
-               } else {
-                       if (st->hw_it)
-                               *val = jsa_it_tbl[st->scale_i].ms * 1000;
-                       else
-                               *val = jsa_it_tbl[st->it_i_hi].ms * 1000;
-               }
-               return IIO_VAL_INT;
-
-       case IIO_CHAN_INFO_SCALE:
-               *val = st->scale_val;
-               *val2 = st->scale_val2;
-               return IIO_VAL_INT_PLUS_MICRO;
-
-       case IIO_CHAN_INFO_OFFSET:
-               *val = st->offset_val;
-               *val2 = st->offset_val2;
-               return IIO_VAL_INT_PLUS_MICRO;
-
-       case IIO_CHAN_INFO_PEAK:
-               *val2 = JSA_LIGHT_MAX_RANGE_MICRO;
-               if (st->hw_it)
-                       *val = jsa_it_tbl[st->scale_i].range;
-               else
-                       *val = jsa_it_tbl[st->it_i_hi].range;
-               return IIO_VAL_INT_PLUS_MICRO;
-
-       case IIO_CHAN_INFO_PEAK_SCALE:
-               *val = JSA_LIGHT_RESOLUTION_IVAL;
-               *val2 = JSA_LIGHT_RESOLUTION_MICRO;
-               return IIO_VAL_INT_PLUS_MICRO;
-
-       default:
-               return -EINVAL;
-       }
-
+       st->sts |= NVS_STS_SUSPEND;
+       if (st->nvs && st->nvs_data)
+               ret = st->nvs->suspend(st->nvs_data);
+       if (st->sts & NVS_STS_SPEW_MSG)
+               dev_info(&client->dev, "%s\n", __func__);
        return ret;
 }
 
-static int jsa_write_raw(struct iio_dev *indio_dev,
-                        struct iio_chan_spec const *chan,
-                        int val, int val2, long mask)
+static int jsa_resume(struct device *dev)
 {
-       struct jsa_state *st = iio_priv(indio_dev);
-       char *msg;
-       int old = 0;
-       int old2 = 0;
+       struct i2c_client *client = to_i2c_client(dev);
+       struct jsa_state *st = i2c_get_clientdata(client);
        int ret = 0;
 
-       mutex_lock(&indio_dev->mlock);
-       if (st->shutdown || st->suspend) {
-               mutex_unlock(&indio_dev->mlock);
-               return -EPERM;
-       }
-
-       switch (mask) {
-       case IIO_CHAN_INFO_SAMP_FREQ:
-               msg = "IIO_CHAN_INFO_SAMP_FREQ";
-               old = st->poll_delay_ms * 1000; /* ms => us*/
-               st->poll_delay_ms = (unsigned int)(val / 1000); /* us => ms */
-               break;
-
-       case IIO_CHAN_INFO_SCALE:
-               msg = "IIO_CHAN_INFO_SCALE";
-               old = st->scale_val;
-               old2 = st->scale_val2;
-               st->scale_val = val;
-               st->scale_val2 = val2;
-               break;
-
-       case IIO_CHAN_INFO_OFFSET:
-               msg = "IIO_CHAN_INFO_SCALE";
-               old = st->offset_val;
-               old2 = st->offset_val2;
-               st->offset_val = val;
-               st->offset_val2 = val2;
-               break;
-
-       case IIO_CHAN_INFO_RAW:
-               msg = "IIO_CHAN_INFO_RAW";
-               old = st->light;
-               st->light = val;
-               st->ts = jsa_get_time_ns(st);
-               st->dbg |= JSA_DBG_VAL_LIGHT;
-               jsa_buf_push(indio_dev, st);
-               break;
-
-       default:
-               msg = "IIO_CHAN_INFO_UNKNOWN";
-               ret = -EINVAL;
-       }
-
-       jsa_report_init(st);
-       mutex_unlock(&indio_dev->mlock);
-       jsa_read(indio_dev);
-       if (st->dbg & JSA_DBG_SPEW_MSG) {
-               if (ret)
-                       dev_err(&st->i2c->dev, "%s c=%d %d:%d->%d:%d ERR=%d\n",
-                               msg, chan->scan_index,
-                               old, old2, val, val2, ret);
-               else
-                       dev_info(&st->i2c->dev, "%s %s chan=%d %d:%d->%d:%d\n",
-                                __func__, msg, chan->scan_index,
-                                old, old2, val, val2);
-       }
-       return ret;
-}
-
-static const struct iio_info jsa_iio_info = {
-       .driver_module = THIS_MODULE,
-       .attrs = &jsa_attr_group,
-       .read_raw = &jsa_read_raw,
-       .write_raw = &jsa_write_raw,
-};
-
-static const struct iio_chan_spec jsa_channels[] = {
-       {
-               .type                   = IIO_LIGHT,
-               .scan_index             = JSA_SCAN_LIGHT,
-               .scan_type              = IIO_ST('u', 32, 32, 0),
-               .info_mask              = BIT(IIO_CHAN_INFO_RAW) |
-                                         BIT(IIO_CHAN_INFO_SAMP_FREQ) |
-                                         BIT(IIO_CHAN_INFO_PEAK) |
-                                         BIT(IIO_CHAN_INFO_PEAK_SCALE) |
-                                         BIT(IIO_CHAN_INFO_SCALE) |
-                                         BIT(IIO_CHAN_INFO_OFFSET),
-               .info_mask_separate     = BIT(IIO_CHAN_INFO_RAW) |
-                                         BIT(IIO_CHAN_INFO_SAMP_FREQ) |
-                                         BIT(IIO_CHAN_INFO_PEAK) |
-                                         BIT(IIO_CHAN_INFO_PEAK_SCALE) |
-                                         BIT(IIO_CHAN_INFO_SCALE) |
-                                         BIT(IIO_CHAN_INFO_OFFSET),
-       },
-       IIO_CHAN_SOFT_TIMESTAMP(JSA_SCAN_TIMESTAMP)
-};
-
-static int jsa_buffer_preenable(struct iio_dev *indio_dev)
-{
-       struct jsa_state *st = iio_priv(indio_dev);
-
-       if (st->shutdown || st->suspend)
-               return -EINVAL;
-
-       return iio_sw_buffer_preenable(indio_dev);
-}
-
-static int jsa_buffer_postenable(struct iio_dev *indio_dev)
-{
-       int ret;
-
-       ret = jsa_enable(indio_dev);
-       /* never return > 0 to IIO buffer engine */
-       if (ret > 0)
-               ret = 0;
+       if (st->nvs && st->nvs_data)
+               ret = st->nvs->resume(st->nvs_data);
+       st->sts &= ~NVS_STS_SUSPEND;
+       if (st->sts & NVS_STS_SPEW_MSG)
+               dev_info(&client->dev, "%s\n", __func__);
        return ret;
 }
 
-static const struct iio_buffer_setup_ops jsa_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 = &jsa_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 = &jsa_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 struct iio_trigger_ops jsa_trigger_ops = {
-       .owner = THIS_MODULE,
-};
+static SIMPLE_DEV_PM_OPS(jsa_pm_ops, jsa_suspend, jsa_resume);
 
-static int jsa_suspend(struct device *dev)
+static void jsa_shutdown(struct i2c_client *client)
 {
-       struct i2c_client *client = to_i2c_client(dev);
-       struct iio_dev *indio_dev = i2c_get_clientdata(client);
-       struct jsa_state *st = iio_priv(indio_dev);
-       int ret = 0;
+       struct jsa_state *st = i2c_get_clientdata(client);
 
-       mutex_lock(&indio_dev->mlock);
-       if (st->enable)
-               ret = jsa_disable(indio_dev);
-       st->suspend = true;
-       mutex_unlock(&indio_dev->mlock);
-       if (st->dbg & JSA_DBG_SPEW_MSG)
+       st->sts |= NVS_STS_SHUTDOWN;
+       if (st->nvs && st->nvs_data)
+               st->nvs->shutdown(st->nvs_data);
+       if (st->sts & NVS_STS_SPEW_MSG)
                dev_info(&client->dev, "%s\n", __func__);
-       return ret;
 }
 
-static int jsa_resume(struct device *dev)
+static int jsa_remove(struct i2c_client *client)
 {
-       struct i2c_client *client = to_i2c_client(dev);
-       struct iio_dev *indio_dev = i2c_get_clientdata(client);
-       struct jsa_state *st = iio_priv(indio_dev);
+       struct jsa_state *st = i2c_get_clientdata(client);
 
-       st->suspend = false;
-       if (st->dbg & JSA_DBG_SPEW_MSG)
-               dev_info(&client->dev, "%s\n", __func__);
+       if (st != NULL) {
+               jsa_shutdown(client);
+               if (st->nvs && st->nvs_data)
+                       st->nvs->remove(st->nvs_data);
+               if (st->dw.wq)
+                       destroy_workqueue(st->dw.wq);
+               jsa_pm_exit(st);
+       }
+       dev_info(&client->dev, "%s\n", __func__);
        return 0;
 }
 
-static SIMPLE_DEV_PM_OPS(jsa_pm_ops, jsa_suspend, jsa_resume);
-
-static int jsa_id_dev(struct iio_dev *indio_dev, const char *name)
+static int jsa_id_dev(struct jsa_state *st, const char *name)
 {
-       struct jsa_state *st = iio_priv(indio_dev);
        u16 val;
        int ret = 0;
 
@@ -1179,9 +440,8 @@ static int jsa_id_dev(struct iio_dev *indio_dev, const char *name)
        return ret;
 }
 
-static int jsa_id_i2c(struct iio_dev *indio_dev, const char *name)
+static int jsa_id_i2c(struct jsa_state *st, const char *name)
 {
-       struct jsa_state *st = iio_priv(indio_dev);
        int i;
        int ret;
 
@@ -1192,12 +452,12 @@ static int jsa_id_i2c(struct iio_dev *indio_dev, const char *name)
 
        if (i < ARRAY_SIZE(jsa_i2c_addrs)) {
                st->i2c_addr = st->i2c->addr;
-               ret = jsa_id_dev(indio_dev, name);
+               ret = jsa_id_dev(st, name);
        } else {
                name = NULL;
                for (i = 0; i < ARRAY_SIZE(jsa_i2c_addrs); i++) {
                        st->i2c_addr = jsa_i2c_addrs[i];
-                       ret = jsa_id_dev(indio_dev, name);
+                       ret = jsa_id_dev(st, name);
                        if (!ret)
                                break;
                }
@@ -1207,220 +467,124 @@ static int jsa_id_i2c(struct iio_dev *indio_dev, const char *name)
        return ret;
 }
 
-static void jsa_shutdown(struct i2c_client *client)
-{
-       struct iio_dev *indio_dev = i2c_get_clientdata(client);
-       struct jsa_state *st = iio_priv(indio_dev);
-
-       mutex_lock(&indio_dev->mlock);
-       if (st->enable)
-               jsa_disable(indio_dev);
-       st->shutdown = true;
-       mutex_unlock(&indio_dev->mlock);
-       if (st->dbg & JSA_DBG_SPEW_MSG)
-               dev_info(&client->dev, "%s\n", __func__);
-}
-
-static int jsa_remove(struct i2c_client *client)
-{
-       struct iio_dev *indio_dev = i2c_get_clientdata(client);
-       struct jsa_state *st = iio_priv(indio_dev);
-
-       if (st != NULL) {
-               jsa_shutdown(client);
-               if (indio_dev->dev.devt)
-                       iio_device_unregister(indio_dev);
-               if (st->trig != NULL) {
-                       iio_trigger_unregister(st->trig);
-                       iio_trigger_free(st->trig);
-               }
-               if (indio_dev->buffer != NULL) {
-                       iio_buffer_unregister(indio_dev);
-                       iio_kfifo_free(indio_dev->buffer);
-               }
-               if (st->dw.wq)
-                       destroy_workqueue(st->dw.wq);
-               jsa_pm_exit(st);
-               iio_device_free(indio_dev);
-       }
-       dev_info(&client->dev, "%s\n", __func__);
-       return 0;
-}
+static struct sensor_cfg jsa_cfg_dflt = {
+       .name                   = NVS_LIGHT_STRING,
+       .ch_n                   = ARRAY_SIZE(iio_chan_spec_nvs_light),
+       .ch_inf                 = &iio_chan_spec_nvs_light,
+       .part                   = JSA_NAME,
+       .vendor                 = JSA_VENDOR,
+       .version                = JSA_LIGHT_VERSION,
+       .max_range              = {
+               .ival           = JSA_LIGHT_MAX_RANGE_IVAL,
+               .fval           = JSA_LIGHT_MAX_RANGE_MICRO,
+       },
+       .resolution             = {
+               .ival           = JSA_LIGHT_RESOLUTION_IVAL,
+               .fval           = JSA_LIGHT_RESOLUTION_MICRO,
+       },
+       .milliamp               = {
+               .ival           = JSA_LIGHT_MILLIAMP_IVAL,
+               .fval           = JSA_LIGHT_MILLIAMP_MICRO,
+       },
+       .delay_us_min           = JSA_POLL_DLY_MS_MIN * 1000,
+       .delay_us_max           = JSA_POLL_DLY_MS_MAX * 1000,
+       .scale                  = {
+               .ival           = JSA_LIGHT_SCALE_IVAL,
+               .fval           = JSA_LIGHT_SCALE_MICRO,
+       },
+       .thresh_lo              = JSA_LIGHT_THRESHOLD_LO,
+       .thresh_hi              = JSA_LIGHT_THRESHOLD_HI,
+};
 
-static int jsa_of_dt(struct i2c_client *client, struct jsa_state *st)
+static int jsa_of_dt(struct jsa_state *st, struct device_node *dn)
 {
-       struct device_node *dn = client->dev.of_node;
-       unsigned int val;
        unsigned int i;
 
-       /* default NVS ALS programmable parameters */
-       st->scale_val = JSA_LIGHT_SCALE_IVAL;
-       st->scale_val2 = JSA_LIGHT_SCALE_MICRO;
-       st->offset_val = JSA_LIGHT_OFFSET_IVAL;
-       st->offset_val2 = JSA_LIGHT_OFFSET_MICRO;
-       st->lux_thr_lo = JSA_THRESHOLD_LUX_DFLT;
-       st->lux_thr_hi = JSA_THRESHOLD_LUX_DFLT;
-       st->it_i_lo = 0;
-       st->it_i_hi = ARRAY_SIZE(jsa_it_tbl);
-       st->hw_it = true;
+       /* default NVS programmable parameters */
+       memcpy(&st->cfg, &jsa_cfg_dflt, sizeof(st->cfg));
+       st->light.cfg = &st->cfg;
+       st->light.hw_mask = 0x7FFF;
+       st->light.nld_tbl = jsa_nld_tbl;
        /* device tree parameters */
-       if (client->dev.of_node) {
+       if (dn)
                /* common NVS programmable parameters */
                st->iio_ts_en = of_property_read_bool(dn, "iio_timestamps");
-               of_property_read_u32(dn, "report_count", &st->report_n);
-               /* common NVS ALS programmable parameters */
-               of_property_read_s32(dn, "light_uncalibrated_lo",
-                                    &st->lux_uc_lo);
-               of_property_read_s32(dn, "light_uncalibrated_hi",
-                                    &st->lux_uc_hi);
-               of_property_read_s32(dn, "light_calibrated_lo",
-                                    &st->lux_c_lo);
-               of_property_read_s32(dn, "light_calibrated_hi",
-                                    &st->lux_c_hi);
-               of_property_read_s32(dn, "light_scale_val",
-                                    &st->scale_val);
-               of_property_read_s32(dn, "light_scale_val2",
-                                    &st->scale_val2);
-               of_property_read_s32(dn, "light_offset_val",
-                                    &st->offset_val);
-               of_property_read_s32(dn, "light_offset_val2",
-                                    &st->offset_val2);
-               of_property_read_u32(dn, "light_threshold_lo",
-                                    &st->lux_thr_lo);
-               of_property_read_u32(dn, "light_threshold_hi",
-                                    &st->lux_thr_hi);
-               /* this device supports these programmable parameters */
-               if (!of_property_read_u32(dn, "light_integration_time_ms_lo",
-                                         &val)) {
-                       for (i = ARRAY_SIZE(jsa_it_tbl); i > 1; i--) {
-                               if (val <= jsa_it_tbl[i - 1].ms)
-                                       break;
-                       }
-                       st->it_i_hi = i;
-                       st->hw_it = false;
-               }
-               if (!of_property_read_u32(dn, "light_integration_time_ms_hi",
-                                         &val)) {
-                       for (i = 0; i < ARRAY_SIZE(jsa_it_tbl) - 1; i++) {
-                               if (val >= jsa_it_tbl[i].ms)
-                                       break;
-                       }
-                       st->it_i_lo = i;
-                       st->hw_it = false;
-               }
-               if (st->it_i_hi < st->it_i_lo) {
-                       dev_err(&client->dev,
-                               "%s light_integration_time_ms_ ERR\n",
-                               __func__);
-                       st->it_i_lo = 0;
-                       st->it_i_hi = ARRAY_SIZE(jsa_it_tbl);
-               }
-               if (!of_property_read_u32(dn, "light_integration_time_ms_hw",
-                                         &val)) {
-                       for (i = 0; i < ARRAY_SIZE(jsa_it_tbl) - 1; i++) {
-                               if (val >= jsa_it_tbl[i].ms)
-                                       break;
-                       }
-                       st->scale_i = i;
-                       st->hw_it = true;
-               }
-       }
-       if (!st->report_n)
-               st->report_n = JSA_REPORT_N;
+       /* common NVS parameters */
+       nvs_of_dt(dn, &st->cfg, NULL);
+       /* this device supports these programmable parameters */
+       if (nvs_light_of_dt(&st->light, dn, NULL)) {
+               /* default is HW IT */
+               st->light.nld_i_lo = 0;
+               st->light.nld_i_hi = 0;
+       }
+       if (st->light.nld_i_lo == st->light.nld_i_hi) {
+               /* HW IT enabled when indexes are the same */
+               st->hw_it = true;
+               st->light.nld_tbl = NULL; /* disable dynamic resolution */
+       }
+       i = st->light.nld_i_lo;
+       st->cfg.resolution.ival = jsa_nld_tbl[i].resolution.ival;
+       st->cfg.resolution.fval = jsa_nld_tbl[i].resolution.fval;
+       i = st->light.nld_i_hi;
+       st->cfg.max_range.ival = jsa_nld_tbl[i].max_range.ival;
+       st->cfg.max_range.fval = jsa_nld_tbl[i].max_range.fval;
+       st->cfg.delay_us_min = jsa_nld_tbl[i].delay_min_ms * 1000;
        return 0;
 }
 
 static int jsa_probe(struct i2c_client *client,
                     const struct i2c_device_id *id)
 {
-       struct iio_dev *indio_dev;
        struct jsa_state *st;
        int ret;
 
        dev_info(&client->dev, "%s\n", __func__);
-       indio_dev = iio_device_alloc(sizeof(*st));
-       if (indio_dev == NULL) {
-               dev_err(&client->dev, "%s iio_device_alloc ERR\n", __func__);
+       st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
+       if (st == NULL) {
+               dev_err(&client->dev, "%s devm_kzalloc ERR\n", __func__);
                return -ENOMEM;
        }
 
-       st = iio_priv(indio_dev);
+       i2c_set_clientdata(client, st);
        st->i2c = client;
-       i2c_set_clientdata(client, indio_dev);
-       ret = jsa_of_dt(client, st);
+       ret = jsa_of_dt(st, client->dev.of_node);
        if (ret) {
                dev_err(&client->dev, "%s _of_dt ERR\n", __func__);
                ret = -ENODEV;
-               goto jsa_probe_err;
+               goto jsa_probe_exit;
        }
 
-       mutex_init(&st->mutex_client);
        jsa_pm_init(st);
-       ret = jsa_id_i2c(indio_dev, id->name);
+       ret = jsa_id_i2c(st, id->name);
        if (ret) {
                dev_err(&client->dev, "%s _id_i2c ERR\n", __func__);
                ret = -ENODEV;
                goto jsa_probe_exit;
        }
 
-       indio_dev->buffer = iio_kfifo_allocate(indio_dev);
-       if (!indio_dev->buffer) {
-               dev_err(&client->dev, "%s iio_kfifo_allocate ERR\n", __func__);
-               ret = -ENOMEM;
-               goto jsa_probe_err;
+       jsa_fn_dev.errs = &st->errs;
+       jsa_fn_dev.sts = &st->sts;
+       st->nvs = nvs_iio();
+       if (st->nvs == NULL) {
+               dev_err(&client->dev, "%s nvs_iio ERR\n", __func__);
+               ret = -ENODEV;
+               goto jsa_probe_exit;
        }
 
-       indio_dev->buffer->scan_timestamp = true;
-       indio_dev->modes = INDIO_DIRECT_MODE;
-       indio_dev->currentmode = INDIO_DIRECT_MODE;
-       indio_dev->dev.parent = &client->dev;
-       indio_dev->channels = jsa_channels;
-       indio_dev->num_channels = ARRAY_SIZE(jsa_channels);
-       indio_dev->name = JSA_NAME;
-       indio_dev->info = &jsa_iio_info;
-       indio_dev->setup_ops = &jsa_buffer_setup_ops;
-       ret = iio_buffer_register(indio_dev, indio_dev->channels,
-                                 indio_dev->num_channels);
+       ret = st->nvs->probe(&st->nvs_data, st, &client->dev,
+                            &jsa_fn_dev, &st->cfg);
        if (ret) {
-               dev_err(&client->dev, "%s iio_buffer_register ERR\n",
-                       __func__);
-               goto jsa_probe_err;
+               dev_err(&client->dev, "%s nvs_probe ERR\n", __func__);
+               ret = -ENODEV;
+               goto jsa_probe_exit;
        }
 
+       st->light.nvs_data = st->nvs_data;
+       st->light.handler = st->nvs->handler;
        INIT_DELAYED_WORK(&st->dw, jsa_work);
-       st->trig = iio_trigger_alloc("%s-dev%d",
-                                    indio_dev->name, indio_dev->id);
-       if (st->trig == NULL) {
-               dev_err(&client->dev, "%s iio_allocate_trigger ERR\n",
-                       __func__);
-               ret = -ENOMEM;
-               goto jsa_probe_err;
-       }
-
-       st->trig->dev.parent = &st->i2c->dev;
-       st->trig->ops = &jsa_trigger_ops;
-       ret = iio_trigger_register(st->trig);
-       if (ret) {
-               dev_err(&client->dev, "%s iio_trigger_register ERR\n",
-                       __func__);
-               ret = -ENOMEM;
-               goto jsa_probe_err;
-       }
-
-       indio_dev->trig = st->trig;
-       indio_dev->modes |= INDIO_BUFFER_TRIGGERED;
-       ret = iio_device_register(indio_dev);
-       if (ret) {
-               dev_err(&client->dev, "%s iio_device_register ERR\n",
-                       __func__);
-               goto jsa_probe_err;
-       }
-
        dev_info(&client->dev, "%s done\n", __func__);
        return 0;
 
-jsa_probe_err:
-       dev_err(&client->dev, "%s ERR %d\n", __func__, ret);
 jsa_probe_exit:
        jsa_remove(client);
        return ret;
diff --git a/drivers/iio/light/nvs_ltr659.c b/drivers/iio/light/nvs_ltr659.c
new file mode 100644 (file)
index 0000000..ffccd57
--- /dev/null
@@ -0,0 +1,1162 @@
+/* Copyright (c) 2014, 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 */
+/* See nvs_iio.c and nvs.h for documentation */
+/* See nvs_light.c and nvs_light.h for documentation */
+/* See nvs_proximity.c and nvs_proximity.h for documentation */
+
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/ktime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/nvs.h>
+#include <linux/nvs_light.h>
+#include <linux/nvs_proximity.h>
+
+
+#define LTR_VENDOR                     "Lite-On Technology Corp."
+#define LTR_NAME                       "ltrX5X"
+#define LTR_NAME_LTR558ALS             "ltr558als"
+#define LTR_NAME_LTR659PS              "ltr659ps"
+#define LTR_DEVID_558ALS               (0x80)
+#define LTR_DEVID_659PS                        (0x90)
+#define LTR_HW_DELAY_MS                        (600)
+#define LTR_WAKEUP_DELAY_MS            (10)
+#define LTR_POLL_DLY_MS_DFLT           (2000)
+#define LTR_POLL_DLY_MS_MIN            (100)
+#define LTR_POLL_DLY_MS_MAX            (60000)
+#define LTR_REG_PS_CONTR_DFLT          (0x00)
+#define LTR_REG_PS_LED_DFLT            (0x04)
+#define LTR_REG_PS_N_PULSES_DFLT       (0x7F)
+#define LTR_REG_PS_MEAS_RATE_DFLT      (0x05)
+#define LTR_REG_ALS_MEAS_RATE_DFLT     (0x04)
+#define LTR_REG_INTERRUPT_PERSIST_DFLT (0x00)
+/* light defines */
+#define LTR_LIGHT_VERSION              (1)
+#define LTR_LIGHT_MAX_RANGE_IVAL       (14323)
+#define LTR_LIGHT_MAX_RANGE_MICRO      (0)
+#define LTR_LIGHT_RESOLUTION_IVAL      (0)
+#define LTR_LIGHT_RESOLUTION_MICRO     (14000)
+#define LTR_LIGHT_MILLIAMP_IVAL                (0)
+#define LTR_LIGHT_MILLIAMP_MICRO       (13500)
+#define LTR_LIGHT_SCALE_IVAL           (0)
+#define LTR_LIGHT_SCALE_MICRO          (10000)
+#define LTR_LIGHT_THRESHOLD_DFLT       (50)
+/* proximity defines */
+#define LTR_PROX_THRESHOLD_LO          (1000)
+#define LTR_PROX_THRESHOLD_HI          (2000)
+#define LTR_PROX_VERSION               (1)
+/* setting max_range and resolution to 1.0 = binary proximity */
+#define LTR_PROX_MAX_RANGE_IVAL                (1)
+#define LTR_PROX_MAX_RANGE_MICRO       (0)
+#define LTR_PROX_RESOLUTION_IVAL       (1)
+#define LTR_PROX_RESOLUTION_MICRO      (0)
+#define LTR_PROX_MILLIAMP_IVAL         (10)
+#define LTR_PROX_MILLIAMP_MICRO                (250000)
+#define LTR_PROX_SCALE_IVAL            (0)
+#define LTR_PROX_SCALE_MICRO           (0)
+#define LTR_PROX_OFFSET_IVAL           (0)
+#define LTR_PROX_OFFSET_MICRO          (0)
+/* HW registers */
+#define LTR_REG_ALS_CONTR              (0x80)
+#define LTR_REG_ALS_CONTR_MODE         (1)
+#define LTR_REG_ALS_CONTR_SW_RESET     (2)
+#define LTR_REG_ALS_CONTR_GAIN         (3)
+#define LTR_REG_PS_CONTR               (0x81)
+#define LTR_REG_PS_CONTR_MODE          (1)
+#define LTR_REG_PS_CONTR_GAIN          (2)
+#define LTR_REG_PS_CONTR_SAT_EN                (5)
+#define LTR_REG_PS_CONTR_POR_MASK      (0x2C)
+#define LTR_REG_PS_LED                 (0x82)
+#define LTR_REG_PS_N_PULSES            (0x83)
+#define LTR_REG_PS_MEAS_RATE           (0x84)
+#define LTR_REG_ALS_MEAS_RATE          (0x85)
+#define LTR_REG_PART_ID                        (0x86)
+#define LTR_REG_PART_ID_MASK           (0xF0)
+#define LTR_REG_MANUFAC_ID             (0x87)
+#define LTR_REG_MANUFAC_ID_VAL         (0x05)
+#define LTR_REG_ALS_DATA_CH1_0         (0x88)
+#define LTR_REG_ALS_DATA_CH1_1         (0x89)
+#define LTR_REG_ALS_DATA_CH0_0         (0x8A)
+#define LTR_REG_ALS_DATA_CH0_1         (0x8B)
+#define LTR_REG_STATUS                 (0x8C)
+#define LTR_REG_STATUS_DATA_PS         (0)
+#define LTR_REG_STATUS_IRQ_PS          (1)
+#define LTR_REG_STATUS_DATA_ALS                (2)
+#define LTR_REG_STATUS_IRQ_ALS         (3)
+#define LTR_REG_STATUS_DATA_MASK       (0x05)
+#define LTR_REG_PS_DATA_0              (0x8D)
+#define LTR_REG_PS_DATA_1              (0x8E)
+#define LTR_REG_PS_DATA_MASK           (0x07FF)
+#define LTR_REG_PS_DATA_SAT            (15)
+#define LTR_REG_INTERRUPT              (0x8F)
+#define LTR_REG_INTERRUPT_PS_EN                (0)
+#define LTR_REG_INTERRUPT_ALS_EN       (1)
+#define LTR_REG_INTERRUPT_MODE_MASK    (0x03)
+#define LTR_REG_INTERRUPT_POLARITY     (2)
+#define LTR_REG_PS_THRES_UP_0          (0x90)
+#define LTR_REG_PS_THRES_UP_1          (0x91)
+#define LTR_REG_PS_THRES_LOW_0         (0x92)
+#define LTR_REG_PS_THRES_LOW_1         (0x93)
+#define LTR_REG_PS_OFFSET_1            (0x94)
+#define LTR_REG_PS_OFFSET_0            (0x95)
+#define LTR_REG_ALS_THRES_UP_0         (0x97)
+#define LTR_REG_ALS_THRES_UP_1         (0x98)
+#define LTR_REG_ALS_THRES_LOW_0                (0x99)
+#define LTR_REG_ALS_THRES_LOW_1                (0x9A)
+#define LTR_REG_INTERRUPT_PERSIST      (0x9E)
+#define LTR_REG_INTERRUPT_PERSIST_MASK (0xF0)
+/* devices */
+#define LTR_DEV_LIGHT                  (0)
+#define LTR_DEV_PROX                   (1)
+#define LTR_DEV_N                      (2)
+
+
+/* regulator names in order of powering on */
+static char *ltr_vregs[] = {
+       "vdd",
+};
+
+static u8 ltr_ids[] = {
+       LTR_DEVID_558ALS,
+       LTR_DEVID_659PS,
+};
+
+static unsigned short ltr_i2c_addrs[] = {
+       0x23,
+};
+
+static struct nvs_light_dynamic ltr_nld_tbl[] = {
+       {{0, 10000}, {327,   670000}, {0, 90000}, 100, 0},
+       {{2, 0},     {65534, 0},      {0, 90000}, 100, 0},
+};
+
+struct ltr_state {
+       struct i2c_client *i2c;
+       struct nvs_fn_if *nvs;
+       void *nvs_data[LTR_DEV_N];
+       struct sensor_cfg cfg[LTR_DEV_N];
+       struct delayed_work dw;
+       struct regulator_bulk_data vreg[ARRAY_SIZE(ltr_vregs)];
+       struct nvs_light light;
+       struct nvs_proximity prox;
+       unsigned int dev[LTR_DEV_N];
+       unsigned int snsr_n;            /* number of sensors */
+       unsigned int sts;               /* status flags */
+       unsigned int errs;              /* error count */
+       unsigned int enabled;           /* enable status */
+       bool iio_ts_en;                 /* use IIO timestamps */
+       u16 i2c_addr;                   /* I2C address */
+       u8 dev_id;                      /* device ID */
+       u8 ps_contr;                    /* PS_CONTR register default */
+       u8 ps_led;                      /* PS_LED register default */
+       u8 ps_n_pulses;                 /* PS_N_PULSES register default */
+       u8 ps_meas_rate;                /* PS_MEAS_RATE register default */
+       u8 als_meas_rate;               /* ALS_CONTR register default */
+       u8 interrupt;                   /* INTERRUPT register default */
+       u8 interrupt_persist;           /* INTERRUPT_PERSIST reg default */
+       u8 rc_als_contr;                /* cache of ALS_CONTR */
+       u8 rc_ps_contr;                 /* cache of PS_CONTR */
+       u8 rc_interrupt;                /* cache of INTERRUPT */
+};
+
+static s64 ltr_get_time_ns(struct ltr_state *st)
+{
+       struct timespec ts;
+
+       if (st->iio_ts_en)
+               return iio_get_time_ns();
+
+       ktime_get_ts(&ts);
+       return timespec_to_ns(&ts);
+}
+
+static void ltr_err(struct ltr_state *st)
+{
+       st->errs++;
+       if (!st->errs)
+               st->errs--;
+}
+
+static int ltr_i2c_read(struct ltr_state *st, u8 reg, u16 len, u8 *val)
+{
+       struct i2c_msg msg[2];
+
+       msg[0].addr = st->i2c_addr;
+       msg[0].flags = 0;
+       msg[0].len = 1;
+       msg[0].buf = &reg;
+       msg[1].addr = st->i2c_addr;
+       msg[1].flags = I2C_M_RD;
+       msg[1].len = len;
+       msg[1].buf = val;
+       if (i2c_transfer(st->i2c->adapter, msg, 2) != 2) {
+               ltr_err(st);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static int ltr_i2c_rd(struct ltr_state *st, u8 reg, u8 *val)
+{
+       return ltr_i2c_read(st, reg, 1, val);
+}
+
+static int ltr_i2c_write(struct ltr_state *st, u16 len, u8 *buf)
+{
+       struct i2c_msg msg;
+
+       if (st->i2c_addr) {
+               msg.addr = st->i2c_addr;
+               msg.flags = 0;
+               msg.len = len;
+               msg.buf = buf;
+               if (i2c_transfer(st->i2c->adapter, &msg, 1) != 1) {
+                       ltr_err(st);
+                       return -EIO;
+               }
+       }
+
+       return 0;
+}
+
+static int ltr_i2c_wr(struct ltr_state *st, u8 reg, u8 val)
+{
+       u8 buf[2];
+
+       buf[0] = reg;
+       buf[1] = val;
+       return ltr_i2c_write(st, sizeof(buf), buf);
+}
+
+static int ltr_reset_sw(struct ltr_state *st)
+{
+       u8 buf[5];
+       u8 reset;
+       int ret;
+
+       reset = 1 << LTR_REG_ALS_CONTR_SW_RESET;
+       if (st->dev_id == LTR_DEVID_659PS)
+               reset >>= 1;
+       ret = ltr_i2c_wr(st, LTR_REG_ALS_CONTR, reset);
+       if (!ret) {
+               mdelay(LTR_HW_DELAY_MS);
+               st->rc_als_contr = 0;
+               st->rc_ps_contr = 0;
+               st->rc_interrupt = 0;
+       }
+       buf[0] = LTR_REG_PS_LED;
+       buf[1] = st->ps_led;
+       buf[2] = st->ps_n_pulses;
+       buf[3] = st->ps_meas_rate;
+       buf[4] = st->als_meas_rate;
+       ret |= ltr_i2c_write(st, sizeof(buf), buf);
+       ret |= ltr_i2c_wr(st, LTR_REG_INTERRUPT_PERSIST, st->interrupt_persist);
+       return ret;
+}
+
+static int ltr_pm(struct ltr_state *st, bool enable)
+{
+       int ret = 0;
+
+       if (enable) {
+               ret = nvs_vregs_enable(&st->i2c->dev, st->vreg,
+                                ARRAY_SIZE(ltr_vregs));
+               if (ret > 0)
+                       mdelay(LTR_HW_DELAY_MS);
+               ret = ltr_reset_sw(st);
+       } else {
+               ret = nvs_vregs_sts(st->vreg, ARRAY_SIZE(ltr_vregs));
+               if ((ret < 0) || (ret == ARRAY_SIZE(ltr_vregs))) {
+                       ret = ltr_i2c_wr(st, LTR_REG_ALS_CONTR, 0);
+                       ret |= ltr_i2c_wr(st, LTR_REG_PS_CONTR, 0);
+               } else if (ret > 0) {
+                       nvs_vregs_enable(&st->i2c->dev, st->vreg,
+                                        ARRAY_SIZE(ltr_vregs));
+                       mdelay(LTR_HW_DELAY_MS);
+                       ret = ltr_i2c_wr(st, LTR_REG_ALS_CONTR, 0);
+                       ret |= ltr_i2c_wr(st, LTR_REG_PS_CONTR, 0);
+               }
+               ret |= nvs_vregs_disable(&st->i2c->dev, st->vreg,
+                                        ARRAY_SIZE(ltr_vregs));
+       }
+       if (ret > 0)
+               ret = 0;
+       if (ret) {
+               dev_err(&st->i2c->dev, "%s pwr=%x ERR=%d\n",
+                       __func__, enable, ret);
+       } else {
+               if (st->sts & NVS_STS_SPEW_MSG)
+                       dev_info(&st->i2c->dev, "%s pwr=%x\n",
+                                __func__, enable);
+       }
+       return ret;
+}
+
+static void ltr_pm_exit(struct ltr_state *st)
+{
+       ltr_pm(st, false);
+       nvs_vregs_exit(&st->i2c->dev, st->vreg, ARRAY_SIZE(ltr_vregs));
+}
+
+static int ltr_pm_init(struct ltr_state *st)
+{
+       int ret;
+
+       st->enabled = 0;
+       nvs_vregs_init(&st->i2c->dev,
+                      st->vreg, ARRAY_SIZE(ltr_vregs), ltr_vregs);
+       ret = ltr_pm(st, true);
+       return ret;
+}
+
+static int ltr_interrupt_wr(struct ltr_state *st, u8 interrupt)
+{
+       int ret = 0;
+
+       if (interrupt != st->interrupt) {
+               ret = ltr_i2c_wr(st, LTR_REG_INTERRUPT, interrupt);
+               if (st->sts & NVS_STS_SPEW_MSG)
+                       dev_info(&st->i2c->dev, "%s irq %hhx->%hhx err=%d\n",
+                                __func__, st->rc_interrupt, interrupt, ret);
+               if (!ret)
+                       st->rc_interrupt = interrupt;
+       }
+       return ret;
+}
+
+static int ltr_cmd_wr(struct ltr_state *st, unsigned int enable, bool irq_en)
+{
+       u8 interrupt;
+       u8 als_contr;
+       u8 ps_contr;
+       int ret;
+       int ret_t = 0;
+
+       if (enable & (1 << LTR_DEV_LIGHT)) {
+               als_contr = st->light.nld_i << LTR_REG_ALS_CONTR_GAIN;
+               als_contr |= 1 << LTR_REG_ALS_CONTR_MODE;
+       } else {
+               als_contr = 0;
+       }
+       if (st->rc_als_contr != als_contr) {
+               ret = ltr_i2c_wr(st, LTR_REG_ALS_CONTR, als_contr);
+               if (ret)
+                       ret_t |= ret;
+               else
+                       st->rc_als_contr = als_contr;
+       }
+       ps_contr = st->ps_contr;
+       if (enable & (1 << LTR_DEV_PROX))
+               ps_contr |= 1 << LTR_REG_PS_CONTR_MODE;
+       if (st->rc_ps_contr != ps_contr) {
+               ret = ltr_i2c_wr(st, LTR_REG_PS_CONTR, ps_contr);
+               if (ret)
+                       ret_t |= ret;
+               else
+                       st->rc_ps_contr = ps_contr;
+       }
+       if (st->i2c->irq) {
+               interrupt = st->interrupt;
+               if (irq_en) {
+                       if (enable & (1 << LTR_DEV_LIGHT))
+                               interrupt |= (1 << LTR_REG_INTERRUPT_ALS_EN);
+                       if (enable & (1 << LTR_DEV_PROX))
+                               interrupt |= (1 << LTR_REG_INTERRUPT_PS_EN);
+               }
+               ret_t |= ltr_interrupt_wr(st, interrupt);
+       }
+       if ((st->rc_interrupt & LTR_REG_INTERRUPT_MODE_MASK) && !ret_t)
+               ret_t = 1; /* flag IRQ enabled */
+       if (st->sts & NVS_STS_SPEW_MSG)
+               dev_info(&st->i2c->dev, "%s als=%hhx ps=%hhx ret=%d\n",
+                        __func__, st->rc_als_contr, st->rc_ps_contr, ret_t);
+       return ret_t;
+}
+
+static int ltr_thr_wr(struct ltr_state *st, u8 reg, u16 thr_lo, u16 thr_hi)
+{
+       u8 buf[5];
+       int ret;
+
+       ret = ltr_interrupt_wr(st, st->interrupt); /* irq disable */
+       if (st->i2c->irq) {
+               buf[0] = reg;
+               buf[1] = thr_hi & 0xFF;
+               buf[2] = thr_hi >> 8;
+               buf[3] = thr_lo & 0xFF;
+               buf[4] = thr_lo >> 8;
+               ret |= ltr_i2c_write(st, sizeof(buf), buf);
+               if (st->sts & NVS_STS_SPEW_MSG)
+                       dev_info(&st->i2c->dev,
+                                "%s reg=%hhx lo=%hd hi=%hd ret=%d\n",
+                                __func__, reg, thr_lo, thr_hi, ret);
+       }
+       return ret;
+}
+
+static int ltr_rd_light(struct ltr_state *st, s64 ts)
+{
+       u32 hw;
+       u16 hw0;
+       u16 hw1;
+       unsigned int divisor;
+       int ratio;
+       int ch0_coeff = 1;
+       int ch1_coeff = 1;
+       int ret;
+
+       ret = ltr_i2c_read(st, LTR_REG_ALS_DATA_CH0_0, 2, (u8 *)&hw0);
+       if (ret)
+               return ret;
+
+       hw0 = be16_to_cpu(hw0);
+       ret = ltr_i2c_read(st, LTR_REG_ALS_DATA_CH1_0, 2, (u8 *)&hw1);
+       if (ret)
+               return ret;
+
+       hw1 = be16_to_cpu(hw1);
+       /* The code from here to the next comment is from a previous driver.
+        * It appears to do all the resolution calculations that the NVS light
+        * module would do.  A 558 part wasn't available when this was written,
+        * so either this code is removed and use the nvs_light_dynamic table
+        * along with interpolation calibration or clear the
+        * nvs_light_dynamic.resolution to 1.0.
+        * Note that scale will divide the final result by 100 in user space.
+        */
+       divisor = hw0 + hw1;
+       if (divisor) {
+               ratio = (hw1 * 100) / divisor;
+               if (ratio < 45) {
+                       ch0_coeff = 17743;
+                       ch1_coeff = -11059;
+               } else if ((ratio >= 45) && (ratio < 64)) {
+                       ch0_coeff = 37725;
+                       ch1_coeff = 13363;
+               } else if ((ratio >= 64) && (ratio < 85)) {
+                       ch0_coeff = 16900;
+                       ch1_coeff = 1690;
+               } else if (ratio >= 85) {
+                       ch0_coeff = 0;
+                       ch1_coeff = 0;
+               }
+               hw = ((hw0 * ch0_coeff) - (hw1 * ch1_coeff)) / 100;
+       } else {
+               hw = 0;
+       }
+       /* next comment */
+       if (st->sts & NVS_STS_SPEW_DATA)
+               dev_info(&st->i2c->dev,
+                        "poll light hw %u %lld  diff=%d %lldns  index=%u\n",
+                        hw, ts, hw - st->light.hw, ts - st->light.timestamp,
+                        st->light.nld_i);
+       st->light.hw = hw;
+       st->light.timestamp = ts;
+       ret = nvs_light_read(&st->light);
+       if (ret < 1)
+               /* either poll or nothing to do */
+               return ret;
+
+       ret = ltr_thr_wr(st, LTR_REG_ALS_THRES_UP_0,
+                        st->light.hw_thresh_lo, st->light.hw_thresh_hi);
+       return ret;
+}
+
+static int ltr_rd_prox(struct ltr_state *st, s64 ts)
+{
+       u16 hw;
+       int ret;
+
+       ret = ltr_i2c_read(st, LTR_REG_PS_DATA_0, 2, (u8 *)&hw);
+       if (ret)
+               return ret;
+
+       hw = le16_to_cpu(hw);
+       hw &= LTR_REG_PS_DATA_MASK;
+       if (st->sts & NVS_STS_SPEW_DATA)
+               dev_info(&st->i2c->dev,
+                        "poll proximity hw %hu %lld  diff=%d %lldns\n",
+                        hw, ts, hw - st->prox.hw, ts - st->prox.timestamp);
+       st->prox.hw = hw;
+       st->prox.timestamp = ts;
+       ret = nvs_proximity_read(&st->prox);
+       if (ret < 1)
+               /* either poll or nothing to do */
+               return ret;
+
+       ret = ltr_thr_wr(st, LTR_REG_PS_THRES_UP_0,
+                        st->prox.hw_thresh_lo, st->prox.hw_thresh_hi);
+       return ret;
+}
+
+static int ltr_en(struct ltr_state *st, unsigned int enable)
+{
+       if (enable & (1 << LTR_DEV_LIGHT))
+               nvs_light_enable(&st->light);
+       if (enable & (1 << LTR_DEV_PROX))
+               nvs_proximity_enable(&st->prox);
+       return ltr_cmd_wr(st, enable, false);
+}
+
+static int ltr_rd(struct ltr_state *st)
+{
+       s64 ts;
+       u8 sts;
+       int ret = 0;
+
+       /* clear possible IRQ */
+       ret = ltr_i2c_rd(st, LTR_REG_STATUS, &sts);
+       if (ret)
+               return ret;
+
+       if (sts & LTR_REG_STATUS_DATA_MASK) {
+               ts = ltr_get_time_ns(st);
+               if (st->enabled & (1 << LTR_DEV_PROX))
+                       ret |= ltr_rd_prox(st, ts);
+               if (st->enabled & (1 << LTR_DEV_LIGHT))
+                       ret |= ltr_rd_light(st, ts);
+       } else {
+               ret = RET_POLL_NEXT;
+       }
+       if (ret < 0)
+               /* poll if error or more reporting */
+               ret = ltr_cmd_wr(st, st->enabled, false);
+       else
+               ret = ltr_cmd_wr(st, st->enabled, true);
+       return ret;
+}
+
+static unsigned int ltr_polldelay(struct ltr_state *st)
+{
+       unsigned int poll_delay_ms = LTR_POLL_DLY_MS_DFLT;
+
+       if (st->enabled & (1 << LTR_DEV_LIGHT))
+             &n