1) Add support for configuring the gain and resolution(integration time) for the sensor. 2) Add a channel for ALS and provide support for reading the raw and scale values. 3) Add automatic mode switching between UVS and ALS based on the channel type. 4) Calculate 'counts_per_uvi' based on the current gain and integration time. Signed-off-by: Abhash Jha <abhashkumarjha123@xxxxxxxxx> --- drivers/iio/light/ltr390.c | 256 ++++++++++++++++++++++++++++++++++--- 1 file changed, 238 insertions(+), 18 deletions(-) diff --git a/drivers/iio/light/ltr390.c b/drivers/iio/light/ltr390.c index fff1e8990..56f3c74ae 100644 --- a/drivers/iio/light/ltr390.c +++ b/drivers/iio/light/ltr390.c @@ -25,19 +25,33 @@ #include <linux/regmap.h> #include <linux/iio/iio.h> - +#include <linux/iio/sysfs.h> #include <asm/unaligned.h> #define LTR390_MAIN_CTRL 0x00 #define LTR390_PART_ID 0x06 #define LTR390_UVS_DATA 0x10 +#define LTR390_ALS_DATA 0x0D +#define LTR390_ALS_UVS_GAIN 0x05 +#define LTR390_ALS_UVS_MEAS_RATE 0x04 +#define LTR390_INT_CFG 0x19 + #define LTR390_SW_RESET BIT(4) #define LTR390_UVS_MODE BIT(3) #define LTR390_SENSOR_ENABLE BIT(1) #define LTR390_PART_NUMBER_ID 0xb +#define LTR390_ALS_UVS_GAIN_MASK 0x07 +#define LTR390_ALS_UVS_INT_TIME_MASK 0x70 +#define LTR390_ALS_UVS_INT_TIME_MASK_SHIFT 4 + +#define LTR390_SET_ALS_MODE 1 +#define LTR390_SET_UVS_MODE 2 + +#define LTR390_FRACTIONAL_PRECISION 100 + /* * At 20-bit resolution (integration time: 400ms) and 18x gain, 2300 counts of * the sensor are equal to 1 UV Index [Datasheet Page#8]. @@ -60,6 +74,9 @@ struct ltr390_data { struct i2c_client *client; /* Protects device from simulataneous reads */ struct mutex lock; + int mode; + int gain; + int int_time_us; }; static const struct regmap_config ltr390_regmap_config = { @@ -87,36 +104,232 @@ static int ltr390_register_read(struct ltr390_data *data, u8 register_address) return get_unaligned_le24(recieve_buffer); } + +static int ltr390_set_mode(struct ltr390_data *data, int mode) +{ + if (data->mode == mode) + return 0; + + if (mode == LTR390_SET_ALS_MODE) { + regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_UVS_MODE); + data->mode = LTR390_SET_ALS_MODE; + } else if (mode == LTR390_SET_UVS_MODE) { + regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_UVS_MODE); + data->mode = LTR390_SET_UVS_MODE; + } else { + return -EINVAL; + } + + return 0; +} + +static int ltr390_counts_per_uvi(struct ltr390_data *data) +{ + int orig_gain = 18; + int orig_int_time = 400; + int divisor = orig_gain * orig_int_time; + int gain = data->gain; + + int int_time_ms = DIV_ROUND_CLOSEST(data->int_time_us, 1000); + int uvi = DIV_ROUND_CLOSEST(2300*gain*int_time_ms, divisor); + + return uvi; +} + static int ltr390_read_raw(struct iio_dev *iio_device, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { - int ret; struct ltr390_data *data = iio_priv(iio_device); + int ret; switch (mask) { case IIO_CHAN_INFO_RAW: - ret = ltr390_register_read(data, LTR390_UVS_DATA); - if (ret < 0) - return ret; + switch (chan->type) { + case IIO_UVINDEX: + ret = ltr390_set_mode(data, LTR390_SET_UVS_MODE); + if (ret < 0) + return ret; + + ret = ltr390_register_read(data, LTR390_UVS_DATA); + if (ret < 0) + return ret; + + break; + + case IIO_INTENSITY: + ret = ltr390_set_mode(data, LTR390_SET_ALS_MODE); + if (ret < 0) + return ret; + + ret = ltr390_register_read(data, LTR390_ALS_DATA); + if (ret < 0) + return ret; + break; + + default: + ret = -EINVAL; + } + *val = ret; - return IIO_VAL_INT; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: - *val = LTR390_WINDOW_FACTOR; - *val2 = LTR390_COUNTS_PER_UVI; - return IIO_VAL_FRACTIONAL; + mutex_lock(&data->lock); + + switch (chan->type) { + case IIO_UVINDEX: + ret = ltr390_set_mode(data, LTR390_SET_UVS_MODE); + if (ret < 0) + return ret; + + *val = LTR390_WINDOW_FACTOR * LTR390_FRACTIONAL_PRECISION; + *val2 = ltr390_counts_per_uvi(data); + ret = IIO_VAL_FRACTIONAL; + break; + + case IIO_INTENSITY: + ret = ltr390_set_mode(data, LTR390_SET_ALS_MODE); + if (ret < 0) + return ret; + + *val = LTR390_WINDOW_FACTOR; + *val2 = data->gain; + + ret = IIO_VAL_FRACTIONAL; + break; + + default: + ret = -EINVAL; + } + + mutex_unlock(&data->lock); + break; + + case IIO_CHAN_INFO_INT_TIME: + mutex_lock(&data->lock); + *val = data->int_time_us; + mutex_unlock(&data->lock); + ret = IIO_VAL_INT; + break; + default: - return -EINVAL; + ret = -EINVAL; } + + return ret; } -static const struct iio_info ltr390_info = { - .read_raw = ltr390_read_raw, +/* integration time in us */ +static const int ltr390_int_time_map_us[] = {400000, 200000, 100000, 50000, 25000, 12500}; +static const int ltr390_gain_map[] = {1, 3, 6, 9, 18}; + +static IIO_CONST_ATTR_INT_TIME_AVAIL("400000 200000 100000 50000 25000 12500"); +static IIO_CONST_ATTR(gain_available, "1 3 6 9 18"); + +static struct attribute *ltr390_attributes[] = { + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_const_attr_gain_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ltr390_attribute_group = { + .attrs = ltr390_attributes, }; -static const struct iio_chan_spec ltr390_channel = { +static const struct iio_chan_spec ltr390_channels[] = { + /* UV sensor */ + { .type = IIO_UVINDEX, - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE) + .scan_index = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) + }, + /* ALS sensor */ + { + .type = IIO_INTENSITY, + .scan_index = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) + }, +}; + +static int ltr390_set_gain(struct ltr390_data *data, int val) +{ + int ret, idx; + + for (idx = 0; idx < ARRAY_SIZE(ltr390_gain_map); idx++) { + if (ltr390_gain_map[idx] == val) { + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, + LTR390_ALS_UVS_GAIN, + LTR390_ALS_UVS_GAIN_MASK, idx); + if (!ret) + data->gain = ltr390_gain_map[idx]; + + mutex_unlock(&data->lock); + break; + } + } + + return 0; +} + +static int ltr390_set_int_time(struct ltr390_data *data, int val) +{ + int ret, idx; + + for (idx = 0; idx < ARRAY_SIZE(ltr390_int_time_map_us); idx++) { + if (ltr390_int_time_map_us[idx] == val) { + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, + LTR390_ALS_UVS_MEAS_RATE, + LTR390_ALS_UVS_INT_TIME_MASK, + idx<<LTR390_ALS_UVS_INT_TIME_MASK_SHIFT); + if (!ret) + data->int_time_us = ltr390_int_time_map_us[idx]; + + mutex_unlock(&data->lock); + break; + } + } + + return 0; +} + +static int ltr390_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ltr390_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (val2 != 0) + ret = -EINVAL; + + ret = ltr390_set_gain(data, val); + break; + + case IIO_CHAN_INFO_INT_TIME: + if (val2 != 0) + ret = -EINVAL; + + ret = ltr390_set_int_time(data, val); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static const struct iio_info ltr390_info = { + .attrs = <r390_attribute_group, + .read_raw = ltr390_read_raw, + .write_raw = ltr390_write_raw, }; static int ltr390_probe(struct i2c_client *client) @@ -139,11 +352,18 @@ static int ltr390_probe(struct i2c_client *client) "regmap initialization failed\n"); data->client = client; + /* default value of int time from pg: 15 of the datasheet */ + data->int_time_us = 100000; + /* default value of gain from pg: 16 of the datasheet */ + data->gain = 3; + /* default mode for ltr390 is ALS mode */ + data->mode = LTR390_SET_ALS_MODE; + mutex_init(&data->lock); indio_dev->info = <r390_info; - indio_dev->channels = <r390_channel; - indio_dev->num_channels = 1; + indio_dev->channels = ltr390_channels; + indio_dev->num_channels = ARRAY_SIZE(ltr390_channels); indio_dev->name = "ltr390"; ret = regmap_read(data->regmap, LTR390_PART_ID, &part_number); @@ -161,8 +381,7 @@ static int ltr390_probe(struct i2c_client *client) /* Wait for the registers to reset before proceeding */ usleep_range(1000, 2000); - ret = regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, - LTR390_SENSOR_ENABLE | LTR390_UVS_MODE); + ret = regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE); if (ret) return dev_err_probe(dev, ret, "failed to enable the sensor\n"); @@ -189,6 +408,7 @@ static struct i2c_driver ltr390_driver = { .probe = ltr390_probe, .id_table = ltr390_id, }; + module_i2c_driver(ltr390_driver); MODULE_AUTHOR("Anshul Dalal <anshulusr@xxxxxxxxx>"); -- 2.43.0