1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Vishay VEML6075 UVA and UVB light sensor |
4 | * |
5 | * Copyright 2023 Javier Carrasco <javier.carrasco.cruz@gmail.com> |
6 | * |
7 | * 7-bit I2C slave, address 0x10 |
8 | */ |
9 | |
10 | #include <linux/bitfield.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/err.h> |
13 | #include <linux/i2c.h> |
14 | #include <linux/module.h> |
15 | #include <linux/mutex.h> |
16 | #include <linux/regmap.h> |
17 | #include <linux/units.h> |
18 | |
19 | #include <linux/iio/iio.h> |
20 | |
21 | #define VEML6075_CMD_CONF 0x00 /* configuration register */ |
22 | #define VEML6075_CMD_UVA 0x07 /* UVA channel */ |
23 | #define VEML6075_CMD_UVB 0x09 /* UVB channel */ |
24 | #define VEML6075_CMD_COMP1 0x0A /* visible light compensation */ |
25 | #define VEML6075_CMD_COMP2 0x0B /* infrarred light compensation */ |
26 | #define VEML6075_CMD_ID 0x0C /* device ID */ |
27 | |
28 | #define VEML6075_CONF_IT GENMASK(6, 4) /* intregration time */ |
29 | #define VEML6075_CONF_HD BIT(3) /* dynamic setting */ |
30 | #define VEML6075_CONF_TRIG BIT(2) /* trigger */ |
31 | #define VEML6075_CONF_AF BIT(1) /* active force enable */ |
32 | #define VEML6075_CONF_SD BIT(0) /* shutdown */ |
33 | |
34 | #define VEML6075_IT_50_MS 0x00 |
35 | #define VEML6075_IT_100_MS 0x01 |
36 | #define VEML6075_IT_200_MS 0x02 |
37 | #define VEML6075_IT_400_MS 0x03 |
38 | #define VEML6075_IT_800_MS 0x04 |
39 | |
40 | #define VEML6075_AF_DISABLE 0x00 |
41 | #define VEML6075_AF_ENABLE 0x01 |
42 | |
43 | #define VEML6075_SD_DISABLE 0x00 |
44 | #define VEML6075_SD_ENABLE 0x01 |
45 | |
46 | /* Open-air coefficients and responsivity */ |
47 | #define VEML6075_A_COEF 2220 |
48 | #define VEML6075_B_COEF 1330 |
49 | #define VEML6075_C_COEF 2950 |
50 | #define VEML6075_D_COEF 1740 |
51 | #define VEML6075_UVA_RESP 1461 |
52 | #define VEML6075_UVB_RESP 2591 |
53 | |
54 | static const int veml6075_it_ms[] = { 50, 100, 200, 400, 800 }; |
55 | |
56 | struct veml6075_data { |
57 | struct i2c_client *client; |
58 | struct regmap *regmap; |
59 | /* |
60 | * prevent integration time modification and triggering |
61 | * measurements while a measurement is underway. |
62 | */ |
63 | struct mutex lock; |
64 | }; |
65 | |
66 | /* channel number */ |
67 | enum veml6075_chan { |
68 | CH_UVA, |
69 | CH_UVB, |
70 | }; |
71 | |
72 | static const struct iio_chan_spec veml6075_channels[] = { |
73 | { |
74 | .type = IIO_INTENSITY, |
75 | .channel = CH_UVA, |
76 | .modified = 1, |
77 | .channel2 = IIO_MOD_LIGHT_UVA, |
78 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
79 | BIT(IIO_CHAN_INFO_SCALE), |
80 | .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), |
81 | .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), |
82 | }, |
83 | { |
84 | .type = IIO_INTENSITY, |
85 | .channel = CH_UVB, |
86 | .modified = 1, |
87 | .channel2 = IIO_MOD_LIGHT_UVB, |
88 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
89 | BIT(IIO_CHAN_INFO_SCALE), |
90 | .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), |
91 | .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), |
92 | }, |
93 | { |
94 | .type = IIO_UVINDEX, |
95 | .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), |
96 | .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME), |
97 | .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), |
98 | }, |
99 | }; |
100 | |
101 | static int veml6075_request_measurement(struct veml6075_data *data) |
102 | { |
103 | int ret, conf, int_time; |
104 | |
105 | ret = regmap_read(map: data->regmap, VEML6075_CMD_CONF, val: &conf); |
106 | if (ret < 0) |
107 | return ret; |
108 | |
109 | /* disable shutdown and trigger measurement */ |
110 | ret = regmap_write(map: data->regmap, VEML6075_CMD_CONF, |
111 | val: (conf | VEML6075_CONF_TRIG) & ~VEML6075_CONF_SD); |
112 | if (ret < 0) |
113 | return ret; |
114 | |
115 | /* |
116 | * A measurement requires between 1.30 and 1.40 times the integration |
117 | * time for all possible configurations. Using a 1.50 factor simplifies |
118 | * operations and ensures reliability under all circumstances. |
119 | */ |
120 | int_time = veml6075_it_ms[FIELD_GET(VEML6075_CONF_IT, conf)]; |
121 | msleep(msecs: int_time + (int_time / 2)); |
122 | |
123 | /* shutdown again, data registers are still accessible */ |
124 | return regmap_update_bits(map: data->regmap, VEML6075_CMD_CONF, |
125 | VEML6075_CONF_SD, VEML6075_CONF_SD); |
126 | } |
127 | |
128 | static int veml6075_uva_comp(int raw_uva, int comp1, int comp2) |
129 | { |
130 | int comp1a_c, comp2a_c, uva_comp; |
131 | |
132 | comp1a_c = (comp1 * VEML6075_A_COEF) / 1000U; |
133 | comp2a_c = (comp2 * VEML6075_B_COEF) / 1000U; |
134 | uva_comp = raw_uva - comp1a_c - comp2a_c; |
135 | |
136 | return clamp_val(uva_comp, 0, U16_MAX); |
137 | } |
138 | |
139 | static int veml6075_uvb_comp(int raw_uvb, int comp1, int comp2) |
140 | { |
141 | int comp1b_c, comp2b_c, uvb_comp; |
142 | |
143 | comp1b_c = (comp1 * VEML6075_C_COEF) / 1000U; |
144 | comp2b_c = (comp2 * VEML6075_D_COEF) / 1000U; |
145 | uvb_comp = raw_uvb - comp1b_c - comp2b_c; |
146 | |
147 | return clamp_val(uvb_comp, 0, U16_MAX); |
148 | } |
149 | |
150 | static int veml6075_read_comp(struct veml6075_data *data, int *c1, int *c2) |
151 | { |
152 | int ret; |
153 | |
154 | ret = regmap_read(map: data->regmap, VEML6075_CMD_COMP1, val: c1); |
155 | if (ret < 0) |
156 | return ret; |
157 | |
158 | return regmap_read(map: data->regmap, VEML6075_CMD_COMP2, val: c2); |
159 | } |
160 | |
161 | static int veml6075_read_uv_direct(struct veml6075_data *data, int chan, |
162 | int *val) |
163 | { |
164 | int c1, c2, ret; |
165 | |
166 | guard(mutex)(T: &data->lock); |
167 | |
168 | ret = veml6075_request_measurement(data); |
169 | if (ret < 0) |
170 | return ret; |
171 | |
172 | ret = veml6075_read_comp(data, c1: &c1, c2: &c2); |
173 | if (ret < 0) |
174 | return ret; |
175 | |
176 | switch (chan) { |
177 | case CH_UVA: |
178 | ret = regmap_read(map: data->regmap, VEML6075_CMD_UVA, val); |
179 | if (ret < 0) |
180 | return ret; |
181 | |
182 | *val = veml6075_uva_comp(raw_uva: *val, comp1: c1, comp2: c2); |
183 | return IIO_VAL_INT; |
184 | case CH_UVB: |
185 | ret = regmap_read(map: data->regmap, VEML6075_CMD_UVB, val); |
186 | if (ret < 0) |
187 | return ret; |
188 | |
189 | *val = veml6075_uvb_comp(raw_uvb: *val, comp1: c1, comp2: c2); |
190 | return IIO_VAL_INT; |
191 | default: |
192 | return -EINVAL; |
193 | } |
194 | } |
195 | |
196 | static int veml6075_read_int_time_index(struct veml6075_data *data) |
197 | { |
198 | int ret, conf; |
199 | |
200 | ret = regmap_read(map: data->regmap, VEML6075_CMD_CONF, val: &conf); |
201 | if (ret < 0) |
202 | return ret; |
203 | |
204 | return FIELD_GET(VEML6075_CONF_IT, conf); |
205 | } |
206 | |
207 | static int veml6075_read_int_time_ms(struct veml6075_data *data, int *val) |
208 | { |
209 | int int_index; |
210 | |
211 | guard(mutex)(T: &data->lock); |
212 | int_index = veml6075_read_int_time_index(data); |
213 | if (int_index < 0) |
214 | return int_index; |
215 | |
216 | *val = veml6075_it_ms[int_index]; |
217 | |
218 | return IIO_VAL_INT; |
219 | } |
220 | |
221 | static int veml6075_get_uvi_micro(struct veml6075_data *data, int uva_comp, |
222 | int uvb_comp) |
223 | { |
224 | int uvia_micro = uva_comp * VEML6075_UVA_RESP; |
225 | int uvib_micro = uvb_comp * VEML6075_UVB_RESP; |
226 | int int_index; |
227 | |
228 | int_index = veml6075_read_int_time_index(data); |
229 | if (int_index < 0) |
230 | return int_index; |
231 | |
232 | switch (int_index) { |
233 | case VEML6075_IT_50_MS: |
234 | return uvia_micro + uvib_micro; |
235 | case VEML6075_IT_100_MS: |
236 | case VEML6075_IT_200_MS: |
237 | case VEML6075_IT_400_MS: |
238 | case VEML6075_IT_800_MS: |
239 | return (uvia_micro + uvib_micro) / (2 << int_index); |
240 | default: |
241 | return -EINVAL; |
242 | } |
243 | } |
244 | |
245 | static int veml6075_read_uvi(struct veml6075_data *data, int *val, int *val2) |
246 | { |
247 | int ret, c1, c2, uva, uvb, uvi_micro; |
248 | |
249 | guard(mutex)(T: &data->lock); |
250 | |
251 | ret = veml6075_request_measurement(data); |
252 | if (ret < 0) |
253 | return ret; |
254 | |
255 | ret = veml6075_read_comp(data, c1: &c1, c2: &c2); |
256 | if (ret < 0) |
257 | return ret; |
258 | |
259 | ret = regmap_read(map: data->regmap, VEML6075_CMD_UVA, val: &uva); |
260 | if (ret < 0) |
261 | return ret; |
262 | |
263 | ret = regmap_read(map: data->regmap, VEML6075_CMD_UVB, val: &uvb); |
264 | if (ret < 0) |
265 | return ret; |
266 | |
267 | uvi_micro = veml6075_get_uvi_micro(data, uva_comp: veml6075_uva_comp(raw_uva: uva, comp1: c1, comp2: c2), |
268 | uvb_comp: veml6075_uvb_comp(raw_uvb: uvb, comp1: c1, comp2: c2)); |
269 | if (uvi_micro < 0) |
270 | return uvi_micro; |
271 | |
272 | *val = uvi_micro / MICRO; |
273 | *val2 = uvi_micro % MICRO; |
274 | |
275 | return IIO_VAL_INT_PLUS_MICRO; |
276 | } |
277 | |
278 | static int veml6075_read_responsivity(int chan, int *val, int *val2) |
279 | { |
280 | /* scale = 1 / resp */ |
281 | switch (chan) { |
282 | case CH_UVA: |
283 | /* resp = 0.93 c/uW/cm2: scale = 1.75268817 */ |
284 | *val = 1; |
285 | *val2 = 75268817; |
286 | return IIO_VAL_INT_PLUS_NANO; |
287 | case CH_UVB: |
288 | /* resp = 2.1 c/uW/cm2: scale = 0.476190476 */ |
289 | *val = 0; |
290 | *val2 = 476190476; |
291 | return IIO_VAL_INT_PLUS_NANO; |
292 | default: |
293 | return -EINVAL; |
294 | } |
295 | } |
296 | |
297 | static int veml6075_read_avail(struct iio_dev *indio_dev, |
298 | struct iio_chan_spec const *chan, |
299 | const int **vals, int *type, int *length, |
300 | long mask) |
301 | { |
302 | switch (mask) { |
303 | case IIO_CHAN_INFO_INT_TIME: |
304 | *length = ARRAY_SIZE(veml6075_it_ms); |
305 | *vals = veml6075_it_ms; |
306 | *type = IIO_VAL_INT; |
307 | return IIO_AVAIL_LIST; |
308 | |
309 | default: |
310 | return -EINVAL; |
311 | } |
312 | } |
313 | |
314 | static int veml6075_read_raw(struct iio_dev *indio_dev, |
315 | struct iio_chan_spec const *chan, |
316 | int *val, int *val2, long mask) |
317 | { |
318 | struct veml6075_data *data = iio_priv(indio_dev); |
319 | |
320 | switch (mask) { |
321 | case IIO_CHAN_INFO_RAW: |
322 | return veml6075_read_uv_direct(data, chan: chan->channel, val); |
323 | case IIO_CHAN_INFO_PROCESSED: |
324 | return veml6075_read_uvi(data, val, val2); |
325 | case IIO_CHAN_INFO_INT_TIME: |
326 | return veml6075_read_int_time_ms(data, val); |
327 | case IIO_CHAN_INFO_SCALE: |
328 | return veml6075_read_responsivity(chan: chan->channel, val, val2); |
329 | default: |
330 | return -EINVAL; |
331 | } |
332 | } |
333 | |
334 | static int veml6075_write_int_time_ms(struct veml6075_data *data, int val) |
335 | { |
336 | int i = ARRAY_SIZE(veml6075_it_ms); |
337 | |
338 | guard(mutex)(T: &data->lock); |
339 | |
340 | while (i-- > 0) { |
341 | if (val == veml6075_it_ms[i]) |
342 | break; |
343 | } |
344 | if (i < 0) |
345 | return -EINVAL; |
346 | |
347 | return regmap_update_bits(map: data->regmap, VEML6075_CMD_CONF, |
348 | VEML6075_CONF_IT, |
349 | FIELD_PREP(VEML6075_CONF_IT, i)); |
350 | } |
351 | |
352 | static int veml6075_write_raw(struct iio_dev *indio_dev, |
353 | struct iio_chan_spec const *chan, |
354 | int val, int val2, long mask) |
355 | { |
356 | struct veml6075_data *data = iio_priv(indio_dev); |
357 | |
358 | switch (mask) { |
359 | case IIO_CHAN_INFO_INT_TIME: |
360 | return veml6075_write_int_time_ms(data, val); |
361 | default: |
362 | return -EINVAL; |
363 | } |
364 | } |
365 | |
366 | static const struct iio_info veml6075_info = { |
367 | .read_avail = veml6075_read_avail, |
368 | .read_raw = veml6075_read_raw, |
369 | .write_raw = veml6075_write_raw, |
370 | }; |
371 | |
372 | static bool veml6075_readable_reg(struct device *dev, unsigned int reg) |
373 | { |
374 | switch (reg) { |
375 | case VEML6075_CMD_CONF: |
376 | case VEML6075_CMD_UVA: |
377 | case VEML6075_CMD_UVB: |
378 | case VEML6075_CMD_COMP1: |
379 | case VEML6075_CMD_COMP2: |
380 | case VEML6075_CMD_ID: |
381 | return true; |
382 | default: |
383 | return false; |
384 | } |
385 | } |
386 | |
387 | static bool veml6075_writable_reg(struct device *dev, unsigned int reg) |
388 | { |
389 | switch (reg) { |
390 | case VEML6075_CMD_CONF: |
391 | return true; |
392 | default: |
393 | return false; |
394 | } |
395 | } |
396 | |
397 | static const struct regmap_config veml6075_regmap_config = { |
398 | .name = "veml6075" , |
399 | .reg_bits = 8, |
400 | .val_bits = 16, |
401 | .max_register = VEML6075_CMD_ID, |
402 | .readable_reg = veml6075_readable_reg, |
403 | .writeable_reg = veml6075_writable_reg, |
404 | .val_format_endian = REGMAP_ENDIAN_LITTLE, |
405 | }; |
406 | |
407 | static int veml6075_probe(struct i2c_client *client) |
408 | { |
409 | struct veml6075_data *data; |
410 | struct iio_dev *indio_dev; |
411 | struct regmap *regmap; |
412 | int config, ret; |
413 | |
414 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
415 | if (!indio_dev) |
416 | return -ENOMEM; |
417 | |
418 | regmap = devm_regmap_init_i2c(client, &veml6075_regmap_config); |
419 | if (IS_ERR(ptr: regmap)) |
420 | return PTR_ERR(ptr: regmap); |
421 | |
422 | data = iio_priv(indio_dev); |
423 | data->client = client; |
424 | data->regmap = regmap; |
425 | |
426 | mutex_init(&data->lock); |
427 | |
428 | indio_dev->name = "veml6075" ; |
429 | indio_dev->info = &veml6075_info; |
430 | indio_dev->channels = veml6075_channels; |
431 | indio_dev->num_channels = ARRAY_SIZE(veml6075_channels); |
432 | indio_dev->modes = INDIO_DIRECT_MODE; |
433 | |
434 | ret = devm_regulator_get_enable(dev: &client->dev, id: "vdd" ); |
435 | if (ret < 0) |
436 | return ret; |
437 | |
438 | /* default: 100ms integration time, active force enable, shutdown */ |
439 | config = FIELD_PREP(VEML6075_CONF_IT, VEML6075_IT_100_MS) | |
440 | FIELD_PREP(VEML6075_CONF_AF, VEML6075_AF_ENABLE) | |
441 | FIELD_PREP(VEML6075_CONF_SD, VEML6075_SD_ENABLE); |
442 | ret = regmap_write(map: data->regmap, VEML6075_CMD_CONF, val: config); |
443 | if (ret < 0) |
444 | return ret; |
445 | |
446 | return devm_iio_device_register(&client->dev, indio_dev); |
447 | } |
448 | |
449 | static const struct i2c_device_id veml6075_id[] = { |
450 | { "veml6075" }, |
451 | { } |
452 | }; |
453 | MODULE_DEVICE_TABLE(i2c, veml6075_id); |
454 | |
455 | static const struct of_device_id veml6075_of_match[] = { |
456 | { .compatible = "vishay,veml6075" }, |
457 | {} |
458 | }; |
459 | MODULE_DEVICE_TABLE(of, veml6075_of_match); |
460 | |
461 | static struct i2c_driver veml6075_driver = { |
462 | .driver = { |
463 | .name = "veml6075" , |
464 | .of_match_table = veml6075_of_match, |
465 | }, |
466 | .probe = veml6075_probe, |
467 | .id_table = veml6075_id, |
468 | }; |
469 | |
470 | module_i2c_driver(veml6075_driver); |
471 | |
472 | MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gmail.com>" ); |
473 | MODULE_DESCRIPTION("Vishay VEML6075 UVA and UVB light sensor driver" ); |
474 | MODULE_LICENSE("GPL" ); |
475 | |