OSDN Git Service

iio: light: Add support for ltrf216a sensor
authorShreeya Patel <shreeya.patel@collabora.com>
Mon, 25 Jul 2022 10:40:50 +0000 (16:10 +0530)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Mon, 15 Aug 2022 21:29:57 +0000 (22:29 +0100)
Add initial support for ltrf216a ambient light sensor.

Datasheet: https://gitlab.collabora.com/shreeya/iio/-/blob/master/LTRF216A.pdf
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Co-developed-by: Zhigang Shi <Zhigang.Shi@liteon.com>
Signed-off-by: Zhigang Shi <Zhigang.Shi@liteon.com>
Co-developed-by: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Signed-off-by: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Signed-off-by: Shreeya Patel <shreeya.patel@collabora.com>
Link: https://lore.kernel.org/r/20220725104050.491396-3-shreeya.patel@collabora.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/light/Kconfig
drivers/iio/light/Makefile
drivers/iio/light/ltrf216a.c [new file with mode: 0644]

index 8537e88..7cf6e84 100644 (file)
@@ -331,6 +331,17 @@ config LTR501
          This driver can also be built as a module.  If so, the module
          will be called ltr501.
 
+config LTRF216A
+       tristate "Liteon LTRF216A Light Sensor"
+       depends on I2C
+       select REGMAP_I2C
+       help
+         If you say Y or M here, you get support for Liteon LTRF216A
+         Ambient Light Sensor.
+
+         If built as a dynamically linked module, it will be called
+         ltrf216a.
+
 config LV0104CS
        tristate "LV0104CS Ambient Light Sensor"
        depends on I2C
index d10912f..6f23817 100644 (file)
@@ -31,6 +31,7 @@ obj-$(CONFIG_ISL29125)                += isl29125.o
 obj-$(CONFIG_JSA1212)          += jsa1212.o
 obj-$(CONFIG_SENSORS_LM3533)   += lm3533-als.o
 obj-$(CONFIG_LTR501)           += ltr501.o
+obj-$(CONFIG_LTRF216A)         += ltrf216a.o
 obj-$(CONFIG_LV0104CS)         += lv0104cs.o
 obj-$(CONFIG_MAX44000)         += max44000.o
 obj-$(CONFIG_MAX44009)         += max44009.o
diff --git a/drivers/iio/light/ltrf216a.c b/drivers/iio/light/ltrf216a.c
new file mode 100644 (file)
index 0000000..e6e24e7
--- /dev/null
@@ -0,0 +1,537 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LTRF216A Ambient Light Sensor
+ *
+ * Copyright (C) 2022 Collabora, Ltd.
+ * Author: Shreeya Patel <shreeya.patel@collabora.com>
+ *
+ * Copyright (C) 2021 Lite-On Technology Corp (Singapore)
+ * Author: Shi Zhigang <Zhigang.Shi@liteon.com>
+ *
+ * IIO driver for LTRF216A (7-bit I2C slave address 0x53).
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/iopoll.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <linux/iio/iio.h>
+
+#include <asm/unaligned.h>
+
+#define LTRF216A_ALS_RESET_MASK                BIT(4)
+#define LTRF216A_ALS_DATA_STATUS       BIT(3)
+#define LTRF216A_ALS_ENABLE_MASK       BIT(1)
+#define LTRF216A_MAIN_CTRL             0x00
+#define LTRF216A_ALS_MEAS_RES          0x04
+#define LTRF216A_ALS_GAIN              0x05
+#define LTRF216A_PART_ID               0x06
+#define LTRF216A_MAIN_STATUS           0x07
+#define LTRF216A_ALS_CLEAR_DATA_0      0x0a
+#define LTRF216A_ALS_CLEAR_DATA_1      0x0b
+#define LTRF216A_ALS_CLEAR_DATA_2      0x0c
+#define LTRF216A_ALS_DATA_0            0x0d
+#define LTRF216A_ALS_DATA_1            0x0e
+#define LTRF216A_ALS_DATA_2            0x0f
+#define LTRF216A_INT_CFG               0x19
+#define LTRF216A_INT_PST               0x1a
+#define LTRF216A_ALS_THRES_UP_0                0x21
+#define LTRF216A_ALS_THRES_UP_1                0x22
+#define LTRF216A_ALS_THRES_UP_2                0x23
+#define LTRF216A_ALS_THRES_LOW_0       0x24
+#define LTRF216A_ALS_THRES_LOW_1       0x25
+#define LTRF216A_ALS_THRES_LOW_2       0x26
+#define LTRF216A_ALS_READ_DATA_DELAY_US        20000
+
+static const int ltrf216a_int_time_available[][2] = {
+       { 0, 400000 },
+       { 0, 200000 },
+       { 0, 100000 },
+       { 0,  50000 },
+       { 0,  25000 },
+};
+
+static const int ltrf216a_int_time_reg[][2] = {
+       { 400, 0x03 },
+       { 200, 0x13 },
+       { 100, 0x22 },
+       {  50, 0x31 },
+       {  25, 0x40 },
+};
+
+/*
+ * Window Factor is needed when the device is under Window glass
+ * with coated tinted ink. This is to compensate for the light loss
+ * due to the lower transmission rate of the window glass and helps
+ * in calculating lux.
+ */
+#define LTRF216A_WIN_FAC       1
+
+struct ltrf216a_data {
+       struct regmap *regmap;
+       struct i2c_client *client;
+       u32 int_time;
+       u16 int_time_fac;
+       u8 als_gain_fac;
+       /*
+        * Protects regmap accesses and makes sure integration time
+        * remains constant during the measurement of lux.
+        */
+       struct mutex lock;
+};
+
+static const struct iio_chan_spec ltrf216a_channels[] = {
+       {
+               .type = IIO_LIGHT,
+               .info_mask_separate =
+                       BIT(IIO_CHAN_INFO_PROCESSED) |
+                       BIT(IIO_CHAN_INFO_INT_TIME),
+               .info_mask_separate_available =
+                       BIT(IIO_CHAN_INFO_INT_TIME),
+       },
+};
+
+static void ltrf216a_reset(struct iio_dev *indio_dev)
+{
+       struct ltrf216a_data *data = iio_priv(indio_dev);
+
+       /* reset sensor, chip fails to respond to this, so ignore any errors */
+       regmap_write(data->regmap, LTRF216A_MAIN_CTRL, LTRF216A_ALS_RESET_MASK);
+
+       /* reset time */
+       usleep_range(1000, 2000);
+}
+
+static int ltrf216a_enable(struct iio_dev *indio_dev)
+{
+       struct ltrf216a_data *data = iio_priv(indio_dev);
+       struct device *dev = &data->client->dev;
+       int ret;
+
+       /* enable sensor */
+       ret = regmap_set_bits(data->regmap,
+                             LTRF216A_MAIN_CTRL, LTRF216A_ALS_ENABLE_MASK);
+       if (ret) {
+               dev_err(dev, "failed to enable sensor: %d\n", ret);
+               return ret;
+       }
+
+       /* sleep for one integration cycle after enabling the device */
+       msleep(ltrf216a_int_time_reg[0][0]);
+
+       return 0;
+}
+
+static int ltrf216a_disable(struct iio_dev *indio_dev)
+{
+       struct ltrf216a_data *data = iio_priv(indio_dev);
+       struct device *dev = &data->client->dev;
+       int ret;
+
+       ret = regmap_write(data->regmap, LTRF216A_MAIN_CTRL, 0);
+       if (ret)
+               dev_err(dev, "failed to disable sensor: %d\n", ret);
+
+       return ret;
+}
+
+static void ltrf216a_cleanup(void *data)
+{
+       struct iio_dev *indio_dev = data;
+
+       ltrf216a_disable(indio_dev);
+}
+
+static int ltrf216a_set_int_time(struct ltrf216a_data *data, int itime)
+{
+       struct device *dev = &data->client->dev;
+       unsigned int i;
+       u8 reg_val;
+       int ret;
+
+       for (i = 0; i < ARRAY_SIZE(ltrf216a_int_time_available); i++) {
+               if (ltrf216a_int_time_available[i][1] == itime)
+                       break;
+       }
+       if (i == ARRAY_SIZE(ltrf216a_int_time_available))
+               return -EINVAL;
+
+       reg_val = ltrf216a_int_time_reg[i][1];
+
+       ret = regmap_write(data->regmap, LTRF216A_ALS_MEAS_RES, reg_val);
+       if (ret) {
+               dev_err(dev, "failed to set integration time: %d\n", ret);
+               return ret;
+       }
+
+       data->int_time_fac = ltrf216a_int_time_reg[i][0];
+       data->int_time = itime;
+
+       return 0;
+}
+
+static int ltrf216a_get_int_time(struct ltrf216a_data *data,
+                                int *val, int *val2)
+{
+       *val = 0;
+       *val2 = data->int_time;
+       return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int ltrf216a_set_power_state(struct ltrf216a_data *data, bool on)
+{
+       struct device *dev = &data->client->dev;
+       int ret = 0;
+
+       if (on) {
+               ret = pm_runtime_resume_and_get(dev);
+               if (ret) {
+                       dev_err(dev, "failed to resume runtime PM: %d\n", ret);
+                       return ret;
+               }
+       } else {
+               pm_runtime_mark_last_busy(dev);
+               pm_runtime_put_autosuspend(dev);
+       }
+
+       return ret;
+}
+
+static int ltrf216a_read_data(struct ltrf216a_data *data, u8 addr)
+{
+       struct device *dev = &data->client->dev;
+       int ret, val;
+       u8 buf[3];
+
+       ret = regmap_read_poll_timeout(data->regmap, LTRF216A_MAIN_STATUS,
+                                      val, val & LTRF216A_ALS_DATA_STATUS,
+                                      LTRF216A_ALS_READ_DATA_DELAY_US,
+                                      LTRF216A_ALS_READ_DATA_DELAY_US * 50);
+       if (ret) {
+               dev_err(dev, "failed to wait for measurement data: %d\n", ret);
+               return ret;
+       }
+
+       ret = regmap_bulk_read(data->regmap, addr, buf, sizeof(buf));
+       if (ret) {
+               dev_err(dev, "failed to read measurement data: %d\n", ret);
+               return ret;
+       }
+
+       return get_unaligned_le24(&buf[0]);
+}
+
+static int ltrf216a_get_lux(struct ltrf216a_data *data)
+{
+       int ret, greendata;
+       u64 lux, div;
+
+       ret = ltrf216a_set_power_state(data, true);
+       if (ret)
+               return ret;
+
+       greendata = ltrf216a_read_data(data, LTRF216A_ALS_DATA_0);
+       if (greendata < 0)
+               return greendata;
+
+       ltrf216a_set_power_state(data, false);
+
+       lux = greendata * 45 * LTRF216A_WIN_FAC * 100;
+       div = data->als_gain_fac * data->int_time_fac * 100;
+
+       return div_u64(lux, div);
+}
+
+static int ltrf216a_read_raw(struct iio_dev *indio_dev,
+                            struct iio_chan_spec const *chan, int *val,
+                            int *val2, long mask)
+{
+       struct ltrf216a_data *data = iio_priv(indio_dev);
+       int ret;
+
+       switch (mask) {
+       case IIO_CHAN_INFO_PROCESSED:
+               mutex_lock(&data->lock);
+               ret = ltrf216a_get_lux(data);
+               mutex_unlock(&data->lock);
+               if (ret < 0)
+                       return ret;
+               *val = ret;
+               return IIO_VAL_INT;
+       case IIO_CHAN_INFO_INT_TIME:
+               mutex_lock(&data->lock);
+               ret = ltrf216a_get_int_time(data, val, val2);
+               mutex_unlock(&data->lock);
+               return ret;
+       default:
+               return -EINVAL;
+       }
+}
+
+static int ltrf216a_write_raw(struct iio_dev *indio_dev,
+                             struct iio_chan_spec const *chan, int val,
+                             int val2, long mask)
+{
+       struct ltrf216a_data *data = iio_priv(indio_dev);
+       int ret;
+
+       switch (mask) {
+       case IIO_CHAN_INFO_INT_TIME:
+               if (val != 0)
+                       return -EINVAL;
+               mutex_lock(&data->lock);
+               ret = ltrf216a_set_int_time(data, val2);
+               mutex_unlock(&data->lock);
+               return ret;
+       default:
+               return -EINVAL;
+       }
+}
+
+static int ltrf216a_read_available(struct iio_dev *indio_dev,
+                                  struct iio_chan_spec const *chan,
+                                  const int **vals, int *type, int *length,
+                                  long mask)
+{
+       switch (mask) {
+       case IIO_CHAN_INFO_INT_TIME:
+               *length = ARRAY_SIZE(ltrf216a_int_time_available) * 2;
+               *vals = (const int *)ltrf216a_int_time_available;
+               *type = IIO_VAL_INT_PLUS_MICRO;
+               return IIO_AVAIL_LIST;
+       default:
+               return -EINVAL;
+       }
+}
+
+static const struct iio_info ltrf216a_info = {
+       .read_raw = ltrf216a_read_raw,
+       .write_raw = ltrf216a_write_raw,
+       .read_avail = ltrf216a_read_available,
+};
+
+static bool ltrf216a_readable_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case LTRF216A_MAIN_CTRL:
+       case LTRF216A_ALS_MEAS_RES:
+       case LTRF216A_ALS_GAIN:
+       case LTRF216A_PART_ID:
+       case LTRF216A_MAIN_STATUS:
+       case LTRF216A_ALS_CLEAR_DATA_0:
+       case LTRF216A_ALS_CLEAR_DATA_1:
+       case LTRF216A_ALS_CLEAR_DATA_2:
+       case LTRF216A_ALS_DATA_0:
+       case LTRF216A_ALS_DATA_1:
+       case LTRF216A_ALS_DATA_2:
+       case LTRF216A_INT_CFG:
+       case LTRF216A_INT_PST:
+       case LTRF216A_ALS_THRES_UP_0:
+       case LTRF216A_ALS_THRES_UP_1:
+       case LTRF216A_ALS_THRES_UP_2:
+       case LTRF216A_ALS_THRES_LOW_0:
+       case LTRF216A_ALS_THRES_LOW_1:
+       case LTRF216A_ALS_THRES_LOW_2:
+               return true;
+       default:
+               return false;
+       }
+}
+
+static bool ltrf216a_writable_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case LTRF216A_MAIN_CTRL:
+       case LTRF216A_ALS_MEAS_RES:
+       case LTRF216A_ALS_GAIN:
+       case LTRF216A_INT_CFG:
+       case LTRF216A_INT_PST:
+       case LTRF216A_ALS_THRES_UP_0:
+       case LTRF216A_ALS_THRES_UP_1:
+       case LTRF216A_ALS_THRES_UP_2:
+       case LTRF216A_ALS_THRES_LOW_0:
+       case LTRF216A_ALS_THRES_LOW_1:
+       case LTRF216A_ALS_THRES_LOW_2:
+               return true;
+       default:
+               return false;
+       }
+}
+
+static bool ltrf216a_volatile_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case LTRF216A_MAIN_STATUS:
+       case LTRF216A_ALS_CLEAR_DATA_0:
+       case LTRF216A_ALS_CLEAR_DATA_1:
+       case LTRF216A_ALS_CLEAR_DATA_2:
+       case LTRF216A_ALS_DATA_0:
+       case LTRF216A_ALS_DATA_1:
+       case LTRF216A_ALS_DATA_2:
+               return true;
+       default:
+               return false;
+       }
+}
+
+static bool ltrf216a_precious_reg(struct device *dev, unsigned int reg)
+{
+       return reg == LTRF216A_MAIN_STATUS;
+}
+
+static const struct regmap_config ltrf216a_regmap_config = {
+       .name = "ltrf216a",
+       .reg_bits = 8,
+       .val_bits = 8,
+       .cache_type = REGCACHE_RBTREE,
+       .max_register = LTRF216A_ALS_THRES_LOW_2,
+       .readable_reg = ltrf216a_readable_reg,
+       .writeable_reg = ltrf216a_writable_reg,
+       .volatile_reg = ltrf216a_volatile_reg,
+       .precious_reg = ltrf216a_precious_reg,
+       .disable_locking = true,
+};
+
+static int ltrf216a_probe(struct i2c_client *client)
+{
+       struct ltrf216a_data *data;
+       struct iio_dev *indio_dev;
+       int ret;
+
+       indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+       if (!indio_dev)
+               return -ENOMEM;
+
+       data = iio_priv(indio_dev);
+
+       data->regmap = devm_regmap_init_i2c(client, &ltrf216a_regmap_config);
+       if (IS_ERR(data->regmap))
+               return dev_err_probe(&client->dev, PTR_ERR(data->regmap),
+                                    "regmap initialization failed\n");
+
+       i2c_set_clientdata(client, indio_dev);
+       data->client = client;
+
+       mutex_init(&data->lock);
+
+       indio_dev->info = &ltrf216a_info;
+       indio_dev->name = "ltrf216a";
+       indio_dev->channels = ltrf216a_channels;
+       indio_dev->num_channels = ARRAY_SIZE(ltrf216a_channels);
+       indio_dev->modes = INDIO_DIRECT_MODE;
+
+       ret = pm_runtime_set_active(&client->dev);
+       if (ret)
+               return ret;
+
+       /* reset sensor, chip fails to respond to this, so ignore any errors */
+       ltrf216a_reset(indio_dev);
+
+       ret = regmap_reinit_cache(data->regmap, &ltrf216a_regmap_config);
+       if (ret)
+               return dev_err_probe(&client->dev, ret,
+                                    "failed to reinit regmap cache\n");
+
+       ret = ltrf216a_enable(indio_dev);
+       if (ret)
+               return ret;
+
+       ret = devm_add_action_or_reset(&client->dev, ltrf216a_cleanup,
+                                      indio_dev);
+       if (ret)
+               return ret;
+
+       ret = devm_pm_runtime_enable(&client->dev);
+       if (ret)
+               return dev_err_probe(&client->dev, ret,
+                                    "failed to enable runtime PM\n");
+
+       pm_runtime_set_autosuspend_delay(&client->dev, 1000);
+       pm_runtime_use_autosuspend(&client->dev);
+
+       data->int_time = 100000;
+       data->int_time_fac = 100;
+       data->als_gain_fac = 3;
+
+       return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static int ltrf216a_runtime_suspend(struct device *dev)
+{
+       struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+       struct ltrf216a_data *data = iio_priv(indio_dev);
+       int ret;
+
+       ret = ltrf216a_disable(indio_dev);
+       if (ret)
+               return ret;
+
+       regcache_cache_only(data->regmap, true);
+
+       return 0;
+}
+
+static int ltrf216a_runtime_resume(struct device *dev)
+{
+       struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev));
+       struct ltrf216a_data *data = iio_priv(indio_dev);
+       int ret;
+
+       regcache_cache_only(data->regmap, false);
+       regcache_mark_dirty(data->regmap);
+       ret = regcache_sync(data->regmap);
+       if (ret)
+               goto cache_only;
+
+       ret = ltrf216a_enable(indio_dev);
+       if (ret)
+               goto cache_only;
+
+       return 0;
+
+cache_only:
+       regcache_cache_only(data->regmap, true);
+
+       return ret;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(ltrf216a_pm_ops, ltrf216a_runtime_suspend,
+                                ltrf216a_runtime_resume, NULL);
+
+static const struct i2c_device_id ltrf216a_id[] = {
+       { "ltrf216a" },
+       {}
+};
+MODULE_DEVICE_TABLE(i2c, ltrf216a_id);
+
+static const struct of_device_id ltrf216a_of_match[] = {
+       { .compatible = "liteon,ltrf216a" },
+       { .compatible = "ltr,ltrf216a" },
+       {}
+};
+MODULE_DEVICE_TABLE(of, ltrf216a_of_match);
+
+static struct i2c_driver ltrf216a_driver = {
+       .driver = {
+               .name = "ltrf216a",
+               .pm = pm_ptr(&ltrf216a_pm_ops),
+               .of_match_table = ltrf216a_of_match,
+       },
+       .probe_new = ltrf216a_probe,
+       .id_table = ltrf216a_id,
+};
+module_i2c_driver(ltrf216a_driver);
+
+MODULE_AUTHOR("Shreeya Patel <shreeya.patel@collabora.com>");
+MODULE_AUTHOR("Shi Zhigang <Zhigang.Shi@liteon.com>");
+MODULE_DESCRIPTION("LTRF216A ambient light sensor driver");
+MODULE_LICENSE("GPL");