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 | |
58 | struct ltr390_data { |
59 | struct regmap *regmap; |
60 | struct i2c_client *client; |
61 | /* Protects device from simulataneous reads */ |
62 | struct mutex lock; |
63 | }; |
64 | |
65 | static const struct regmap_config ltr390_regmap_config = { |
66 | .name = "ltr390" , |
67 | .reg_bits = 8, |
68 | .reg_stride = 1, |
69 | .val_bits = 8, |
70 | }; |
71 | |
72 | static 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 | |
90 | static 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 | |
113 | static const struct iio_info ltr390_info = { |
114 | .read_raw = ltr390_read_raw, |
115 | }; |
116 | |
117 | static 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 | |
122 | static 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, <r390_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 = <r390_info; |
145 | indio_dev->channels = <r390_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 | |
172 | static const struct i2c_device_id ltr390_id[] = { |
173 | { "ltr390" }, |
174 | { /* Sentinel */ } |
175 | }; |
176 | MODULE_DEVICE_TABLE(i2c, ltr390_id); |
177 | |
178 | static const struct of_device_id ltr390_of_table[] = { |
179 | { .compatible = "liteon,ltr390" }, |
180 | { /* Sentinel */ } |
181 | }; |
182 | MODULE_DEVICE_TABLE(of, ltr390_of_table); |
183 | |
184 | static 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 | }; |
192 | module_i2c_driver(ltr390_driver); |
193 | |
194 | MODULE_AUTHOR("Anshul Dalal <anshulusr@gmail.com>" ); |
195 | MODULE_DESCRIPTION("Lite-On LTR390 ALS and UV sensor Driver" ); |
196 | MODULE_LICENSE("GPL" ); |
197 | |