1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * IIO driver for Lite-On LTR390 ALS and UV sensor
4 * (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.pdf
13 *
14 * TODO:
15 * - Support for configurable gain and resolution
16 * - Sensor suspend/resume support
17 * - Add support for reading the ALS
18 * - Interrupt support
19 */
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
27#include <linux/iio/iio.h>
28
29#include <asm/unaligned.h>
30
31#define LTR390_MAIN_CTRL 0x00
32#define LTR390_PART_ID 0x06
33#define LTR390_UVS_DATA 0x10
34
35#define LTR390_SW_RESET BIT(4)
36#define LTR390_UVS_MODE BIT(3)
37#define LTR390_SENSOR_ENABLE BIT(1)
38
39#define LTR390_PART_NUMBER_ID 0xb
40
41/*
42 * At 20-bit resolution (integration time: 400ms) and 18x gain, 2300 counts of
43 * the sensor are equal to 1 UV Index [Datasheet Page#8].
44 *
45 * For the default resolution of 18-bit (integration time: 100ms) and default
46 * gain of 3x, the counts/uvi are calculated as follows:
47 * 2300 / ((3/18) * (100/400)) = 95.83
48 */
49#define LTR390_COUNTS_PER_UVI 96
50
51/*
52 * Window Factor is needed when the device is under Window glass with coated
53 * tinted ink. This is to compensate for the light loss due to the lower
54 * transmission rate of the window glass and helps * in calculating lux.
55 */
56#define LTR390_WINDOW_FACTOR 1
57
58struct ltr390_data {
59 struct regmap *regmap;
60 struct i2c_client *client;
61 /* Protects device from simulataneous reads */
62 struct mutex lock;
63};
64
65static const struct regmap_config ltr390_regmap_config = {
66 .name = "ltr390",
67 .reg_bits = 8,
68 .reg_stride = 1,
69 .val_bits = 8,
70};
71
72static int ltr390_register_read(struct ltr390_data *data, u8 register_address)
73{
74 struct device *dev = &data->client->dev;
75 int ret;
76 u8 recieve_buffer[3];
77
78 guard(mutex)(T: &data->lock);
79
80 ret = regmap_bulk_read(map: data->regmap, reg: register_address, val: recieve_buffer,
81 val_count: sizeof(recieve_buffer));
82 if (ret) {
83 dev_err(dev, "failed to read measurement data");
84 return ret;
85 }
86
87 return get_unaligned_le24(p: recieve_buffer);
88}
89
90static int ltr390_read_raw(struct iio_dev *iio_device,
91 struct iio_chan_spec const *chan, int *val,
92 int *val2, long mask)
93{
94 int ret;
95 struct ltr390_data *data = iio_priv(indio_dev: iio_device);
96
97 switch (mask) {
98 case IIO_CHAN_INFO_RAW:
99 ret = ltr390_register_read(data, LTR390_UVS_DATA);
100 if (ret < 0)
101 return ret;
102 *val = ret;
103 return IIO_VAL_INT;
104 case IIO_CHAN_INFO_SCALE:
105 *val = LTR390_WINDOW_FACTOR;
106 *val2 = LTR390_COUNTS_PER_UVI;
107 return IIO_VAL_FRACTIONAL;
108 default:
109 return -EINVAL;
110 }
111}
112
113static const struct iio_info ltr390_info = {
114 .read_raw = ltr390_read_raw,
115};
116
117static const struct iio_chan_spec ltr390_channel = {
118 .type = IIO_UVINDEX,
119 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)
120};
121
122static int ltr390_probe(struct i2c_client *client)
123{
124 struct ltr390_data *data;
125 struct iio_dev *indio_dev;
126 struct device *dev;
127 int ret, part_number;
128
129 dev = &client->dev;
130 indio_dev = devm_iio_device_alloc(parent: dev, sizeof_priv: sizeof(*data));
131 if (!indio_dev)
132 return -ENOMEM;
133
134 data = iio_priv(indio_dev);
135
136 data->regmap = devm_regmap_init_i2c(client, &ltr390_regmap_config);
137 if (IS_ERR(ptr: data->regmap))
138 return dev_err_probe(dev, err: PTR_ERR(ptr: data->regmap),
139 fmt: "regmap initialization failed\n");
140
141 data->client = client;
142 mutex_init(&data->lock);
143
144 indio_dev->info = &ltr390_info;
145 indio_dev->channels = &ltr390_channel;
146 indio_dev->num_channels = 1;
147 indio_dev->name = "ltr390";
148
149 ret = regmap_read(map: data->regmap, LTR390_PART_ID, val: &part_number);
150 if (ret)
151 return dev_err_probe(dev, err: ret,
152 fmt: "failed to get sensor's part id\n");
153 /* Lower 4 bits of `part_number` change with hardware revisions */
154 if (part_number >> 4 != LTR390_PART_NUMBER_ID)
155 dev_info(dev, "received invalid product id: 0x%x", part_number);
156 dev_dbg(dev, "LTR390, product id: 0x%x\n", part_number);
157
158 /* reset sensor, chip fails to respond to this, so ignore any errors */
159 regmap_set_bits(map: data->regmap, LTR390_MAIN_CTRL, LTR390_SW_RESET);
160
161 /* Wait for the registers to reset before proceeding */
162 usleep_range(min: 1000, max: 2000);
163
164 ret = regmap_set_bits(map: data->regmap, LTR390_MAIN_CTRL,
165 LTR390_SENSOR_ENABLE | LTR390_UVS_MODE);
166 if (ret)
167 return dev_err_probe(dev, err: ret, fmt: "failed to enable the sensor\n");
168
169 return devm_iio_device_register(dev, indio_dev);
170}
171
172static const struct i2c_device_id ltr390_id[] = {
173 { "ltr390" },
174 { /* Sentinel */ }
175};
176MODULE_DEVICE_TABLE(i2c, ltr390_id);
177
178static const struct of_device_id ltr390_of_table[] = {
179 { .compatible = "liteon,ltr390" },
180 { /* Sentinel */ }
181};
182MODULE_DEVICE_TABLE(of, ltr390_of_table);
183
184static struct i2c_driver ltr390_driver = {
185 .driver = {
186 .name = "ltr390",
187 .of_match_table = ltr390_of_table,
188 },
189 .probe = ltr390_probe,
190 .id_table = ltr390_id,
191};
192module_i2c_driver(ltr390_driver);
193
194MODULE_AUTHOR("Anshul Dalal <anshulusr@gmail.com>");
195MODULE_DESCRIPTION("Lite-On LTR390 ALS and UV sensor Driver");
196MODULE_LICENSE("GPL");
197

source code of linux/drivers/iio/light/ltr390.c