1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * vl6180.c - Support for STMicroelectronics VL6180 ALS, range and proximity |
4 | * sensor |
5 | * |
6 | * Copyright 2017 Peter Meerwald-Stadler <pmeerw@pmeerw.net> |
7 | * Copyright 2017 Manivannan Sadhasivam <manivannanece23@gmail.com> |
8 | * |
9 | * IIO driver for VL6180 (7-bit I2C slave address 0x29) |
10 | * |
11 | * Range: 0 to 100mm |
12 | * ALS: < 1 Lux up to 100 kLux |
13 | * IR: 850nm |
14 | * |
15 | * TODO: irq, threshold events, continuous mode, hardware buffer |
16 | */ |
17 | |
18 | #include <linux/module.h> |
19 | #include <linux/mod_devicetable.h> |
20 | #include <linux/i2c.h> |
21 | #include <linux/mutex.h> |
22 | #include <linux/err.h> |
23 | #include <linux/of.h> |
24 | #include <linux/delay.h> |
25 | #include <linux/util_macros.h> |
26 | |
27 | #include <linux/iio/iio.h> |
28 | #include <linux/iio/sysfs.h> |
29 | |
30 | #define VL6180_DRV_NAME "vl6180" |
31 | |
32 | /* Device identification register and value */ |
33 | #define VL6180_MODEL_ID 0x000 |
34 | #define VL6180_MODEL_ID_VAL 0xb4 |
35 | |
36 | /* Configuration registers */ |
37 | #define VL6180_INTR_CONFIG 0x014 |
38 | #define VL6180_INTR_CLEAR 0x015 |
39 | #define VL6180_OUT_OF_RESET 0x016 |
40 | #define VL6180_HOLD 0x017 |
41 | #define VL6180_RANGE_START 0x018 |
42 | #define VL6180_ALS_START 0x038 |
43 | #define VL6180_ALS_GAIN 0x03f |
44 | #define VL6180_ALS_IT 0x040 |
45 | |
46 | /* Status registers */ |
47 | #define VL6180_RANGE_STATUS 0x04d |
48 | #define VL6180_ALS_STATUS 0x04e |
49 | #define VL6180_INTR_STATUS 0x04f |
50 | |
51 | /* Result value registers */ |
52 | #define VL6180_ALS_VALUE 0x050 |
53 | #define VL6180_RANGE_VALUE 0x062 |
54 | #define VL6180_RANGE_RATE 0x066 |
55 | |
56 | /* bits of the RANGE_START and ALS_START register */ |
57 | #define VL6180_MODE_CONT BIT(1) /* continuous mode */ |
58 | #define VL6180_STARTSTOP BIT(0) /* start measurement, auto-reset */ |
59 | |
60 | /* bits of the INTR_STATUS and INTR_CONFIG register */ |
61 | #define VL6180_ALS_READY BIT(5) |
62 | #define VL6180_RANGE_READY BIT(2) |
63 | |
64 | /* bits of the INTR_CLEAR register */ |
65 | #define VL6180_CLEAR_ERROR BIT(2) |
66 | #define VL6180_CLEAR_ALS BIT(1) |
67 | #define VL6180_CLEAR_RANGE BIT(0) |
68 | |
69 | /* bits of the HOLD register */ |
70 | #define VL6180_HOLD_ON BIT(0) |
71 | |
72 | /* default value for the ALS_IT register */ |
73 | #define VL6180_ALS_IT_100 0x63 /* 100 ms */ |
74 | |
75 | /* values for the ALS_GAIN register */ |
76 | #define VL6180_ALS_GAIN_1 0x46 |
77 | #define VL6180_ALS_GAIN_1_25 0x45 |
78 | #define VL6180_ALS_GAIN_1_67 0x44 |
79 | #define VL6180_ALS_GAIN_2_5 0x43 |
80 | #define VL6180_ALS_GAIN_5 0x42 |
81 | #define VL6180_ALS_GAIN_10 0x41 |
82 | #define VL6180_ALS_GAIN_20 0x40 |
83 | #define VL6180_ALS_GAIN_40 0x47 |
84 | |
85 | struct vl6180_data { |
86 | struct i2c_client *client; |
87 | struct mutex lock; |
88 | unsigned int als_gain_milli; |
89 | unsigned int als_it_ms; |
90 | }; |
91 | |
92 | enum { VL6180_ALS, VL6180_RANGE, VL6180_PROX }; |
93 | |
94 | /** |
95 | * struct vl6180_chan_regs - Registers for accessing channels |
96 | * @drdy_mask: Data ready bit in status register |
97 | * @start_reg: Conversion start register |
98 | * @value_reg: Result value register |
99 | * @word: Register word length |
100 | */ |
101 | struct vl6180_chan_regs { |
102 | u8 drdy_mask; |
103 | u16 start_reg, value_reg; |
104 | bool word; |
105 | }; |
106 | |
107 | static const struct vl6180_chan_regs vl6180_chan_regs_table[] = { |
108 | [VL6180_ALS] = { |
109 | .drdy_mask = VL6180_ALS_READY, |
110 | .start_reg = VL6180_ALS_START, |
111 | .value_reg = VL6180_ALS_VALUE, |
112 | .word = true, |
113 | }, |
114 | [VL6180_RANGE] = { |
115 | .drdy_mask = VL6180_RANGE_READY, |
116 | .start_reg = VL6180_RANGE_START, |
117 | .value_reg = VL6180_RANGE_VALUE, |
118 | .word = false, |
119 | }, |
120 | [VL6180_PROX] = { |
121 | .drdy_mask = VL6180_RANGE_READY, |
122 | .start_reg = VL6180_RANGE_START, |
123 | .value_reg = VL6180_RANGE_RATE, |
124 | .word = true, |
125 | }, |
126 | }; |
127 | |
128 | static int vl6180_read(struct i2c_client *client, u16 cmd, void *databuf, |
129 | u8 len) |
130 | { |
131 | __be16 cmdbuf = cpu_to_be16(cmd); |
132 | struct i2c_msg msgs[2] = { |
133 | { .addr = client->addr, .len = sizeof(cmdbuf), .buf = (u8 *) &cmdbuf }, |
134 | { .addr = client->addr, .len = len, .buf = databuf, |
135 | .flags = I2C_M_RD } }; |
136 | int ret; |
137 | |
138 | ret = i2c_transfer(adap: client->adapter, msgs, ARRAY_SIZE(msgs)); |
139 | if (ret < 0) |
140 | dev_err(&client->dev, "failed reading register 0x%04x\n" , cmd); |
141 | |
142 | return ret; |
143 | } |
144 | |
145 | static int vl6180_read_byte(struct i2c_client *client, u16 cmd) |
146 | { |
147 | u8 data; |
148 | int ret; |
149 | |
150 | ret = vl6180_read(client, cmd, databuf: &data, len: sizeof(data)); |
151 | if (ret < 0) |
152 | return ret; |
153 | |
154 | return data; |
155 | } |
156 | |
157 | static int vl6180_read_word(struct i2c_client *client, u16 cmd) |
158 | { |
159 | __be16 data; |
160 | int ret; |
161 | |
162 | ret = vl6180_read(client, cmd, databuf: &data, len: sizeof(data)); |
163 | if (ret < 0) |
164 | return ret; |
165 | |
166 | return be16_to_cpu(data); |
167 | } |
168 | |
169 | static int vl6180_write_byte(struct i2c_client *client, u16 cmd, u8 val) |
170 | { |
171 | u8 buf[3]; |
172 | struct i2c_msg msgs[1] = { |
173 | { .addr = client->addr, .len = sizeof(buf), .buf = (u8 *) &buf } }; |
174 | int ret; |
175 | |
176 | buf[0] = cmd >> 8; |
177 | buf[1] = cmd & 0xff; |
178 | buf[2] = val; |
179 | |
180 | ret = i2c_transfer(adap: client->adapter, msgs, ARRAY_SIZE(msgs)); |
181 | if (ret < 0) { |
182 | dev_err(&client->dev, "failed writing register 0x%04x\n" , cmd); |
183 | return ret; |
184 | } |
185 | |
186 | return 0; |
187 | } |
188 | |
189 | static int vl6180_write_word(struct i2c_client *client, u16 cmd, u16 val) |
190 | { |
191 | __be16 buf[2]; |
192 | struct i2c_msg msgs[1] = { |
193 | { .addr = client->addr, .len = sizeof(buf), .buf = (u8 *) &buf } }; |
194 | int ret; |
195 | |
196 | buf[0] = cpu_to_be16(cmd); |
197 | buf[1] = cpu_to_be16(val); |
198 | |
199 | ret = i2c_transfer(adap: client->adapter, msgs, ARRAY_SIZE(msgs)); |
200 | if (ret < 0) { |
201 | dev_err(&client->dev, "failed writing register 0x%04x\n" , cmd); |
202 | return ret; |
203 | } |
204 | |
205 | return 0; |
206 | } |
207 | |
208 | static int vl6180_measure(struct vl6180_data *data, int addr) |
209 | { |
210 | struct i2c_client *client = data->client; |
211 | int tries = 20, ret; |
212 | u16 value; |
213 | |
214 | mutex_lock(&data->lock); |
215 | /* Start single shot measurement */ |
216 | ret = vl6180_write_byte(client, |
217 | cmd: vl6180_chan_regs_table[addr].start_reg, VL6180_STARTSTOP); |
218 | if (ret < 0) |
219 | goto fail; |
220 | |
221 | while (tries--) { |
222 | ret = vl6180_read_byte(client, VL6180_INTR_STATUS); |
223 | if (ret < 0) |
224 | goto fail; |
225 | |
226 | if (ret & vl6180_chan_regs_table[addr].drdy_mask) |
227 | break; |
228 | msleep(msecs: 20); |
229 | } |
230 | |
231 | if (tries < 0) { |
232 | ret = -EIO; |
233 | goto fail; |
234 | } |
235 | |
236 | /* Read result value from appropriate registers */ |
237 | ret = vl6180_chan_regs_table[addr].word ? |
238 | vl6180_read_word(client, cmd: vl6180_chan_regs_table[addr].value_reg) : |
239 | vl6180_read_byte(client, cmd: vl6180_chan_regs_table[addr].value_reg); |
240 | if (ret < 0) |
241 | goto fail; |
242 | value = ret; |
243 | |
244 | /* Clear the interrupt flag after data read */ |
245 | ret = vl6180_write_byte(client, VL6180_INTR_CLEAR, |
246 | VL6180_CLEAR_ERROR | VL6180_CLEAR_ALS | VL6180_CLEAR_RANGE); |
247 | if (ret < 0) |
248 | goto fail; |
249 | |
250 | ret = value; |
251 | |
252 | fail: |
253 | mutex_unlock(lock: &data->lock); |
254 | |
255 | return ret; |
256 | } |
257 | |
258 | static const struct iio_chan_spec vl6180_channels[] = { |
259 | { |
260 | .type = IIO_LIGHT, |
261 | .address = VL6180_ALS, |
262 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
263 | BIT(IIO_CHAN_INFO_INT_TIME) | |
264 | BIT(IIO_CHAN_INFO_SCALE) | |
265 | BIT(IIO_CHAN_INFO_HARDWAREGAIN), |
266 | }, { |
267 | .type = IIO_DISTANCE, |
268 | .address = VL6180_RANGE, |
269 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
270 | BIT(IIO_CHAN_INFO_SCALE), |
271 | }, { |
272 | .type = IIO_PROXIMITY, |
273 | .address = VL6180_PROX, |
274 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
275 | } |
276 | }; |
277 | |
278 | /* |
279 | * Available Ambient Light Sensor gain settings, 1/1000th, and |
280 | * corresponding setting for the VL6180_ALS_GAIN register |
281 | */ |
282 | static const int vl6180_als_gain_tab[8] = { |
283 | 1000, 1250, 1670, 2500, 5000, 10000, 20000, 40000 |
284 | }; |
285 | static const u8 vl6180_als_gain_tab_bits[8] = { |
286 | VL6180_ALS_GAIN_1, VL6180_ALS_GAIN_1_25, |
287 | VL6180_ALS_GAIN_1_67, VL6180_ALS_GAIN_2_5, |
288 | VL6180_ALS_GAIN_5, VL6180_ALS_GAIN_10, |
289 | VL6180_ALS_GAIN_20, VL6180_ALS_GAIN_40 |
290 | }; |
291 | |
292 | static int vl6180_read_raw(struct iio_dev *indio_dev, |
293 | struct iio_chan_spec const *chan, |
294 | int *val, int *val2, long mask) |
295 | { |
296 | struct vl6180_data *data = iio_priv(indio_dev); |
297 | int ret; |
298 | |
299 | switch (mask) { |
300 | case IIO_CHAN_INFO_RAW: |
301 | ret = vl6180_measure(data, addr: chan->address); |
302 | if (ret < 0) |
303 | return ret; |
304 | *val = ret; |
305 | |
306 | return IIO_VAL_INT; |
307 | case IIO_CHAN_INFO_INT_TIME: |
308 | *val = data->als_it_ms; |
309 | *val2 = 1000; |
310 | |
311 | return IIO_VAL_FRACTIONAL; |
312 | |
313 | case IIO_CHAN_INFO_SCALE: |
314 | switch (chan->type) { |
315 | case IIO_LIGHT: |
316 | /* one ALS count is 0.32 Lux @ gain 1, IT 100 ms */ |
317 | *val = 32000; /* 0.32 * 1000 * 100 */ |
318 | *val2 = data->als_gain_milli * data->als_it_ms; |
319 | |
320 | return IIO_VAL_FRACTIONAL; |
321 | |
322 | case IIO_DISTANCE: |
323 | *val = 0; /* sensor reports mm, scale to meter */ |
324 | *val2 = 1000; |
325 | break; |
326 | default: |
327 | return -EINVAL; |
328 | } |
329 | |
330 | return IIO_VAL_INT_PLUS_MICRO; |
331 | case IIO_CHAN_INFO_HARDWAREGAIN: |
332 | *val = data->als_gain_milli; |
333 | *val2 = 1000; |
334 | |
335 | return IIO_VAL_FRACTIONAL; |
336 | |
337 | default: |
338 | return -EINVAL; |
339 | } |
340 | } |
341 | |
342 | static IIO_CONST_ATTR(als_gain_available, "1 1.25 1.67 2.5 5 10 20 40" ); |
343 | |
344 | static struct attribute *vl6180_attributes[] = { |
345 | &iio_const_attr_als_gain_available.dev_attr.attr, |
346 | NULL |
347 | }; |
348 | |
349 | static const struct attribute_group vl6180_attribute_group = { |
350 | .attrs = vl6180_attributes, |
351 | }; |
352 | |
353 | /* HOLD is needed before updating any config registers */ |
354 | static int vl6180_hold(struct vl6180_data *data, bool hold) |
355 | { |
356 | return vl6180_write_byte(client: data->client, VL6180_HOLD, |
357 | val: hold ? VL6180_HOLD_ON : 0); |
358 | } |
359 | |
360 | static int vl6180_set_als_gain(struct vl6180_data *data, int val, int val2) |
361 | { |
362 | int i, ret, gain; |
363 | |
364 | if (val < 1 || val > 40) |
365 | return -EINVAL; |
366 | |
367 | gain = (val * 1000000 + val2) / 1000; |
368 | if (gain < 1 || gain > 40000) |
369 | return -EINVAL; |
370 | |
371 | i = find_closest(gain, vl6180_als_gain_tab, |
372 | ARRAY_SIZE(vl6180_als_gain_tab)); |
373 | |
374 | mutex_lock(&data->lock); |
375 | ret = vl6180_hold(data, hold: true); |
376 | if (ret < 0) |
377 | goto fail; |
378 | |
379 | ret = vl6180_write_byte(client: data->client, VL6180_ALS_GAIN, |
380 | val: vl6180_als_gain_tab_bits[i]); |
381 | |
382 | if (ret >= 0) |
383 | data->als_gain_milli = vl6180_als_gain_tab[i]; |
384 | |
385 | fail: |
386 | vl6180_hold(data, hold: false); |
387 | mutex_unlock(lock: &data->lock); |
388 | return ret; |
389 | } |
390 | |
391 | static int vl6180_set_it(struct vl6180_data *data, int val, int val2) |
392 | { |
393 | int ret, it_ms; |
394 | |
395 | it_ms = DIV_ROUND_CLOSEST(val2, 1000); /* round to ms */ |
396 | if (val != 0 || it_ms < 1 || it_ms > 512) |
397 | return -EINVAL; |
398 | |
399 | mutex_lock(&data->lock); |
400 | ret = vl6180_hold(data, hold: true); |
401 | if (ret < 0) |
402 | goto fail; |
403 | |
404 | ret = vl6180_write_word(client: data->client, VL6180_ALS_IT, val: it_ms - 1); |
405 | |
406 | if (ret >= 0) |
407 | data->als_it_ms = it_ms; |
408 | |
409 | fail: |
410 | vl6180_hold(data, hold: false); |
411 | mutex_unlock(lock: &data->lock); |
412 | |
413 | return ret; |
414 | } |
415 | |
416 | static int vl6180_write_raw(struct iio_dev *indio_dev, |
417 | struct iio_chan_spec const *chan, |
418 | int val, int val2, long mask) |
419 | { |
420 | struct vl6180_data *data = iio_priv(indio_dev); |
421 | |
422 | switch (mask) { |
423 | case IIO_CHAN_INFO_INT_TIME: |
424 | return vl6180_set_it(data, val, val2); |
425 | |
426 | case IIO_CHAN_INFO_HARDWAREGAIN: |
427 | if (chan->type != IIO_LIGHT) |
428 | return -EINVAL; |
429 | |
430 | return vl6180_set_als_gain(data, val, val2); |
431 | default: |
432 | return -EINVAL; |
433 | } |
434 | } |
435 | |
436 | static const struct iio_info vl6180_info = { |
437 | .read_raw = vl6180_read_raw, |
438 | .write_raw = vl6180_write_raw, |
439 | .attrs = &vl6180_attribute_group, |
440 | }; |
441 | |
442 | static int vl6180_init(struct vl6180_data *data) |
443 | { |
444 | struct i2c_client *client = data->client; |
445 | int ret; |
446 | |
447 | ret = vl6180_read_byte(client, VL6180_MODEL_ID); |
448 | if (ret < 0) |
449 | return ret; |
450 | |
451 | if (ret != VL6180_MODEL_ID_VAL) { |
452 | dev_err(&client->dev, "invalid model ID %02x\n" , ret); |
453 | return -ENODEV; |
454 | } |
455 | |
456 | ret = vl6180_hold(data, hold: true); |
457 | if (ret < 0) |
458 | return ret; |
459 | |
460 | ret = vl6180_read_byte(client, VL6180_OUT_OF_RESET); |
461 | if (ret < 0) |
462 | return ret; |
463 | |
464 | /* |
465 | * Detect false reset condition here. This bit is always set when the |
466 | * system comes out of reset. |
467 | */ |
468 | if (ret != 0x01) |
469 | dev_info(&client->dev, "device is not fresh out of reset\n" ); |
470 | |
471 | /* Enable ALS and Range ready interrupts */ |
472 | ret = vl6180_write_byte(client, VL6180_INTR_CONFIG, |
473 | VL6180_ALS_READY | VL6180_RANGE_READY); |
474 | if (ret < 0) |
475 | return ret; |
476 | |
477 | /* ALS integration time: 100ms */ |
478 | data->als_it_ms = 100; |
479 | ret = vl6180_write_word(client, VL6180_ALS_IT, VL6180_ALS_IT_100); |
480 | if (ret < 0) |
481 | return ret; |
482 | |
483 | /* ALS gain: 1 */ |
484 | data->als_gain_milli = 1000; |
485 | ret = vl6180_write_byte(client, VL6180_ALS_GAIN, VL6180_ALS_GAIN_1); |
486 | if (ret < 0) |
487 | return ret; |
488 | |
489 | ret = vl6180_write_byte(client, VL6180_OUT_OF_RESET, val: 0x00); |
490 | if (ret < 0) |
491 | return ret; |
492 | |
493 | return vl6180_hold(data, hold: false); |
494 | } |
495 | |
496 | static int vl6180_probe(struct i2c_client *client) |
497 | { |
498 | struct vl6180_data *data; |
499 | struct iio_dev *indio_dev; |
500 | int ret; |
501 | |
502 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
503 | if (!indio_dev) |
504 | return -ENOMEM; |
505 | |
506 | data = iio_priv(indio_dev); |
507 | i2c_set_clientdata(client, data: indio_dev); |
508 | data->client = client; |
509 | mutex_init(&data->lock); |
510 | |
511 | indio_dev->info = &vl6180_info; |
512 | indio_dev->channels = vl6180_channels; |
513 | indio_dev->num_channels = ARRAY_SIZE(vl6180_channels); |
514 | indio_dev->name = VL6180_DRV_NAME; |
515 | indio_dev->modes = INDIO_DIRECT_MODE; |
516 | |
517 | ret = vl6180_init(data); |
518 | if (ret < 0) |
519 | return ret; |
520 | |
521 | return devm_iio_device_register(&client->dev, indio_dev); |
522 | } |
523 | |
524 | static const struct of_device_id vl6180_of_match[] = { |
525 | { .compatible = "st,vl6180" , }, |
526 | { }, |
527 | }; |
528 | MODULE_DEVICE_TABLE(of, vl6180_of_match); |
529 | |
530 | static const struct i2c_device_id vl6180_id[] = { |
531 | { "vl6180" , 0 }, |
532 | { } |
533 | }; |
534 | MODULE_DEVICE_TABLE(i2c, vl6180_id); |
535 | |
536 | static struct i2c_driver vl6180_driver = { |
537 | .driver = { |
538 | .name = VL6180_DRV_NAME, |
539 | .of_match_table = vl6180_of_match, |
540 | }, |
541 | .probe = vl6180_probe, |
542 | .id_table = vl6180_id, |
543 | }; |
544 | |
545 | module_i2c_driver(vl6180_driver); |
546 | |
547 | MODULE_AUTHOR("Peter Meerwald-Stadler <pmeerw@pmeerw.net>" ); |
548 | MODULE_AUTHOR("Manivannan Sadhasivam <manivannanece23@gmail.com>" ); |
549 | MODULE_DESCRIPTION("STMicro VL6180 ALS, range and proximity sensor driver" ); |
550 | MODULE_LICENSE("GPL" ); |
551 | |