396 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * IIO driver for Lite-On LTR390 ALS and UV sensor4 * (7-bit I2C slave address 0x53)5 *6 * Based on the work of:7 * Shreeya Patel and Shi Zhigang (LTRF216 Driver)8 *9 * Copyright (C) 2023 Anshul Dalal <anshulusr@gmail.com>10 *11 * Datasheet:12 * https://optoelectronics.liteon.com/upload/download/DS86-2015-0004/LTR-390UV_Final_%20DS_V1%201.pdf13 *14 * TODO:15 * - Support for configurable gain and resolution16 * - Sensor suspend/resume support17 * - Add support for reading the ALS18 * - Interrupt support19 */20 21#include <linux/i2c.h>22#include <linux/math.h>23#include <linux/module.h>24#include <linux/mutex.h>25#include <linux/regmap.h>26#include <linux/bitfield.h>27 28#include <linux/iio/iio.h>29 30#include <linux/unaligned.h>31 32#define LTR390_MAIN_CTRL 0x0033#define LTR390_ALS_UVS_MEAS_RATE 0x0434#define LTR390_ALS_UVS_GAIN 0x0535#define LTR390_PART_ID 0x0636#define LTR390_ALS_DATA 0x0D37#define LTR390_UVS_DATA 0x1038#define LTR390_INT_CFG 0x1939 40#define LTR390_PART_NUMBER_ID 0xb41#define LTR390_ALS_UVS_GAIN_MASK 0x0742#define LTR390_ALS_UVS_INT_TIME_MASK 0x7043#define LTR390_ALS_UVS_INT_TIME(x) FIELD_PREP(LTR390_ALS_UVS_INT_TIME_MASK, (x))44 45#define LTR390_SW_RESET BIT(4)46#define LTR390_UVS_MODE BIT(3)47#define LTR390_SENSOR_ENABLE BIT(1)48 49#define LTR390_FRACTIONAL_PRECISION 10050 51/*52 * At 20-bit resolution (integration time: 400ms) and 18x gain, 2300 counts of53 * the sensor are equal to 1 UV Index [Datasheet Page#8].54 *55 * For the default resolution of 18-bit (integration time: 100ms) and default56 * gain of 3x, the counts/uvi are calculated as follows:57 * 2300 / ((3/18) * (100/400)) = 95.8358 */59#define LTR390_COUNTS_PER_UVI 9660 61/*62 * Window Factor is needed when the device is under Window glass with coated63 * tinted ink. This is to compensate for the light loss due to the lower64 * transmission rate of the window glass and helps * in calculating lux.65 */66#define LTR390_WINDOW_FACTOR 167 68enum ltr390_mode {69 LTR390_SET_ALS_MODE,70 LTR390_SET_UVS_MODE,71};72 73struct ltr390_data {74 struct regmap *regmap;75 struct i2c_client *client;76 /* Protects device from simulataneous reads */77 struct mutex lock;78 enum ltr390_mode mode;79 int gain;80 int int_time_us;81};82 83static const struct regmap_config ltr390_regmap_config = {84 .name = "ltr390",85 .reg_bits = 8,86 .reg_stride = 1,87 .val_bits = 8,88};89 90static int ltr390_register_read(struct ltr390_data *data, u8 register_address)91{92 struct device *dev = &data->client->dev;93 int ret;94 u8 recieve_buffer[3];95 96 ret = regmap_bulk_read(data->regmap, register_address, recieve_buffer,97 sizeof(recieve_buffer));98 if (ret) {99 dev_err(dev, "failed to read measurement data");100 return ret;101 }102 103 return get_unaligned_le24(recieve_buffer);104}105 106static int ltr390_set_mode(struct ltr390_data *data, enum ltr390_mode mode)107{108 int ret;109 110 if (data->mode == mode)111 return 0;112 113 switch (mode) {114 case LTR390_SET_ALS_MODE:115 ret = regmap_clear_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_UVS_MODE);116 break;117 118 case LTR390_SET_UVS_MODE:119 ret = regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_UVS_MODE);120 break;121 }122 123 if (ret)124 return ret;125 126 data->mode = mode;127 return 0;128}129 130static int ltr390_counts_per_uvi(struct ltr390_data *data)131{132 const int orig_gain = 18;133 const int orig_int_time = 400;134 135 return DIV_ROUND_CLOSEST(23 * data->gain * data->int_time_us, 10 * orig_gain * orig_int_time);136}137 138static int ltr390_read_raw(struct iio_dev *iio_device,139 struct iio_chan_spec const *chan, int *val,140 int *val2, long mask)141{142 int ret;143 struct ltr390_data *data = iio_priv(iio_device);144 145 guard(mutex)(&data->lock);146 switch (mask) {147 case IIO_CHAN_INFO_RAW:148 switch (chan->type) {149 case IIO_UVINDEX:150 ret = ltr390_set_mode(data, LTR390_SET_UVS_MODE);151 if (ret < 0)152 return ret;153 154 ret = ltr390_register_read(data, LTR390_UVS_DATA);155 if (ret < 0)156 return ret;157 break;158 159 case IIO_LIGHT:160 ret = ltr390_set_mode(data, LTR390_SET_ALS_MODE);161 if (ret < 0)162 return ret;163 164 ret = ltr390_register_read(data, LTR390_ALS_DATA);165 if (ret < 0)166 return ret;167 break;168 169 default:170 return -EINVAL;171 }172 *val = ret;173 return IIO_VAL_INT;174 case IIO_CHAN_INFO_SCALE:175 switch (chan->type) {176 case IIO_UVINDEX:177 *val = LTR390_WINDOW_FACTOR * LTR390_FRACTIONAL_PRECISION;178 *val2 = ltr390_counts_per_uvi(data);179 return IIO_VAL_FRACTIONAL;180 181 case IIO_LIGHT:182 *val = LTR390_WINDOW_FACTOR * 6 * 100;183 *val2 = data->gain * data->int_time_us;184 return IIO_VAL_FRACTIONAL;185 186 default:187 return -EINVAL;188 }189 190 case IIO_CHAN_INFO_INT_TIME:191 *val = data->int_time_us;192 return IIO_VAL_INT;193 194 default:195 return -EINVAL;196 }197}198 199/* integration time in us */200static const int ltr390_int_time_map_us[] = { 400000, 200000, 100000, 50000, 25000, 12500 };201static const int ltr390_gain_map[] = { 1, 3, 6, 9, 18 };202 203static const struct iio_chan_spec ltr390_channels[] = {204 /* UV sensor */205 {206 .type = IIO_UVINDEX,207 .scan_index = 0,208 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),209 .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),210 .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | BIT(IIO_CHAN_INFO_SCALE)211 },212 /* ALS sensor */213 {214 .type = IIO_LIGHT,215 .scan_index = 1,216 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),217 .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),218 .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME) | BIT(IIO_CHAN_INFO_SCALE)219 },220};221 222static int ltr390_set_gain(struct ltr390_data *data, int val)223{224 int ret, idx;225 226 for (idx = 0; idx < ARRAY_SIZE(ltr390_gain_map); idx++) {227 if (ltr390_gain_map[idx] != val)228 continue;229 230 guard(mutex)(&data->lock);231 ret = regmap_update_bits(data->regmap,232 LTR390_ALS_UVS_GAIN,233 LTR390_ALS_UVS_GAIN_MASK, idx);234 if (ret)235 return ret;236 237 data->gain = ltr390_gain_map[idx];238 return 0;239 }240 241 return -EINVAL;242}243 244static int ltr390_set_int_time(struct ltr390_data *data, int val)245{246 int ret, idx;247 248 for (idx = 0; idx < ARRAY_SIZE(ltr390_int_time_map_us); idx++) {249 if (ltr390_int_time_map_us[idx] != val)250 continue;251 252 guard(mutex)(&data->lock);253 ret = regmap_update_bits(data->regmap,254 LTR390_ALS_UVS_MEAS_RATE,255 LTR390_ALS_UVS_INT_TIME_MASK,256 LTR390_ALS_UVS_INT_TIME(idx));257 if (ret)258 return ret;259 260 data->int_time_us = ltr390_int_time_map_us[idx];261 return 0;262 }263 264 return -EINVAL;265}266 267static int ltr390_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,268 const int **vals, int *type, int *length, long mask)269{270 switch (mask) {271 case IIO_CHAN_INFO_SCALE:272 *length = ARRAY_SIZE(ltr390_gain_map);273 *type = IIO_VAL_INT;274 *vals = ltr390_gain_map;275 return IIO_AVAIL_LIST;276 case IIO_CHAN_INFO_INT_TIME:277 *length = ARRAY_SIZE(ltr390_int_time_map_us);278 *type = IIO_VAL_INT;279 *vals = ltr390_int_time_map_us;280 return IIO_AVAIL_LIST;281 default:282 return -EINVAL;283 }284}285 286static int ltr390_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,287 int val, int val2, long mask)288{289 struct ltr390_data *data = iio_priv(indio_dev);290 291 switch (mask) {292 case IIO_CHAN_INFO_SCALE:293 if (val2 != 0)294 return -EINVAL;295 296 return ltr390_set_gain(data, val);297 298 case IIO_CHAN_INFO_INT_TIME:299 if (val2 != 0)300 return -EINVAL;301 302 return ltr390_set_int_time(data, val);303 304 default:305 return -EINVAL;306 }307}308 309static const struct iio_info ltr390_info = {310 .read_raw = ltr390_read_raw,311 .write_raw = ltr390_write_raw,312 .read_avail = ltr390_read_avail,313};314 315static int ltr390_probe(struct i2c_client *client)316{317 struct ltr390_data *data;318 struct iio_dev *indio_dev;319 struct device *dev;320 int ret, part_number;321 322 dev = &client->dev;323 indio_dev = devm_iio_device_alloc(dev, sizeof(*data));324 if (!indio_dev)325 return -ENOMEM;326 327 data = iio_priv(indio_dev);328 329 data->regmap = devm_regmap_init_i2c(client, <r390_regmap_config);330 if (IS_ERR(data->regmap))331 return dev_err_probe(dev, PTR_ERR(data->regmap),332 "regmap initialization failed\n");333 334 data->client = client;335 /* default value of integration time from pg: 15 of the datasheet */336 data->int_time_us = 100000;337 /* default value of gain from pg: 16 of the datasheet */338 data->gain = 3;339 /* default mode for ltr390 is ALS mode */340 data->mode = LTR390_SET_ALS_MODE;341 342 mutex_init(&data->lock);343 344 indio_dev->info = <r390_info;345 indio_dev->channels = ltr390_channels;346 indio_dev->num_channels = ARRAY_SIZE(ltr390_channels);347 indio_dev->name = "ltr390";348 349 ret = regmap_read(data->regmap, LTR390_PART_ID, &part_number);350 if (ret)351 return dev_err_probe(dev, ret,352 "failed to get sensor's part id\n");353 /* Lower 4 bits of `part_number` change with hardware revisions */354 if (part_number >> 4 != LTR390_PART_NUMBER_ID)355 dev_info(dev, "received invalid product id: 0x%x", part_number);356 dev_dbg(dev, "LTR390, product id: 0x%x\n", part_number);357 358 /* reset sensor, chip fails to respond to this, so ignore any errors */359 regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SW_RESET);360 361 /* Wait for the registers to reset before proceeding */362 usleep_range(1000, 2000);363 364 ret = regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SENSOR_ENABLE);365 if (ret)366 return dev_err_probe(dev, ret, "failed to enable the sensor\n");367 368 return devm_iio_device_register(dev, indio_dev);369}370 371static const struct i2c_device_id ltr390_id[] = {372 { "ltr390" },373 { /* Sentinel */ }374};375MODULE_DEVICE_TABLE(i2c, ltr390_id);376 377static const struct of_device_id ltr390_of_table[] = {378 { .compatible = "liteon,ltr390" },379 { /* Sentinel */ }380};381MODULE_DEVICE_TABLE(of, ltr390_of_table);382 383static struct i2c_driver ltr390_driver = {384 .driver = {385 .name = "ltr390",386 .of_match_table = ltr390_of_table,387 },388 .probe = ltr390_probe,389 .id_table = ltr390_id,390};391module_i2c_driver(ltr390_driver);392 393MODULE_AUTHOR("Anshul Dalal <anshulusr@gmail.com>");394MODULE_DESCRIPTION("Lite-On LTR390 ALS and UV sensor Driver");395MODULE_LICENSE("GPL");396