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