diff options
author | Shreeya Patel <shreeya.patel@collabora.com> | 2022-07-25 16:10:50 +0530 |
---|---|---|
committer | Jonathan Cameron <Jonathan.Cameron@huawei.com> | 2022-08-15 22:29:57 +0100 |
commit | 83f0bcd40d5cf88870d2f2bb8b97c772b92a3d1f (patch) | |
tree | cfaa76c2f88d4dbe9eadaace17bbe5a047e1cdda /drivers/iio/light/ltrf216a.c | |
parent | af0a61e940f8cea62c3c9a18ff5b3830b16e2087 (diff) |
iio: light: Add support for ltrf216a sensor
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>
Diffstat (limited to 'drivers/iio/light/ltrf216a.c')
-rw-r--r-- | drivers/iio/light/ltrf216a.c | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/drivers/iio/light/ltrf216a.c b/drivers/iio/light/ltrf216a.c new file mode 100644 index 000000000000..e6e24e70d2b9 --- /dev/null +++ b/drivers/iio/light/ltrf216a.c @@ -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, <rf216a_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 = <rf216a_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, <rf216a_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(<rf216a_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"); |