This driver adds the initial support for the Capella cm36686
ambient light and proximity sensor and cm36672p proximity sensor.

Signed-off-by: Erikas Bitovtas <[email protected]>
---
 drivers/iio/light/Kconfig   |  11 +
 drivers/iio/light/Makefile  |   1 +
 drivers/iio/light/cm36686.c | 810 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 822 insertions(+)

diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index ac1408d374c9..b1d1943dec33 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -220,6 +220,17 @@ config CM36651
          To compile this driver as a module, choose M here:
          the module will be called cm36651.
 
+config CM36686
+       depends on I2C
+       tristate "CM36686 driver"
+       help
+         Say Y here if you use cm36686.
+         This option enables ambient light & proximity sensor using
+         Capella cm36686 device driver.
+
+         To compile this driver as a module, choose M here:
+         the module will be called cm36686.
+
 config IIO_CROS_EC_LIGHT_PROX
        tristate "ChromeOS EC Light and Proximity Sensors"
        depends on IIO_CROS_EC_SENSORS_CORE
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index c0048e0d5ca8..806df80f6454 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_CM3232)          += cm3232.o
 obj-$(CONFIG_CM3323)           += cm3323.o
 obj-$(CONFIG_CM3605)           += cm3605.o
 obj-$(CONFIG_CM36651)          += cm36651.o
+obj-$(CONFIG_CM36686)          += cm36686.o
 obj-$(CONFIG_IIO_CROS_EC_LIGHT_PROX) += cros_ec_light_prox.o
 obj-$(CONFIG_GP2AP002)         += gp2ap002.o
 obj-$(CONFIG_GP2AP020A00F)     += gp2ap020a00f.o
diff --git a/drivers/iio/light/cm36686.c b/drivers/iio/light/cm36686.c
new file mode 100644
index 000000000000..eb108af7226d
--- /dev/null
+++ b/drivers/iio/light/cm36686.c
@@ -0,0 +1,810 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <asm-generic/errno-base.h>
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/dev_printk.h>
+#include <linux/device/devres.h>
+#include <linux/iio/types.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sysfs.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/interrupt.h>
+
+/* Device registers */
+#define CM36686_REG_ALS_CONF           0x00
+#define CM36686_REG_PS_CONF1           0x03
+#define CM36686_REG_PS_CONF3           0x04
+#define CM36686_REG_PS_THDL            0x06
+#define CM36686_REG_PS_THDH            0x07
+#define CM36686_REG_PS_DATA            0x08
+#define CM36686_REG_ALS_DATA           0x09
+#define CM36686_REG_INT_FLAG           0x0B
+#define CM36686_REG_ID_FLAG            0x0C
+
+/* ALS_CONF */
+#define CM36686_ALS_IT                 GENMASK(7, 6)
+#define CM36686_ALS_GAIN               GENMASK(3, 2)
+#define CM36686_ALS_INT_EN             BIT(1)
+#define CM36686_ALS_SD                 BIT(0)
+
+/* PS_CONF1 */
+#define CM36686_PS_DR                  GENMASK(7, 6)
+#define CM36686_PS_PERS                        GENMASK(5, 4)
+#define CM36686_PS_IT                  GENMASK(3, 1)
+#define CM36686_PS_SD                  BIT(0)
+
+#define CM36686_PS_INT_OUT             BIT(9)
+#define CM36686_PS_INT_IN              BIT(8)
+
+/* PS_CONF3 */
+#define CM36686_PS_SMART_PERS_ENABLE   BIT(4)
+
+#define CM36686_LED_I                  GENMASK(10, 8)
+
+/* INT_FLAG */
+#define CM36686_PS_IF                  GENMASK(9, 8)
+
+/* Default values */
+#define CM36686_ALS_ENABLE             0x00
+#define CM36686_PS_DR_1_320            FIELD_PREP(CM36686_PS_DR, 3)
+#define CM36686_PS_PERS_2              FIELD_PREP(CM36686_PS_PERS, 1)
+#define CM36686_PS_IT_2_5T             FIELD_PREP(CM36686_PS_IT, 3)
+#define CM36686_LED_I_100              FIELD_PREP(CM36686_LED_I, 2)
+
+/* Shifts */
+#define CM36686_INT_FLAG_SHIFT         8
+
+/* Max proximity thresholds */
+#define CM36686_MAX_PS_VALUE           (BIT(12) - 1)
+
+#define CM36686_DEVICE_ID              0x86
+
+enum {
+       CM36686,
+       CM36672P,
+};
+
+enum cm36686_distance {
+       CM36686_AWAY = 1,
+       CM36686_CLOSE,
+       CM36686_BOTH
+};
+
+enum {
+       CM36686_PS_CONF1,
+       CM36686_PS_CONF3,
+       CM36686_PS_CONF_NUM
+};
+
+enum {
+       CM36686_SUPPLY_VDD,
+       CM36686_SUPPLY_VDDIO,
+       CM36686_SUPPLY_VLED,
+       CM36686_SUPPLY_NUM,
+};
+
+static const int cm36686_als_it_times[][2] = {
+       {0, 80000},
+       {0, 160000},
+       {0, 320000},
+       {0, 640000}
+};
+
+static const int cm36686_ps_it_times[][2] = {
+       {0, 320},
+       {0, 480},
+       {0, 640},
+       {0, 800},
+       {0, 960},
+       {0, 1120},
+       {0, 1280},
+       {0, 2560}
+};
+
+static const int cm36686_ps_led_current[] = {
+       50,
+       75,
+       100,
+       120,
+       140,
+       160,
+       180,
+       200
+};
+
+struct cm36686_data {
+       struct mutex lock;
+       struct i2c_client *client;
+       struct regulator_bulk_data supplies[CM36686_SUPPLY_NUM];
+       int als_conf;
+       int ps_conf[CM36686_PS_CONF_NUM];
+       int ps_close;
+       int ps_away;
+};
+
+struct cm36686_chip_info {
+       const char *name;
+       const struct iio_chan_spec *channels;
+       const int num_channels;
+};
+
+static int cm36686_current_to_index(int led_current)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(cm36686_ps_led_current); i++)
+               if (led_current < cm36686_ps_led_current[i])
+                       break;
+
+       return i > 0 ? i - 1 : -EINVAL;
+}
+
+static ssize_t cm36686_read_near_level(struct iio_dev *indio_dev,
+                                      uintptr_t priv,
+                                      const struct iio_chan_spec *chan,
+                                      char *buf)
+{
+       struct cm36686_data *chip = iio_priv(indio_dev);
+
+       return sysfs_emit(buf, "%u\n", chip->ps_close);
+}
+
+static const struct iio_chan_spec_ext_info cm36686_ext_info[] = {
+       {
+               .name = "nearlevel",
+               .shared = IIO_SEPARATE,
+               .read = cm36686_read_near_level,
+       },
+       {}
+};
+
+static const struct iio_event_spec cm36686_proximity_event_spec[] = {
+       {
+               .type = IIO_EV_TYPE_THRESH,
+               .dir = IIO_EV_DIR_FALLING,
+               .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+                                BIT(IIO_EV_INFO_ENABLE),
+       },
+       {
+               .type = IIO_EV_TYPE_THRESH,
+               .dir = IIO_EV_DIR_RISING,
+               .mask_separate = BIT(IIO_EV_INFO_VALUE) |
+                                BIT(IIO_EV_INFO_ENABLE),
+       }
+};
+
+static const struct iio_chan_spec cm36686_channels[] = {
+       {
+               .type = IIO_LIGHT,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+                                     BIT(IIO_CHAN_INFO_INT_TIME),
+               .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME),
+               .address = CM36686_REG_ALS_DATA,
+       },
+       {
+               .type = IIO_PROXIMITY,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+                                     BIT(IIO_CHAN_INFO_INT_TIME),
+               .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME),
+               .address = CM36686_REG_PS_DATA,
+               .event_spec = cm36686_proximity_event_spec,
+               .num_event_specs = ARRAY_SIZE(cm36686_proximity_event_spec),
+               .ext_info = cm36686_ext_info
+       }
+};
+
+static const struct iio_chan_spec cm36672p_channels[] = {
+       {
+               .type = IIO_PROXIMITY,
+               .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+                                     BIT(IIO_CHAN_INFO_INT_TIME),
+               .info_mask_separate_available = BIT(IIO_CHAN_INFO_INT_TIME),
+               .address = CM36686_REG_PS_DATA,
+               .event_spec = cm36686_proximity_event_spec,
+               .num_event_specs = ARRAY_SIZE(cm36686_proximity_event_spec),
+               .ext_info = cm36686_ext_info
+       }
+};
+
+static int cm36686_read_avail(struct iio_dev *indio_dev,
+                             struct iio_chan_spec const *chan,
+                             const int **vals, int *type, int *length,
+                             long mask)
+{
+       if (mask != IIO_CHAN_INFO_INT_TIME)
+               return -EINVAL;
+
+       switch (chan->type) {
+       case IIO_LIGHT:
+               *vals = (int *)(cm36686_als_it_times);
+               *length = 2 * ARRAY_SIZE(cm36686_als_it_times);
+               *type = IIO_VAL_INT_PLUS_MICRO;
+               return IIO_AVAIL_LIST;
+       case IIO_PROXIMITY:
+               *vals = (int *)(cm36686_ps_it_times);
+               *length = 2 * ARRAY_SIZE(cm36686_ps_it_times);
+               *type = IIO_VAL_INT_PLUS_MICRO;
+               return IIO_AVAIL_LIST;
+       default:
+               return -EINVAL;
+       }
+}
+
+static int cm36686_read_channel(struct cm36686_data *chip,
+                               struct iio_chan_spec const *chan, int *val)
+{
+       struct i2c_client *client = chip->client;
+       int ret = IIO_VAL_INT;
+
+       int data = i2c_smbus_read_word_data(client, chan->address);
+
+       if (data < 0) {
+               dev_err(&client->dev, "Failed to read register: %pe", 
ERR_PTR(data));
+               ret = -EIO;
+       } else {
+               *val = data;
+       }
+       return ret;
+}
+
+static int cm36686_read_int_time(struct cm36686_data *chip,
+                                struct iio_chan_spec const *chan, int *val,
+                                int *val2)
+{
+       int als_it_index, ps_it_index;
+
+       switch (chan->type) {
+       case IIO_LIGHT:
+               als_it_index = FIELD_GET(CM36686_ALS_IT, chip->als_conf);
+               *val = cm36686_als_it_times[als_it_index][0];
+               *val2 = cm36686_als_it_times[als_it_index][1];
+               return IIO_VAL_INT_PLUS_MICRO;
+       case IIO_PROXIMITY:
+               ps_it_index = FIELD_GET(CM36686_PS_IT,
+                       chip->ps_conf[CM36686_PS_CONF1]);
+               *val = cm36686_ps_it_times[ps_it_index][0];
+               *val2 = cm36686_ps_it_times[ps_it_index][1];
+               return IIO_VAL_INT_PLUS_MICRO;
+       default:
+               return -EINVAL;
+       }
+}
+
+static int cm36686_write_light_int_time(struct cm36686_data *chip, int val2)
+{
+       struct i2c_client *client = chip->client;
+       int index = -1, ret, new_it_time;
+
+       for (int i = 0; i < ARRAY_SIZE(cm36686_als_it_times); i++) {
+               if (cm36686_als_it_times[i][1] == val2) {
+                       index = i;
+                       break;
+               }
+       }
+
+       if (index == -1)
+               return -EINVAL;
+
+       new_it_time = chip->als_conf & ~CM36686_ALS_IT;
+       new_it_time |= FIELD_PREP(CM36686_ALS_IT, index);
+
+       ret = i2c_smbus_write_word_data(chip->client, CM36686_REG_ALS_CONF,
+                                       new_it_time);
+       if (ret < 0)
+               dev_err(&client->dev,
+                       "Failed to set ALS integration time: %pe", 
ERR_PTR(ret));
+       else
+               chip->als_conf = new_it_time;
+
+       return ret;
+}
+
+static int cm36686_write_prox_int_time(struct cm36686_data *chip, int val2)
+{
+       struct i2c_client *client = chip->client;
+       int index = -1, ret, new_it_time;
+
+       for (int i = 0; i < ARRAY_SIZE(cm36686_ps_it_times); i++) {
+               if (cm36686_ps_it_times[i][1] == val2) {
+                       index = i;
+                       break;
+               }
+       }
+
+       if (index == -1)
+               return -EINVAL;
+
+       new_it_time = chip->ps_conf[CM36686_PS_CONF1] & ~CM36686_PS_IT;
+       new_it_time |= FIELD_PREP(CM36686_PS_IT, index);
+
+       ret = i2c_smbus_write_word_data(chip->client, CM36686_REG_PS_CONF1,
+                                       new_it_time);
+       if (ret < 0)
+               dev_err(&client->dev, "Failed to set PS integration time: %pe",
+                       ERR_PTR(ret));
+       else
+               chip->ps_conf[CM36686_PS_CONF1] = new_it_time;
+
+       return ret;
+}
+
+static int cm36686_read_raw(struct iio_dev *indio_dev,
+                           struct iio_chan_spec const *chan, int *val,
+                           int *val2, long mask)
+{
+       struct cm36686_data *chip = iio_priv(indio_dev);
+       int ret;
+
+       mutex_lock(&chip->lock);
+
+       switch (mask) {
+       case IIO_CHAN_INFO_RAW:
+               ret = cm36686_read_channel(chip, chan, val);
+               break;
+       case IIO_CHAN_INFO_INT_TIME:
+               ret = cm36686_read_int_time(chip, chan, val, val2);
+               break;
+       default:
+               ret = -EINVAL;
+       }
+
+       mutex_unlock(&chip->lock);
+       return ret;
+}
+
+static int cm36686_write_raw(struct iio_dev *indio_dev,
+                            struct iio_chan_spec const *chan, int val,
+                            int val2, long mask)
+{
+       struct cm36686_data *chip = iio_priv(indio_dev);
+       int ret;
+
+       if (val) /* Integration time more than 1s is not supported */
+               return -EINVAL;
+
+       if (mask != IIO_CHAN_INFO_INT_TIME)
+               return -EINVAL;
+
+       mutex_lock(&chip->lock);
+
+       switch (chan->type) {
+       case IIO_LIGHT:
+               ret = cm36686_write_light_int_time(chip, val2);
+               break;
+       case IIO_PROXIMITY:
+               ret = cm36686_write_prox_int_time(chip, val2);
+               break;
+       default:
+               ret = -EINVAL;
+       }
+
+       mutex_unlock(&chip->lock);
+       return ret;
+}
+
+static int cm36686_read_prox_thresh(struct iio_dev *indio_dev,
+                                   const struct iio_chan_spec *chan,
+                                   enum iio_event_type type,
+                                   enum iio_event_direction dir,
+                                   enum iio_event_info info, int *val,
+                                   int *val2)
+{
+       struct cm36686_data *chip = iio_priv(indio_dev);
+
+       if (chan->type != IIO_PROXIMITY)
+               return -EINVAL;
+
+       switch (dir) {
+       case IIO_EV_DIR_RISING:
+               *val = chip->ps_close;
+               break;
+       case IIO_EV_DIR_FALLING:
+               *val = chip->ps_away;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return IIO_VAL_INT;
+}
+
+static int cm36686_write_prox_thresh(struct iio_dev *indio_dev,
+                                    const struct iio_chan_spec *chan,
+                                    enum iio_event_type type,
+                                    enum iio_event_direction dir,
+                                    enum iio_event_info info, int val,
+                                    int val2)
+{
+       struct cm36686_data *chip = iio_priv(indio_dev);
+       struct i2c_client *client = chip->client;
+       int ret = 0, address, *thresh;
+
+       if (chan->type != IIO_PROXIMITY)
+               return -EINVAL;
+
+       switch (dir) {
+       case IIO_EV_DIR_FALLING:
+               if (val > chip->ps_close || val < 0)
+                       return -EINVAL;
+
+               address = CM36686_REG_PS_THDL;
+               thresh = &chip->ps_away;
+               break;
+       case IIO_EV_DIR_RISING:
+               if (val < chip->ps_away || val > CM36686_MAX_PS_VALUE)
+                       return -EINVAL;
+
+               address = CM36686_REG_PS_THDH;
+               thresh = &chip->ps_close;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       mutex_lock(&chip->lock);
+
+       ret = i2c_smbus_write_word_data(client, address, val);
+       if (ret < 0)
+               dev_err(&client->dev,
+                       "Failed to set PS threshold value: %pe", ERR_PTR(ret));
+       else
+               *thresh = val;
+
+       mutex_unlock(&chip->lock);
+       return ret;
+}
+
+static int cm36686_read_prox_event_config(struct iio_dev *indio_dev,
+                                         const struct iio_chan_spec *chan,
+                                         enum iio_event_type type,
+                                         enum iio_event_direction dir)
+{
+       struct cm36686_data *chip = iio_priv(indio_dev);
+
+       if (chan->type != IIO_PROXIMITY)
+               return -EINVAL;
+
+       switch (dir) {
+       case IIO_EV_DIR_FALLING:
+               return FIELD_GET(CM36686_PS_INT_OUT, 
chip->ps_conf[CM36686_PS_CONF1]);
+       case IIO_EV_DIR_RISING:
+               return FIELD_GET(CM36686_PS_INT_IN, 
chip->ps_conf[CM36686_PS_CONF1]);
+       default:
+               return -EINVAL;
+       }
+}
+
+static int cm36686_write_prox_event_config(struct iio_dev *indio_dev,
+                                          const struct iio_chan_spec *chan,
+                                          enum iio_event_type type,
+                                          enum iio_event_direction dir,
+                                          bool state)
+{
+       struct cm36686_data *chip = iio_priv(indio_dev);
+       struct i2c_client *client = chip->client;
+       int ret = 0, new_ps_conf;
+
+       if (chan->type != IIO_PROXIMITY)
+               return -EINVAL;
+
+       switch (dir) {
+       case IIO_EV_DIR_FALLING:
+               new_ps_conf = chip->ps_conf[CM36686_PS_CONF1] & 
~CM36686_PS_INT_OUT;
+               new_ps_conf |= FIELD_PREP(CM36686_PS_INT_OUT, state);
+               break;
+       case IIO_EV_DIR_RISING:
+               new_ps_conf = chip->ps_conf[CM36686_PS_CONF1] & 
~CM36686_PS_INT_IN;
+               new_ps_conf |= FIELD_PREP(CM36686_PS_INT_IN, state);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       mutex_lock(&chip->lock);
+
+       ret = i2c_smbus_write_word_data(chip->client, CM36686_REG_PS_CONF1, 
new_ps_conf);
+       if (ret < 0)
+               dev_err(&client->dev,
+                       "Failed to set proximity event interrupt config: %pe", 
ERR_PTR(ret));
+       else
+               chip->ps_conf[CM36686_PS_CONF1] = new_ps_conf;
+
+       mutex_unlock(&chip->lock);
+
+       return ret;
+}
+
+static int cm36686_fallback_read_ps(struct iio_dev *indio_dev)
+{
+       struct cm36686_data *chip = iio_priv(indio_dev);
+       struct i2c_client *client = chip->client;
+       int data = i2c_smbus_read_word_data(client, CM36686_REG_PS_DATA);
+
+       if (data < 0)
+               return data;
+
+       if (data < chip->ps_away)
+               return IIO_EV_DIR_FALLING;
+       else if (data > chip->ps_close)
+               return IIO_EV_DIR_RISING;
+       else
+               return IIO_EV_DIR_EITHER;
+}
+
+static irqreturn_t cm36686_irq_handler(int irq, void *data)
+{
+       struct iio_dev *indio_dev = data;
+       struct cm36686_data *chip = iio_priv(indio_dev);
+       struct i2c_client *client = chip->client;
+       int ev_dir, ret;
+       u64 ev_code;
+
+       /* Reading the interrupt flag acknowledges the interrupt */
+       ret = i2c_smbus_read_word_data(client, CM36686_REG_INT_FLAG);
+       if (ret < 0) {
+               dev_err(&client->dev,
+                       "Interrupt flag register read failed: %pe", 
ERR_PTR(ret));
+               return IRQ_HANDLED;
+       }
+
+       ret >>= CM36686_INT_FLAG_SHIFT;
+       switch (ret) {
+       case CM36686_CLOSE:
+               ev_dir = IIO_EV_DIR_RISING;
+               break;
+       case CM36686_AWAY:
+               ev_dir = IIO_EV_DIR_FALLING;
+               break;
+       case CM36686_BOTH:
+               ev_dir = cm36686_fallback_read_ps(indio_dev);
+               if (ev_dir < 0) {
+                       dev_err(&client->dev, "Failed to settle interrupt 
state: %pe",
+                               ERR_PTR(ret));
+                       return IRQ_HANDLED;
+               }
+               break;
+       default:
+               dev_err(&client->dev, "Unknown interrupt state: %x", ret);
+               return IRQ_HANDLED;
+       }
+       ev_code = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, IIO_EV_INFO_VALUE,
+                                      IIO_EV_TYPE_THRESH, ev_dir);
+
+       iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev));
+       return IRQ_HANDLED;
+}
+
+static const struct iio_info cm36686_info = {
+       .read_avail =           cm36686_read_avail,
+       .read_raw =             cm36686_read_raw,
+       .write_raw =            cm36686_write_raw,
+       .read_event_value =     cm36686_read_prox_thresh,
+       .write_event_value =    cm36686_write_prox_thresh,
+       .read_event_config =    cm36686_read_prox_event_config,
+       .write_event_config =   cm36686_write_prox_event_config,
+};
+
+static struct cm36686_chip_info cm36686_chip_info_tbl[] = {
+       [CM36686] = {
+               .name = "cm36686",
+               .channels = cm36686_channels,
+               .num_channels = ARRAY_SIZE(cm36686_channels),
+       },
+       [CM36672P] = {
+               .name = "cm36672p",
+               .channels = cm36672p_channels,
+               .num_channels = ARRAY_SIZE(cm36672p_channels),
+       },
+};
+
+static int cm36686_setup(struct cm36686_data *chip)
+{
+       struct i2c_client *client = chip->client;
+       int ret, led_current, led_index;
+
+       chip->als_conf = CM36686_ALS_ENABLE;
+
+       ret = i2c_smbus_write_word_data(client, CM36686_REG_ALS_CONF,
+                                       chip->als_conf);
+       if (ret < 0) {
+               dev_err(&client->dev,
+                       "Failed to enable ambient light sensor: %pe", 
ERR_PTR(ret));
+               return ret;
+       }
+
+       chip->ps_conf[CM36686_PS_CONF1] = CM36686_PS_INT_IN |
+               CM36686_PS_INT_OUT | CM36686_PS_DR_1_320 |
+               CM36686_PS_PERS_2 | CM36686_PS_IT_2_5T;
+
+       ret = i2c_smbus_write_word_data(client, CM36686_REG_PS_CONF1,
+                                       chip->ps_conf[CM36686_PS_CONF1]);
+       if (ret < 0) {
+               dev_err(&client->dev,
+                       "Failed to enable proximity sensor: %pe", ERR_PTR(ret));
+               return ret;
+       }
+
+       chip->ps_conf[CM36686_PS_CONF3] = CM36686_PS_SMART_PERS_ENABLE;
+
+       ret = device_property_read_u32(&client->dev,
+               "capella,proximity-led-current", &led_current);
+       if (!ret) {
+               led_index = cm36686_current_to_index(led_current);
+               if (led_index < 0) {
+                       dev_err(&client->dev, "No appropriate current for IR 
LED found.");
+                       return led_index;
+               }
+
+               chip->ps_conf[CM36686_PS_CONF3] &= ~CM36686_LED_I;
+               chip->ps_conf[CM36686_PS_CONF3] |= FIELD_PREP(CM36686_LED_I, 
led_index);
+       }
+
+       ret = i2c_smbus_write_word_data(client, CM36686_REG_PS_CONF3,
+                                       chip->ps_conf[CM36686_PS_CONF3]);
+       if (ret < 0) {
+               dev_err(&client->dev,
+                       "Failed to enable proximity sensor: %pe", ERR_PTR(ret));
+               return ret;
+       }
+
+       ret = device_property_read_u32(&client->dev, "proximity-near-level",
+                                           &chip->ps_close);
+       if (ret < 0)
+               chip->ps_close = 0;
+
+       ret = i2c_smbus_write_word_data(client, CM36686_REG_PS_THDH,
+               chip->ps_close);
+       if (ret < 0) {
+               dev_err(&client->dev,
+                       "Failed to set close proximity threshold: %pe", 
ERR_PTR(ret));
+               return ret;
+       }
+
+       chip->ps_away = chip->ps_close;
+
+       ret = i2c_smbus_write_word_data(client, CM36686_REG_PS_THDL,
+               chip->ps_away);
+       if (ret < 0) {
+               dev_err(&client->dev,
+                       "Failed to set away proximity threshold: %pe", 
ERR_PTR(ret));
+               return ret;
+       }
+
+       return 0;
+}
+
+static void cm36686_shutdown(void *data)
+{
+       struct cm36686_data *chip = data;
+       struct i2c_client *client = chip->client;
+       int ret, als_shutdown, ps_shutdown;
+
+       als_shutdown = chip->als_conf | CM36686_ALS_SD;
+
+       ret = i2c_smbus_write_word_data(client, CM36686_REG_ALS_CONF,
+                                       als_shutdown);
+       if (ret < 0)
+               dev_err(&client->dev, "Failed to shutdown ALS");
+
+       ps_shutdown = chip->ps_conf[CM36686_PS_CONF1] | CM36686_PS_SD;
+
+       ret = i2c_smbus_write_word_data(client, CM36686_REG_PS_CONF1,
+                                       ps_shutdown);
+       if (ret < 0)
+               dev_err(&client->dev, "Failed to shutdown PS");
+
+       ret = regulator_bulk_disable(ARRAY_SIZE(chip->supplies), 
chip->supplies);
+       if (ret < 0)
+               dev_err(&client->dev, "Failed to disable regulators");
+}
+
+static int cm36686_probe(struct i2c_client *client)
+{
+       struct iio_dev *indio_dev;
+       struct cm36686_data *chip;
+       int ret;
+
+       indio_dev = devm_iio_device_alloc(&client->dev,
+                                         sizeof(struct cm36686_data));
+       if (!indio_dev)
+               return -ENOMEM;
+
+       chip = iio_priv(indio_dev);
+
+       const struct i2c_device_id *id = i2c_client_get_device_id(client);
+
+       const struct cm36686_chip_info *info =
+               &cm36686_chip_info_tbl[id->driver_data];
+
+       ret = i2c_smbus_read_byte_data(client, CM36686_REG_ID_FLAG);
+       if (ret < 0)
+               return dev_err_probe(&client->dev, ret, "Failed to read device 
ID");
+
+       if (ret != CM36686_DEVICE_ID)
+               return dev_err_probe(&client->dev, -ENODEV, "Device not 
recognized!");
+
+       i2c_set_clientdata(client, indio_dev);
+       chip->client = client;
+       ret = devm_mutex_init(&client->dev, &chip->lock);
+       if (ret < 0)
+               return dev_err_probe(&client->dev, ret,
+                      "Failed to initialize mutex");
+
+       chip->supplies[CM36686_SUPPLY_VDD].supply = "vdd";
+       chip->supplies[CM36686_SUPPLY_VDDIO].supply = "vddio";
+       chip->supplies[CM36686_SUPPLY_VLED].supply = "vled";
+
+       ret = devm_regulator_bulk_get(&client->dev,
+               ARRAY_SIZE(chip->supplies), chip->supplies);
+       if (ret < 0)
+               return dev_err_probe(&client->dev, ret,
+                                    "Failed to enable regulators");
+
+       ret = devm_add_action_or_reset(&client->dev, cm36686_shutdown, chip);
+       if (ret)
+               return dev_err_probe(&client->dev, ret,
+                                    "Failed to set shutdown action");
+
+       ret = cm36686_setup(chip);
+       if (ret < 0)
+               return dev_err_probe(&client->dev, ret,
+                                    "Failed to set up registers");
+
+       indio_dev->name = info->name;
+       indio_dev->channels = info->channels;
+       indio_dev->num_channels = info->num_channels;
+       indio_dev->info = &cm36686_info;
+       indio_dev->modes = INDIO_DIRECT_MODE;
+
+       ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+                                       cm36686_irq_handler,
+                                       IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+                                       indio_dev->name, indio_dev);
+       if (ret)
+               return dev_err_probe(&client->dev, ret,
+                                    "Failed to request irq");
+
+       ret = devm_iio_device_register(&client->dev, indio_dev);
+       if (ret)
+               return dev_err_probe(&client->dev, ret,
+                                    "Failed to register iio device");
+
+       return 0;
+}
+
+static const struct i2c_device_id cm36686_id[] = {
+       { "cm36686", CM36686 },
+       { "cm36672p", CM36672P },
+       {}
+};
+
+MODULE_DEVICE_TABLE(i2c, cm36686_id);
+
+static const struct of_device_id cm36686_of_match[] = {
+       { .compatible = "capella,cm36686" },
+       { .compatible = "capella,cm36672p" },
+       {}
+};
+MODULE_DEVICE_TABLE(of, cm36686_of_match);
+
+static struct i2c_driver cm36686_driver = {
+       .driver = {
+               .name = "cm36686",
+               .of_match_table = cm36686_of_match,
+       },
+       .probe = cm36686_probe,
+       .id_table = cm36686_id
+};
+
+module_i2c_driver(cm36686_driver);
+
+MODULE_AUTHOR("Erikas Bitovtas <[email protected]>");
+MODULE_DESCRIPTION("CM36686 ambient light and proximity sensor driver");
+MODULE_LICENSE("GPL");

-- 
2.52.0


Reply via email to