1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2015 Intel Corporation |
4 | * |
5 | * Driver for UPISEMI us5182d Proximity and Ambient Light Sensor. |
6 | * |
7 | * To do: Interrupt support. |
8 | */ |
9 | |
10 | #include <linux/kernel.h> |
11 | #include <linux/module.h> |
12 | #include <linux/acpi.h> |
13 | #include <linux/delay.h> |
14 | #include <linux/i2c.h> |
15 | #include <linux/iio/events.h> |
16 | #include <linux/iio/iio.h> |
17 | #include <linux/interrupt.h> |
18 | #include <linux/irq.h> |
19 | #include <linux/iio/sysfs.h> |
20 | #include <linux/mutex.h> |
21 | #include <linux/pm.h> |
22 | #include <linux/pm_runtime.h> |
23 | |
24 | #define US5182D_REG_CFG0 0x00 |
25 | #define US5182D_CFG0_ONESHOT_EN BIT(6) |
26 | #define US5182D_CFG0_SHUTDOWN_EN BIT(7) |
27 | #define US5182D_CFG0_WORD_ENABLE BIT(0) |
28 | #define US5182D_CFG0_PROX BIT(3) |
29 | #define US5182D_CFG0_PX_IRQ BIT(2) |
30 | |
31 | #define US5182D_REG_CFG1 0x01 |
32 | #define US5182D_CFG1_ALS_RES16 BIT(4) |
33 | #define US5182D_CFG1_AGAIN_DEFAULT 0x00 |
34 | |
35 | #define US5182D_REG_CFG2 0x02 |
36 | #define US5182D_CFG2_PX_RES16 BIT(4) |
37 | #define US5182D_CFG2_PXGAIN_DEFAULT BIT(2) |
38 | |
39 | #define US5182D_REG_CFG3 0x03 |
40 | #define US5182D_CFG3_LED_CURRENT100 (BIT(4) | BIT(5)) |
41 | #define US5182D_CFG3_INT_SOURCE_PX BIT(3) |
42 | |
43 | #define US5182D_REG_CFG4 0x10 |
44 | |
45 | /* |
46 | * Registers for tuning the auto dark current cancelling feature. |
47 | * DARK_TH(reg 0x27,0x28) - threshold (counts) for auto dark cancelling. |
48 | * when ALS > DARK_TH --> ALS_Code = ALS - Upper(0x2A) * Dark |
49 | * when ALS < DARK_TH --> ALS_Code = ALS - Lower(0x29) * Dark |
50 | */ |
51 | #define US5182D_REG_UDARK_TH 0x27 |
52 | #define US5182D_REG_DARK_AUTO_EN 0x2b |
53 | #define US5182D_REG_AUTO_LDARK_GAIN 0x29 |
54 | #define US5182D_REG_AUTO_HDARK_GAIN 0x2a |
55 | |
56 | /* Thresholds for events: px low (0x08-l, 0x09-h), px high (0x0a-l 0x0b-h) */ |
57 | #define US5182D_REG_PXL_TH 0x08 |
58 | #define US5182D_REG_PXH_TH 0x0a |
59 | |
60 | #define US5182D_REG_PXL_TH_DEFAULT 1000 |
61 | #define US5182D_REG_PXH_TH_DEFAULT 30000 |
62 | |
63 | #define US5182D_OPMODE_ALS 0x01 |
64 | #define US5182D_OPMODE_PX 0x02 |
65 | #define US5182D_OPMODE_SHIFT 4 |
66 | |
67 | #define US5182D_REG_DARK_AUTO_EN_DEFAULT 0x80 |
68 | #define US5182D_REG_AUTO_LDARK_GAIN_DEFAULT 0x16 |
69 | #define US5182D_REG_AUTO_HDARK_GAIN_DEFAULT 0x00 |
70 | |
71 | #define US5182D_REG_ADL 0x0c |
72 | #define US5182D_REG_PDL 0x0e |
73 | |
74 | #define US5182D_REG_MODE_STORE 0x21 |
75 | #define US5182D_STORE_MODE 0x01 |
76 | |
77 | #define US5182D_REG_CHIPID 0xb2 |
78 | |
79 | #define US5182D_OPMODE_MASK GENMASK(5, 4) |
80 | #define US5182D_AGAIN_MASK 0x07 |
81 | #define US5182D_RESET_CHIP 0x01 |
82 | |
83 | #define US5182D_CHIPID 0x26 |
84 | #define US5182D_DRV_NAME "us5182d" |
85 | |
86 | #define US5182D_GA_RESOLUTION 1000 |
87 | |
88 | #define US5182D_READ_BYTE 1 |
89 | #define US5182D_READ_WORD 2 |
90 | #define US5182D_OPSTORE_SLEEP_TIME 20 /* ms */ |
91 | #define US5182D_SLEEP_MS 3000 /* ms */ |
92 | #define US5182D_PXH_TH_DISABLE 0xffff |
93 | #define US5182D_PXL_TH_DISABLE 0x0000 |
94 | |
95 | /* Available ranges: [12354, 7065, 3998, 2202, 1285, 498, 256, 138] lux */ |
96 | static const int us5182d_scales[] = {188500, 107800, 61000, 33600, 19600, 7600, |
97 | 3900, 2100}; |
98 | |
99 | /* |
100 | * Experimental thresholds that work with US5182D sensor on evaluation board |
101 | * roughly between 12-32 lux |
102 | */ |
103 | static u16 us5182d_dark_ths_vals[] = {170, 200, 512, 512, 800, 2000, 4000, |
104 | 8000}; |
105 | |
106 | enum mode { |
107 | US5182D_ALS_PX, |
108 | US5182D_ALS_ONLY, |
109 | US5182D_PX_ONLY |
110 | }; |
111 | |
112 | enum pmode { |
113 | US5182D_CONTINUOUS, |
114 | US5182D_ONESHOT |
115 | }; |
116 | |
117 | struct us5182d_data { |
118 | struct i2c_client *client; |
119 | struct mutex lock; |
120 | |
121 | /* Glass attenuation factor */ |
122 | u32 ga; |
123 | |
124 | /* Dark gain tuning */ |
125 | u8 lower_dark_gain; |
126 | u8 upper_dark_gain; |
127 | u16 *us5182d_dark_ths; |
128 | |
129 | u16 px_low_th; |
130 | u16 px_high_th; |
131 | |
132 | int rising_en; |
133 | int falling_en; |
134 | |
135 | u8 opmode; |
136 | u8 power_mode; |
137 | |
138 | bool als_enabled; |
139 | bool px_enabled; |
140 | |
141 | bool default_continuous; |
142 | }; |
143 | |
144 | static IIO_CONST_ATTR(in_illuminance_scale_available, |
145 | "0.0021 0.0039 0.0076 0.0196 0.0336 0.061 0.1078 0.1885" ); |
146 | |
147 | static struct attribute *us5182d_attrs[] = { |
148 | &iio_const_attr_in_illuminance_scale_available.dev_attr.attr, |
149 | NULL |
150 | }; |
151 | |
152 | static const struct attribute_group us5182d_attr_group = { |
153 | .attrs = us5182d_attrs, |
154 | }; |
155 | |
156 | static const struct { |
157 | u8 reg; |
158 | u8 val; |
159 | } us5182d_regvals[] = { |
160 | {US5182D_REG_CFG0, US5182D_CFG0_WORD_ENABLE}, |
161 | {US5182D_REG_CFG1, US5182D_CFG1_ALS_RES16}, |
162 | {US5182D_REG_CFG2, (US5182D_CFG2_PX_RES16 | |
163 | US5182D_CFG2_PXGAIN_DEFAULT)}, |
164 | {US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100 | |
165 | US5182D_CFG3_INT_SOURCE_PX}, |
166 | {US5182D_REG_CFG4, 0x00}, |
167 | }; |
168 | |
169 | static const struct iio_event_spec us5182d_events[] = { |
170 | { |
171 | .type = IIO_EV_TYPE_THRESH, |
172 | .dir = IIO_EV_DIR_RISING, |
173 | .mask_separate = BIT(IIO_EV_INFO_VALUE) | |
174 | BIT(IIO_EV_INFO_ENABLE), |
175 | }, |
176 | { |
177 | .type = IIO_EV_TYPE_THRESH, |
178 | .dir = IIO_EV_DIR_FALLING, |
179 | .mask_separate = BIT(IIO_EV_INFO_VALUE) | |
180 | BIT(IIO_EV_INFO_ENABLE), |
181 | }, |
182 | }; |
183 | |
184 | static const struct iio_chan_spec us5182d_channels[] = { |
185 | { |
186 | .type = IIO_LIGHT, |
187 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
188 | BIT(IIO_CHAN_INFO_SCALE), |
189 | }, |
190 | { |
191 | .type = IIO_PROXIMITY, |
192 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
193 | .event_spec = us5182d_events, |
194 | .num_event_specs = ARRAY_SIZE(us5182d_events), |
195 | } |
196 | }; |
197 | |
198 | static int us5182d_oneshot_en(struct us5182d_data *data) |
199 | { |
200 | int ret; |
201 | |
202 | ret = i2c_smbus_read_byte_data(client: data->client, US5182D_REG_CFG0); |
203 | if (ret < 0) |
204 | return ret; |
205 | |
206 | /* |
207 | * In oneshot mode the chip will power itself down after taking the |
208 | * required measurement. |
209 | */ |
210 | ret = ret | US5182D_CFG0_ONESHOT_EN; |
211 | |
212 | return i2c_smbus_write_byte_data(client: data->client, US5182D_REG_CFG0, value: ret); |
213 | } |
214 | |
215 | static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) |
216 | { |
217 | int ret; |
218 | |
219 | if (mode == data->opmode) |
220 | return 0; |
221 | |
222 | ret = i2c_smbus_read_byte_data(client: data->client, US5182D_REG_CFG0); |
223 | if (ret < 0) |
224 | return ret; |
225 | |
226 | /* update mode */ |
227 | ret = ret & ~US5182D_OPMODE_MASK; |
228 | ret = ret | (mode << US5182D_OPMODE_SHIFT); |
229 | |
230 | /* |
231 | * After updating the operating mode, the chip requires that |
232 | * the operation is stored, by writing 1 in the STORE_MODE |
233 | * register (auto-clearing). |
234 | */ |
235 | ret = i2c_smbus_write_byte_data(client: data->client, US5182D_REG_CFG0, value: ret); |
236 | if (ret < 0) |
237 | return ret; |
238 | |
239 | ret = i2c_smbus_write_byte_data(client: data->client, US5182D_REG_MODE_STORE, |
240 | US5182D_STORE_MODE); |
241 | if (ret < 0) |
242 | return ret; |
243 | |
244 | data->opmode = mode; |
245 | msleep(US5182D_OPSTORE_SLEEP_TIME); |
246 | |
247 | return 0; |
248 | } |
249 | |
250 | static int us5182d_als_enable(struct us5182d_data *data) |
251 | { |
252 | int ret; |
253 | u8 mode; |
254 | |
255 | if (data->power_mode == US5182D_ONESHOT) { |
256 | ret = us5182d_set_opmode(data, mode: US5182D_ALS_ONLY); |
257 | if (ret < 0) |
258 | return ret; |
259 | data->px_enabled = false; |
260 | } |
261 | |
262 | if (data->als_enabled) |
263 | return 0; |
264 | |
265 | mode = data->px_enabled ? US5182D_ALS_PX : US5182D_ALS_ONLY; |
266 | |
267 | ret = us5182d_set_opmode(data, mode); |
268 | if (ret < 0) |
269 | return ret; |
270 | |
271 | data->als_enabled = true; |
272 | |
273 | return 0; |
274 | } |
275 | |
276 | static int us5182d_px_enable(struct us5182d_data *data) |
277 | { |
278 | int ret; |
279 | u8 mode; |
280 | |
281 | if (data->power_mode == US5182D_ONESHOT) { |
282 | ret = us5182d_set_opmode(data, mode: US5182D_PX_ONLY); |
283 | if (ret < 0) |
284 | return ret; |
285 | data->als_enabled = false; |
286 | } |
287 | |
288 | if (data->px_enabled) |
289 | return 0; |
290 | |
291 | mode = data->als_enabled ? US5182D_ALS_PX : US5182D_PX_ONLY; |
292 | |
293 | ret = us5182d_set_opmode(data, mode); |
294 | if (ret < 0) |
295 | return ret; |
296 | |
297 | data->px_enabled = true; |
298 | |
299 | return 0; |
300 | } |
301 | |
302 | static int us5182d_get_als(struct us5182d_data *data) |
303 | { |
304 | int ret; |
305 | unsigned long result; |
306 | |
307 | ret = us5182d_als_enable(data); |
308 | if (ret < 0) |
309 | return ret; |
310 | |
311 | ret = i2c_smbus_read_word_data(client: data->client, |
312 | US5182D_REG_ADL); |
313 | if (ret < 0) |
314 | return ret; |
315 | |
316 | result = ret * data->ga / US5182D_GA_RESOLUTION; |
317 | if (result > 0xffff) |
318 | result = 0xffff; |
319 | |
320 | return result; |
321 | } |
322 | |
323 | static int us5182d_get_px(struct us5182d_data *data) |
324 | { |
325 | int ret; |
326 | |
327 | ret = us5182d_px_enable(data); |
328 | if (ret < 0) |
329 | return ret; |
330 | |
331 | return i2c_smbus_read_word_data(client: data->client, |
332 | US5182D_REG_PDL); |
333 | } |
334 | |
335 | static int us5182d_shutdown_en(struct us5182d_data *data, u8 state) |
336 | { |
337 | int ret; |
338 | |
339 | if (data->power_mode == US5182D_ONESHOT) |
340 | return 0; |
341 | |
342 | ret = i2c_smbus_read_byte_data(client: data->client, US5182D_REG_CFG0); |
343 | if (ret < 0) |
344 | return ret; |
345 | |
346 | ret = ret & ~US5182D_CFG0_SHUTDOWN_EN; |
347 | ret = ret | state; |
348 | |
349 | ret = i2c_smbus_write_byte_data(client: data->client, US5182D_REG_CFG0, value: ret); |
350 | if (ret < 0) |
351 | return ret; |
352 | |
353 | if (state & US5182D_CFG0_SHUTDOWN_EN) { |
354 | data->als_enabled = false; |
355 | data->px_enabled = false; |
356 | } |
357 | |
358 | return ret; |
359 | } |
360 | |
361 | |
362 | static int us5182d_set_power_state(struct us5182d_data *data, bool on) |
363 | { |
364 | int ret; |
365 | |
366 | if (data->power_mode == US5182D_ONESHOT) |
367 | return 0; |
368 | |
369 | if (on) { |
370 | ret = pm_runtime_resume_and_get(dev: &data->client->dev); |
371 | } else { |
372 | pm_runtime_mark_last_busy(dev: &data->client->dev); |
373 | ret = pm_runtime_put_autosuspend(dev: &data->client->dev); |
374 | } |
375 | |
376 | return ret; |
377 | } |
378 | |
379 | static int us5182d_read_value(struct us5182d_data *data, |
380 | struct iio_chan_spec const *chan) |
381 | { |
382 | int ret, value; |
383 | |
384 | mutex_lock(&data->lock); |
385 | |
386 | if (data->power_mode == US5182D_ONESHOT) { |
387 | ret = us5182d_oneshot_en(data); |
388 | if (ret < 0) |
389 | goto out_err; |
390 | } |
391 | |
392 | ret = us5182d_set_power_state(data, on: true); |
393 | if (ret < 0) |
394 | goto out_err; |
395 | |
396 | if (chan->type == IIO_LIGHT) |
397 | ret = us5182d_get_als(data); |
398 | else |
399 | ret = us5182d_get_px(data); |
400 | if (ret < 0) |
401 | goto out_poweroff; |
402 | |
403 | value = ret; |
404 | |
405 | ret = us5182d_set_power_state(data, on: false); |
406 | if (ret < 0) |
407 | goto out_err; |
408 | |
409 | mutex_unlock(lock: &data->lock); |
410 | return value; |
411 | |
412 | out_poweroff: |
413 | us5182d_set_power_state(data, on: false); |
414 | out_err: |
415 | mutex_unlock(lock: &data->lock); |
416 | return ret; |
417 | } |
418 | |
419 | static int us5182d_read_raw(struct iio_dev *indio_dev, |
420 | struct iio_chan_spec const *chan, int *val, |
421 | int *val2, long mask) |
422 | { |
423 | struct us5182d_data *data = iio_priv(indio_dev); |
424 | int ret; |
425 | |
426 | switch (mask) { |
427 | case IIO_CHAN_INFO_RAW: |
428 | ret = us5182d_read_value(data, chan); |
429 | if (ret < 0) |
430 | return ret; |
431 | *val = ret; |
432 | return IIO_VAL_INT; |
433 | case IIO_CHAN_INFO_SCALE: |
434 | ret = i2c_smbus_read_byte_data(client: data->client, US5182D_REG_CFG1); |
435 | if (ret < 0) |
436 | return ret; |
437 | *val = 0; |
438 | *val2 = us5182d_scales[ret & US5182D_AGAIN_MASK]; |
439 | return IIO_VAL_INT_PLUS_MICRO; |
440 | default: |
441 | return -EINVAL; |
442 | } |
443 | } |
444 | |
445 | /** |
446 | * us5182d_update_dark_th - update Darh_Th registers |
447 | * @data: us5182d_data structure |
448 | * @index: index in us5182d_dark_ths array to use for the updated value |
449 | * |
450 | * Function needs to be called with a lock held because it needs two i2c write |
451 | * byte operations as these registers (0x27 0x28) don't work in word mode |
452 | * accessing. |
453 | */ |
454 | static int us5182d_update_dark_th(struct us5182d_data *data, int index) |
455 | { |
456 | __be16 dark_th = cpu_to_be16(data->us5182d_dark_ths[index]); |
457 | int ret; |
458 | |
459 | ret = i2c_smbus_write_byte_data(client: data->client, US5182D_REG_UDARK_TH, |
460 | value: ((u8 *)&dark_th)[0]); |
461 | if (ret < 0) |
462 | return ret; |
463 | |
464 | return i2c_smbus_write_byte_data(client: data->client, US5182D_REG_UDARK_TH + 1, |
465 | value: ((u8 *)&dark_th)[1]); |
466 | } |
467 | |
468 | /** |
469 | * us5182d_apply_scale - update the ALS scale |
470 | * @data: us5182d_data structure |
471 | * @index: index in us5182d_scales array to use for the updated value |
472 | * |
473 | * Function needs to be called with a lock held as we're having more than one |
474 | * i2c operation. |
475 | */ |
476 | static int us5182d_apply_scale(struct us5182d_data *data, int index) |
477 | { |
478 | int ret; |
479 | |
480 | ret = i2c_smbus_read_byte_data(client: data->client, US5182D_REG_CFG1); |
481 | if (ret < 0) |
482 | return ret; |
483 | |
484 | ret = ret & (~US5182D_AGAIN_MASK); |
485 | ret |= index; |
486 | |
487 | ret = i2c_smbus_write_byte_data(client: data->client, US5182D_REG_CFG1, value: ret); |
488 | if (ret < 0) |
489 | return ret; |
490 | |
491 | return us5182d_update_dark_th(data, index); |
492 | } |
493 | |
494 | static int us5182d_write_raw(struct iio_dev *indio_dev, |
495 | struct iio_chan_spec const *chan, int val, |
496 | int val2, long mask) |
497 | { |
498 | struct us5182d_data *data = iio_priv(indio_dev); |
499 | int ret, i; |
500 | |
501 | switch (mask) { |
502 | case IIO_CHAN_INFO_SCALE: |
503 | if (val != 0) |
504 | return -EINVAL; |
505 | for (i = 0; i < ARRAY_SIZE(us5182d_scales); i++) |
506 | if (val2 == us5182d_scales[i]) { |
507 | mutex_lock(&data->lock); |
508 | ret = us5182d_apply_scale(data, index: i); |
509 | mutex_unlock(lock: &data->lock); |
510 | return ret; |
511 | } |
512 | break; |
513 | default: |
514 | return -EINVAL; |
515 | } |
516 | |
517 | return -EINVAL; |
518 | } |
519 | |
520 | static int us5182d_setup_prox(struct iio_dev *indio_dev, |
521 | enum iio_event_direction dir, u16 val) |
522 | { |
523 | struct us5182d_data *data = iio_priv(indio_dev); |
524 | |
525 | if (dir == IIO_EV_DIR_FALLING) |
526 | return i2c_smbus_write_word_data(client: data->client, |
527 | US5182D_REG_PXL_TH, value: val); |
528 | else if (dir == IIO_EV_DIR_RISING) |
529 | return i2c_smbus_write_word_data(client: data->client, |
530 | US5182D_REG_PXH_TH, value: val); |
531 | |
532 | return 0; |
533 | } |
534 | |
535 | static int us5182d_read_thresh(struct iio_dev *indio_dev, |
536 | const struct iio_chan_spec *chan, enum iio_event_type type, |
537 | enum iio_event_direction dir, enum iio_event_info info, int *val, |
538 | int *val2) |
539 | { |
540 | struct us5182d_data *data = iio_priv(indio_dev); |
541 | |
542 | switch (dir) { |
543 | case IIO_EV_DIR_RISING: |
544 | mutex_lock(&data->lock); |
545 | *val = data->px_high_th; |
546 | mutex_unlock(lock: &data->lock); |
547 | break; |
548 | case IIO_EV_DIR_FALLING: |
549 | mutex_lock(&data->lock); |
550 | *val = data->px_low_th; |
551 | mutex_unlock(lock: &data->lock); |
552 | break; |
553 | default: |
554 | return -EINVAL; |
555 | } |
556 | |
557 | return IIO_VAL_INT; |
558 | } |
559 | |
560 | static int us5182d_write_thresh(struct iio_dev *indio_dev, |
561 | const struct iio_chan_spec *chan, enum iio_event_type type, |
562 | enum iio_event_direction dir, enum iio_event_info info, int val, |
563 | int val2) |
564 | { |
565 | struct us5182d_data *data = iio_priv(indio_dev); |
566 | int ret; |
567 | |
568 | if (val < 0 || val > USHRT_MAX || val2 != 0) |
569 | return -EINVAL; |
570 | |
571 | switch (dir) { |
572 | case IIO_EV_DIR_RISING: |
573 | mutex_lock(&data->lock); |
574 | if (data->rising_en) { |
575 | ret = us5182d_setup_prox(indio_dev, dir, val); |
576 | if (ret < 0) |
577 | goto err; |
578 | } |
579 | data->px_high_th = val; |
580 | mutex_unlock(lock: &data->lock); |
581 | break; |
582 | case IIO_EV_DIR_FALLING: |
583 | mutex_lock(&data->lock); |
584 | if (data->falling_en) { |
585 | ret = us5182d_setup_prox(indio_dev, dir, val); |
586 | if (ret < 0) |
587 | goto err; |
588 | } |
589 | data->px_low_th = val; |
590 | mutex_unlock(lock: &data->lock); |
591 | break; |
592 | default: |
593 | return -EINVAL; |
594 | } |
595 | |
596 | return 0; |
597 | err: |
598 | mutex_unlock(lock: &data->lock); |
599 | return ret; |
600 | } |
601 | |
602 | static int us5182d_read_event_config(struct iio_dev *indio_dev, |
603 | const struct iio_chan_spec *chan, enum iio_event_type type, |
604 | enum iio_event_direction dir) |
605 | { |
606 | struct us5182d_data *data = iio_priv(indio_dev); |
607 | int ret; |
608 | |
609 | switch (dir) { |
610 | case IIO_EV_DIR_RISING: |
611 | mutex_lock(&data->lock); |
612 | ret = data->rising_en; |
613 | mutex_unlock(lock: &data->lock); |
614 | break; |
615 | case IIO_EV_DIR_FALLING: |
616 | mutex_lock(&data->lock); |
617 | ret = data->falling_en; |
618 | mutex_unlock(lock: &data->lock); |
619 | break; |
620 | default: |
621 | ret = -EINVAL; |
622 | break; |
623 | } |
624 | |
625 | return ret; |
626 | } |
627 | |
628 | static int us5182d_write_event_config(struct iio_dev *indio_dev, |
629 | const struct iio_chan_spec *chan, enum iio_event_type type, |
630 | enum iio_event_direction dir, int state) |
631 | { |
632 | struct us5182d_data *data = iio_priv(indio_dev); |
633 | int ret; |
634 | u16 new_th; |
635 | |
636 | mutex_lock(&data->lock); |
637 | |
638 | switch (dir) { |
639 | case IIO_EV_DIR_RISING: |
640 | if (data->rising_en == state) { |
641 | mutex_unlock(lock: &data->lock); |
642 | return 0; |
643 | } |
644 | new_th = US5182D_PXH_TH_DISABLE; |
645 | if (state) { |
646 | data->power_mode = US5182D_CONTINUOUS; |
647 | ret = us5182d_set_power_state(data, on: true); |
648 | if (ret < 0) |
649 | goto err; |
650 | ret = us5182d_px_enable(data); |
651 | if (ret < 0) |
652 | goto err_poweroff; |
653 | new_th = data->px_high_th; |
654 | } |
655 | ret = us5182d_setup_prox(indio_dev, dir, val: new_th); |
656 | if (ret < 0) |
657 | goto err_poweroff; |
658 | data->rising_en = state; |
659 | break; |
660 | case IIO_EV_DIR_FALLING: |
661 | if (data->falling_en == state) { |
662 | mutex_unlock(lock: &data->lock); |
663 | return 0; |
664 | } |
665 | new_th = US5182D_PXL_TH_DISABLE; |
666 | if (state) { |
667 | data->power_mode = US5182D_CONTINUOUS; |
668 | ret = us5182d_set_power_state(data, on: true); |
669 | if (ret < 0) |
670 | goto err; |
671 | ret = us5182d_px_enable(data); |
672 | if (ret < 0) |
673 | goto err_poweroff; |
674 | new_th = data->px_low_th; |
675 | } |
676 | ret = us5182d_setup_prox(indio_dev, dir, val: new_th); |
677 | if (ret < 0) |
678 | goto err_poweroff; |
679 | data->falling_en = state; |
680 | break; |
681 | default: |
682 | ret = -EINVAL; |
683 | goto err; |
684 | } |
685 | |
686 | if (!state) { |
687 | ret = us5182d_set_power_state(data, on: false); |
688 | if (ret < 0) |
689 | goto err; |
690 | } |
691 | |
692 | if (!data->falling_en && !data->rising_en && !data->default_continuous) |
693 | data->power_mode = US5182D_ONESHOT; |
694 | |
695 | mutex_unlock(lock: &data->lock); |
696 | return 0; |
697 | |
698 | err_poweroff: |
699 | if (state) |
700 | us5182d_set_power_state(data, on: false); |
701 | err: |
702 | mutex_unlock(lock: &data->lock); |
703 | return ret; |
704 | } |
705 | |
706 | static const struct iio_info us5182d_info = { |
707 | .read_raw = us5182d_read_raw, |
708 | .write_raw = us5182d_write_raw, |
709 | .attrs = &us5182d_attr_group, |
710 | .read_event_value = &us5182d_read_thresh, |
711 | .write_event_value = &us5182d_write_thresh, |
712 | .read_event_config = &us5182d_read_event_config, |
713 | .write_event_config = &us5182d_write_event_config, |
714 | }; |
715 | |
716 | static int us5182d_reset(struct iio_dev *indio_dev) |
717 | { |
718 | struct us5182d_data *data = iio_priv(indio_dev); |
719 | |
720 | return i2c_smbus_write_byte_data(client: data->client, US5182D_REG_CFG3, |
721 | US5182D_RESET_CHIP); |
722 | } |
723 | |
724 | static int us5182d_init(struct iio_dev *indio_dev) |
725 | { |
726 | struct us5182d_data *data = iio_priv(indio_dev); |
727 | int i, ret; |
728 | |
729 | ret = us5182d_reset(indio_dev); |
730 | if (ret < 0) |
731 | return ret; |
732 | |
733 | data->opmode = 0; |
734 | data->power_mode = US5182D_CONTINUOUS; |
735 | data->px_low_th = US5182D_REG_PXL_TH_DEFAULT; |
736 | data->px_high_th = US5182D_REG_PXH_TH_DEFAULT; |
737 | |
738 | for (i = 0; i < ARRAY_SIZE(us5182d_regvals); i++) { |
739 | ret = i2c_smbus_write_byte_data(client: data->client, |
740 | command: us5182d_regvals[i].reg, |
741 | value: us5182d_regvals[i].val); |
742 | if (ret < 0) |
743 | return ret; |
744 | } |
745 | |
746 | data->als_enabled = true; |
747 | data->px_enabled = true; |
748 | |
749 | if (!data->default_continuous) { |
750 | ret = us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); |
751 | if (ret < 0) |
752 | return ret; |
753 | data->power_mode = US5182D_ONESHOT; |
754 | } |
755 | |
756 | return ret; |
757 | } |
758 | |
759 | static void us5182d_get_platform_data(struct iio_dev *indio_dev) |
760 | { |
761 | struct us5182d_data *data = iio_priv(indio_dev); |
762 | |
763 | if (device_property_read_u32(dev: &data->client->dev, propname: "upisemi,glass-coef" , |
764 | val: &data->ga)) |
765 | data->ga = US5182D_GA_RESOLUTION; |
766 | if (device_property_read_u16_array(dev: &data->client->dev, |
767 | propname: "upisemi,dark-ths" , |
768 | val: data->us5182d_dark_ths, |
769 | ARRAY_SIZE(us5182d_dark_ths_vals))) |
770 | data->us5182d_dark_ths = us5182d_dark_ths_vals; |
771 | if (device_property_read_u8(dev: &data->client->dev, |
772 | propname: "upisemi,upper-dark-gain" , |
773 | val: &data->upper_dark_gain)) |
774 | data->upper_dark_gain = US5182D_REG_AUTO_HDARK_GAIN_DEFAULT; |
775 | if (device_property_read_u8(dev: &data->client->dev, |
776 | propname: "upisemi,lower-dark-gain" , |
777 | val: &data->lower_dark_gain)) |
778 | data->lower_dark_gain = US5182D_REG_AUTO_LDARK_GAIN_DEFAULT; |
779 | data->default_continuous = device_property_read_bool(dev: &data->client->dev, |
780 | propname: "upisemi,continuous" ); |
781 | } |
782 | |
783 | static int us5182d_dark_gain_config(struct iio_dev *indio_dev) |
784 | { |
785 | struct us5182d_data *data = iio_priv(indio_dev); |
786 | int ret; |
787 | |
788 | ret = us5182d_update_dark_th(data, US5182D_CFG1_AGAIN_DEFAULT); |
789 | if (ret < 0) |
790 | return ret; |
791 | |
792 | ret = i2c_smbus_write_byte_data(client: data->client, |
793 | US5182D_REG_AUTO_LDARK_GAIN, |
794 | value: data->lower_dark_gain); |
795 | if (ret < 0) |
796 | return ret; |
797 | |
798 | ret = i2c_smbus_write_byte_data(client: data->client, |
799 | US5182D_REG_AUTO_HDARK_GAIN, |
800 | value: data->upper_dark_gain); |
801 | if (ret < 0) |
802 | return ret; |
803 | |
804 | return i2c_smbus_write_byte_data(client: data->client, US5182D_REG_DARK_AUTO_EN, |
805 | US5182D_REG_DARK_AUTO_EN_DEFAULT); |
806 | } |
807 | |
808 | static irqreturn_t us5182d_irq_thread_handler(int irq, void *private) |
809 | { |
810 | struct iio_dev *indio_dev = private; |
811 | struct us5182d_data *data = iio_priv(indio_dev); |
812 | enum iio_event_direction dir; |
813 | int ret; |
814 | u64 ev; |
815 | |
816 | ret = i2c_smbus_read_byte_data(client: data->client, US5182D_REG_CFG0); |
817 | if (ret < 0) { |
818 | dev_err(&data->client->dev, "i2c transfer error in irq\n" ); |
819 | return IRQ_HANDLED; |
820 | } |
821 | |
822 | dir = ret & US5182D_CFG0_PROX ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING; |
823 | ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, IIO_EV_TYPE_THRESH, dir); |
824 | |
825 | iio_push_event(indio_dev, ev_code: ev, timestamp: iio_get_time_ns(indio_dev)); |
826 | |
827 | ret = i2c_smbus_write_byte_data(client: data->client, US5182D_REG_CFG0, |
828 | value: ret & ~US5182D_CFG0_PX_IRQ); |
829 | if (ret < 0) |
830 | dev_err(&data->client->dev, "i2c transfer error in irq\n" ); |
831 | |
832 | return IRQ_HANDLED; |
833 | } |
834 | |
835 | static int us5182d_probe(struct i2c_client *client) |
836 | { |
837 | struct us5182d_data *data; |
838 | struct iio_dev *indio_dev; |
839 | int ret; |
840 | |
841 | indio_dev = devm_iio_device_alloc(parent: &client->dev, sizeof_priv: sizeof(*data)); |
842 | if (!indio_dev) |
843 | return -ENOMEM; |
844 | |
845 | data = iio_priv(indio_dev); |
846 | i2c_set_clientdata(client, data: indio_dev); |
847 | data->client = client; |
848 | |
849 | mutex_init(&data->lock); |
850 | |
851 | indio_dev->info = &us5182d_info; |
852 | indio_dev->name = US5182D_DRV_NAME; |
853 | indio_dev->channels = us5182d_channels; |
854 | indio_dev->num_channels = ARRAY_SIZE(us5182d_channels); |
855 | indio_dev->modes = INDIO_DIRECT_MODE; |
856 | |
857 | ret = i2c_smbus_read_byte_data(client: data->client, US5182D_REG_CHIPID); |
858 | if (ret != US5182D_CHIPID) { |
859 | dev_err(&data->client->dev, |
860 | "Failed to detect US5182 light chip\n" ); |
861 | return (ret < 0) ? ret : -ENODEV; |
862 | } |
863 | |
864 | if (client->irq > 0) { |
865 | ret = devm_request_threaded_irq(dev: &client->dev, irq: client->irq, NULL, |
866 | thread_fn: us5182d_irq_thread_handler, |
867 | IRQF_TRIGGER_LOW | IRQF_ONESHOT, |
868 | devname: "us5182d-irq" , dev_id: indio_dev); |
869 | if (ret < 0) |
870 | return ret; |
871 | } else |
872 | dev_warn(&client->dev, "no valid irq found\n" ); |
873 | |
874 | us5182d_get_platform_data(indio_dev); |
875 | ret = us5182d_init(indio_dev); |
876 | if (ret < 0) |
877 | return ret; |
878 | |
879 | ret = us5182d_dark_gain_config(indio_dev); |
880 | if (ret < 0) |
881 | goto out_err; |
882 | |
883 | if (data->default_continuous) { |
884 | ret = pm_runtime_set_active(dev: &client->dev); |
885 | if (ret < 0) |
886 | goto out_err; |
887 | } |
888 | |
889 | pm_runtime_enable(dev: &client->dev); |
890 | pm_runtime_set_autosuspend_delay(dev: &client->dev, |
891 | US5182D_SLEEP_MS); |
892 | pm_runtime_use_autosuspend(dev: &client->dev); |
893 | |
894 | ret = iio_device_register(indio_dev); |
895 | if (ret < 0) |
896 | goto out_err; |
897 | |
898 | return 0; |
899 | |
900 | out_err: |
901 | us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); |
902 | return ret; |
903 | |
904 | } |
905 | |
906 | static void us5182d_remove(struct i2c_client *client) |
907 | { |
908 | struct us5182d_data *data = iio_priv(indio_dev: i2c_get_clientdata(client)); |
909 | int ret; |
910 | |
911 | iio_device_unregister(indio_dev: i2c_get_clientdata(client)); |
912 | |
913 | pm_runtime_disable(dev: &client->dev); |
914 | pm_runtime_set_suspended(dev: &client->dev); |
915 | |
916 | ret = us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); |
917 | if (ret) |
918 | dev_warn(&client->dev, "Failed to shut down (%pe)\n" , |
919 | ERR_PTR(ret)); |
920 | } |
921 | |
922 | static int us5182d_suspend(struct device *dev) |
923 | { |
924 | struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); |
925 | struct us5182d_data *data = iio_priv(indio_dev); |
926 | |
927 | if (data->power_mode == US5182D_CONTINUOUS) |
928 | return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); |
929 | |
930 | return 0; |
931 | } |
932 | |
933 | static int us5182d_resume(struct device *dev) |
934 | { |
935 | struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); |
936 | struct us5182d_data *data = iio_priv(indio_dev); |
937 | |
938 | if (data->power_mode == US5182D_CONTINUOUS) |
939 | return us5182d_shutdown_en(data, |
940 | state: ~US5182D_CFG0_SHUTDOWN_EN & 0xff); |
941 | |
942 | return 0; |
943 | } |
944 | |
945 | static const struct dev_pm_ops us5182d_pm_ops = { |
946 | SYSTEM_SLEEP_PM_OPS(us5182d_suspend, us5182d_resume) |
947 | RUNTIME_PM_OPS(us5182d_suspend, us5182d_resume, NULL) |
948 | }; |
949 | |
950 | static const struct acpi_device_id us5182d_acpi_match[] = { |
951 | { "USD5182" , 0 }, |
952 | {} |
953 | }; |
954 | |
955 | MODULE_DEVICE_TABLE(acpi, us5182d_acpi_match); |
956 | |
957 | static const struct i2c_device_id us5182d_id[] = { |
958 | { "usd5182" , 0 }, |
959 | {} |
960 | }; |
961 | |
962 | MODULE_DEVICE_TABLE(i2c, us5182d_id); |
963 | |
964 | static const struct of_device_id us5182d_of_match[] = { |
965 | { .compatible = "upisemi,usd5182" }, |
966 | {} |
967 | }; |
968 | MODULE_DEVICE_TABLE(of, us5182d_of_match); |
969 | |
970 | static struct i2c_driver us5182d_driver = { |
971 | .driver = { |
972 | .name = US5182D_DRV_NAME, |
973 | .pm = pm_ptr(&us5182d_pm_ops), |
974 | .of_match_table = us5182d_of_match, |
975 | .acpi_match_table = ACPI_PTR(us5182d_acpi_match), |
976 | }, |
977 | .probe = us5182d_probe, |
978 | .remove = us5182d_remove, |
979 | .id_table = us5182d_id, |
980 | |
981 | }; |
982 | module_i2c_driver(us5182d_driver); |
983 | |
984 | MODULE_AUTHOR("Adriana Reus <adriana.reus@intel.com>" ); |
985 | MODULE_DESCRIPTION("Driver for us5182d Proximity and Light Sensor" ); |
986 | MODULE_LICENSE("GPL v2" ); |
987 | |