aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdriana Reus <adriana.reus@intel.com>2015-09-16 04:14:11 -0400
committerJonathan Cameron <jic23@kernel.org>2015-09-23 15:23:27 -0400
commitc14f8abe5304ba46b898cd381a0857fd61f87200 (patch)
treed6d2dcb02b6b24a32d9efdf406c40374657ad8b9
parentfd2bb310ca3d3621a0f201e018e1292dca95df6e (diff)
iio: light: Add support for UPISEMI uS5182d als and proximity sensor
Add support for UPISEMI us5182d als and proximity sensor. Supports raw readings. Data sheet for this device can be found here: http://www.upi-semi.com/temp/uS5182D-DS-P0103-temp.pdf Signed-off-by: Adriana Reus <adriana.reus@intel.com> Signed-off-by: Jonathan Cameron <jic23@kernel.org>
-rw-r--r--drivers/iio/light/Kconfig10
-rw-r--r--drivers/iio/light/Makefile1
-rw-r--r--drivers/iio/light/us5182d.c507
3 files changed, 518 insertions, 0 deletions
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 19b9a173fe61..cfd3df8416bb 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -300,6 +300,16 @@ config TSL4531
300 To compile this driver as a module, choose M here: the 300 To compile this driver as a module, choose M here: the
301 module will be called tsl4531. 301 module will be called tsl4531.
302 302
303config US5182D
304 tristate "UPISEMI light and proximity sensor"
305 depends on I2C
306 help
307 If you say yes here you get support for the UPISEMI US5182D
308 ambient light and proximity sensor.
309
310 This driver can also be built as a module. If so, the module
311 will be called us5182d.
312
303config VCNL4000 313config VCNL4000
304 tristate "VCNL4000 combined ALS and proximity sensor" 314 tristate "VCNL4000 combined ALS and proximity sensor"
305 depends on I2C 315 depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index 7b2244550747..b2c31053db0c 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -28,4 +28,5 @@ obj-$(CONFIG_STK3310) += stk3310.o
28obj-$(CONFIG_TCS3414) += tcs3414.o 28obj-$(CONFIG_TCS3414) += tcs3414.o
29obj-$(CONFIG_TCS3472) += tcs3472.o 29obj-$(CONFIG_TCS3472) += tcs3472.o
30obj-$(CONFIG_TSL4531) += tsl4531.o 30obj-$(CONFIG_TSL4531) += tsl4531.o
31obj-$(CONFIG_US5182D) += us5182d.o
31obj-$(CONFIG_VCNL4000) += vcnl4000.o 32obj-$(CONFIG_VCNL4000) += vcnl4000.o
diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c
new file mode 100644
index 000000000000..49dab3cb3e23
--- /dev/null
+++ b/drivers/iio/light/us5182d.c
@@ -0,0 +1,507 @@
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>
23#include <linux/iio/iio.h>
24#include <linux/iio/sysfs.h>
25#include <linux/mutex.h>
26
27#define US5182D_REG_CFG0 0x00
28#define US5182D_CFG0_ONESHOT_EN BIT(6)
29#define US5182D_CFG0_SHUTDOWN_EN BIT(7)
30#define US5182D_CFG0_WORD_ENABLE BIT(0)
31
32#define US5182D_REG_CFG1 0x01
33#define US5182D_CFG1_ALS_RES16 BIT(4)
34#define US5182D_CFG1_AGAIN_DEFAULT 0x00
35
36#define US5182D_REG_CFG2 0x02
37#define US5182D_CFG2_PX_RES16 BIT(4)
38#define US5182D_CFG2_PXGAIN_DEFAULT BIT(2)
39
40#define US5182D_REG_CFG3 0x03
41#define US5182D_CFG3_LED_CURRENT100 (BIT(4) | BIT(5))
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#define US5182D_OPMODE_ALS 0x01
57#define US5182D_OPMODE_PX 0x02
58#define US5182D_OPMODE_SHIFT 4
59
60#define US5182D_REG_DARK_AUTO_EN_DEFAULT 0x80
61#define US5182D_REG_AUTO_LDARK_GAIN_DEFAULT 0x16
62#define US5182D_REG_AUTO_HDARK_GAIN_DEFAULT 0x00
63
64#define US5182D_REG_ADL 0x0c
65#define US5182D_REG_PDL 0x0e
66
67#define US5182D_REG_MODE_STORE 0x21
68#define US5182D_STORE_MODE 0x01
69
70#define US5182D_REG_CHIPID 0xb2
71
72#define US5182D_OPMODE_MASK GENMASK(5, 4)
73#define US5182D_AGAIN_MASK 0x07
74#define US5182D_RESET_CHIP 0x01
75
76#define US5182D_CHIPID 0x26
77#define US5182D_DRV_NAME "us5182d"
78
79#define US5182D_GA_RESOLUTION 1000
80
81#define US5182D_READ_BYTE 1
82#define US5182D_READ_WORD 2
83#define US5182D_OPSTORE_SLEEP_TIME 20 /* ms */
84
85/* Available ranges: [12354, 7065, 3998, 2202, 1285, 498, 256, 138] lux */
86static const int us5182d_scales[] = {188500, 107800, 61000, 33600, 19600, 7600,
87 3900, 2100};
88
89/*
90 * Experimental thresholds that work with US5182D sensor on evaluation board
91 * roughly between 12-32 lux
92 */
93static u16 us5182d_dark_ths_vals[] = {170, 200, 512, 512, 800, 2000, 4000,
94 8000};
95
96enum mode {
97 US5182D_ALS_PX,
98 US5182D_ALS_ONLY,
99 US5182D_PX_ONLY
100};
101
102struct us5182d_data {
103 struct i2c_client *client;
104 struct mutex lock;
105
106 /* Glass attenuation factor */
107 u32 ga;
108
109 /* Dark gain tuning */
110 u8 lower_dark_gain;
111 u8 upper_dark_gain;
112 u16 *us5182d_dark_ths;
113
114 u8 opmode;
115};
116
117static IIO_CONST_ATTR(in_illuminance_scale_available,
118 "0.0021 0.0039 0.0076 0.0196 0.0336 0.061 0.1078 0.1885");
119
120static struct attribute *us5182d_attrs[] = {
121 &iio_const_attr_in_illuminance_scale_available.dev_attr.attr,
122 NULL
123};
124
125static const struct attribute_group us5182d_attr_group = {
126 .attrs = us5182d_attrs,
127};
128
129static const struct {
130 u8 reg;
131 u8 val;
132} us5182d_regvals[] = {
133 {US5182D_REG_CFG0, (US5182D_CFG0_SHUTDOWN_EN |
134 US5182D_CFG0_WORD_ENABLE)},
135 {US5182D_REG_CFG1, US5182D_CFG1_ALS_RES16},
136 {US5182D_REG_CFG2, (US5182D_CFG2_PX_RES16 |
137 US5182D_CFG2_PXGAIN_DEFAULT)},
138 {US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100},
139 {US5182D_REG_MODE_STORE, US5182D_STORE_MODE},
140 {US5182D_REG_CFG4, 0x00},
141};
142
143static const struct iio_chan_spec us5182d_channels[] = {
144 {
145 .type = IIO_LIGHT,
146 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
147 BIT(IIO_CHAN_INFO_SCALE),
148 },
149 {
150 .type = IIO_PROXIMITY,
151 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
152 }
153};
154
155static int us5182d_get_als(struct us5182d_data *data)
156{
157 int ret;
158 unsigned long result;
159
160 ret = i2c_smbus_read_word_data(data->client,
161 US5182D_REG_ADL);
162 if (ret < 0)
163 return ret;
164
165 result = ret * data->ga / US5182D_GA_RESOLUTION;
166 if (result > 0xffff)
167 result = 0xffff;
168
169 return result;
170}
171
172static int us5182d_set_opmode(struct us5182d_data *data, u8 mode)
173{
174 int ret;
175
176 ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0);
177 if (ret < 0)
178 return ret;
179
180 /*
181 * In oneshot mode the chip will power itself down after taking the
182 * required measurement.
183 */
184 ret = ret | US5182D_CFG0_ONESHOT_EN;
185
186 /* update mode */
187 ret = ret & ~US5182D_OPMODE_MASK;
188 ret = ret | (mode << US5182D_OPMODE_SHIFT);
189
190 /*
191 * After updating the operating mode, the chip requires that
192 * the operation is stored, by writing 1 in the STORE_MODE
193 * register (auto-clearing).
194 */
195 ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret);
196 if (ret < 0)
197 return ret;
198
199 if (mode == data->opmode)
200 return 0;
201
202 ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_MODE_STORE,
203 US5182D_STORE_MODE);
204 if (ret < 0)
205 return ret;
206
207 data->opmode = mode;
208 msleep(US5182D_OPSTORE_SLEEP_TIME);
209
210 return 0;
211}
212
213static int us5182d_read_raw(struct iio_dev *indio_dev,
214 struct iio_chan_spec const *chan, int *val,
215 int *val2, long mask)
216{
217 struct us5182d_data *data = iio_priv(indio_dev);
218 int ret;
219
220 switch (mask) {
221 case IIO_CHAN_INFO_RAW:
222 switch (chan->type) {
223 case IIO_LIGHT:
224 mutex_lock(&data->lock);
225 ret = us5182d_set_opmode(data, US5182D_OPMODE_ALS);
226 if (ret < 0)
227 goto out_err;
228
229 ret = us5182d_get_als(data);
230 if (ret < 0)
231 goto out_err;
232 mutex_unlock(&data->lock);
233 *val = ret;
234 return IIO_VAL_INT;
235 case IIO_PROXIMITY:
236 mutex_lock(&data->lock);
237 ret = us5182d_set_opmode(data, US5182D_OPMODE_PX);
238 if (ret < 0)
239 goto out_err;
240
241 ret = i2c_smbus_read_word_data(data->client,
242 US5182D_REG_PDL);
243 if (ret < 0)
244 goto out_err;
245 mutex_unlock(&data->lock);
246 *val = ret;
247 return IIO_VAL_INT;
248 default:
249 return -EINVAL;
250 }
251
252 case IIO_CHAN_INFO_SCALE:
253 ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG1);
254 if (ret < 0)
255 return ret;
256
257 *val = 0;
258 *val2 = us5182d_scales[ret & US5182D_AGAIN_MASK];
259
260 return IIO_VAL_INT_PLUS_MICRO;
261 default:
262 return -EINVAL;
263 }
264
265 return -EINVAL;
266out_err:
267 mutex_unlock(&data->lock);
268 return ret;
269}
270
271/**
272 * us5182d_update_dark_th - update Darh_Th registers
273 * @data us5182d_data structure
274 * @index index in us5182d_dark_ths array to use for the updated value
275 *
276 * Function needs to be called with a lock held because it needs two i2c write
277 * byte operations as these registers (0x27 0x28) don't work in word mode
278 * accessing.
279 */
280static int us5182d_update_dark_th(struct us5182d_data *data, int index)
281{
282 __be16 dark_th = cpu_to_be16(data->us5182d_dark_ths[index]);
283 int ret;
284
285 ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_UDARK_TH,
286 ((u8 *)&dark_th)[0]);
287 if (ret < 0)
288 return ret;
289
290 return i2c_smbus_write_byte_data(data->client, US5182D_REG_UDARK_TH + 1,
291 ((u8 *)&dark_th)[1]);
292}
293
294/**
295 * us5182d_apply_scale - update the ALS scale
296 * @data us5182d_data structure
297 * @index index in us5182d_scales array to use for the updated value
298 *
299 * Function needs to be called with a lock held as we're having more than one
300 * i2c operation.
301 */
302static int us5182d_apply_scale(struct us5182d_data *data, int index)
303{
304 int ret;
305
306 ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG1);
307 if (ret < 0)
308 return ret;
309
310 ret = ret & (~US5182D_AGAIN_MASK);
311 ret |= index;
312
313 ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG1, ret);
314 if (ret < 0)
315 return ret;
316
317 return us5182d_update_dark_th(data, index);
318}
319
320static int us5182d_write_raw(struct iio_dev *indio_dev,
321 struct iio_chan_spec const *chan, int val,
322 int val2, long mask)
323{
324 struct us5182d_data *data = iio_priv(indio_dev);
325 int ret, i;
326
327 switch (mask) {
328 case IIO_CHAN_INFO_SCALE:
329 if (val != 0)
330 return -EINVAL;
331 for (i = 0; i < ARRAY_SIZE(us5182d_scales); i++)
332 if (val2 == us5182d_scales[i]) {
333 mutex_lock(&data->lock);
334 ret = us5182d_apply_scale(data, i);
335 mutex_unlock(&data->lock);
336 return ret;
337 }
338 break;
339 default:
340 return -EINVAL;
341 }
342
343 return -EINVAL;
344}
345
346static const struct iio_info us5182d_info = {
347 .driver_module = THIS_MODULE,
348 .read_raw = us5182d_read_raw,
349 .write_raw = us5182d_write_raw,
350 .attrs = &us5182d_attr_group,
351};
352
353static int us5182d_reset(struct iio_dev *indio_dev)
354{
355 struct us5182d_data *data = iio_priv(indio_dev);
356
357 return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG3,
358 US5182D_RESET_CHIP);
359}
360
361static int us5182d_init(struct iio_dev *indio_dev)
362{
363 struct us5182d_data *data = iio_priv(indio_dev);
364 int i, ret;
365
366 ret = us5182d_reset(indio_dev);
367 if (ret < 0)
368 return ret;
369
370 data->opmode = 0;
371 for (i = 0; i < ARRAY_SIZE(us5182d_regvals); i++) {
372 ret = i2c_smbus_write_byte_data(data->client,
373 us5182d_regvals[i].reg,
374 us5182d_regvals[i].val);
375 if (ret < 0)
376 return ret;
377 }
378
379 return 0;
380}
381
382static void us5182d_get_platform_data(struct iio_dev *indio_dev)
383{
384 struct us5182d_data *data = iio_priv(indio_dev);
385
386 if (device_property_read_u32(&data->client->dev, "upisemi,glass-coef",
387 &data->ga))
388 data->ga = US5182D_GA_RESOLUTION;
389 if (device_property_read_u16_array(&data->client->dev,
390 "upisemi,dark-ths",
391 data->us5182d_dark_ths,
392 ARRAY_SIZE(us5182d_dark_ths_vals)))
393 data->us5182d_dark_ths = us5182d_dark_ths_vals;
394 if (device_property_read_u8(&data->client->dev,
395 "upisemi,upper-dark-gain",
396 &data->upper_dark_gain))
397 data->upper_dark_gain = US5182D_REG_AUTO_HDARK_GAIN_DEFAULT;
398 if (device_property_read_u8(&data->client->dev,
399 "upisemi,lower-dark-gain",
400 &data->lower_dark_gain))
401 data->lower_dark_gain = US5182D_REG_AUTO_LDARK_GAIN_DEFAULT;
402}
403
404static int us5182d_dark_gain_config(struct iio_dev *indio_dev)
405{
406 struct us5182d_data *data = iio_priv(indio_dev);
407 int ret;
408
409 ret = us5182d_update_dark_th(data, US5182D_CFG1_AGAIN_DEFAULT);
410 if (ret < 0)
411 return ret;
412
413 ret = i2c_smbus_write_byte_data(data->client,
414 US5182D_REG_AUTO_LDARK_GAIN,
415 data->lower_dark_gain);
416 if (ret < 0)
417 return ret;
418
419 ret = i2c_smbus_write_byte_data(data->client,
420 US5182D_REG_AUTO_HDARK_GAIN,
421 data->upper_dark_gain);
422 if (ret < 0)
423 return ret;
424
425 return i2c_smbus_write_byte_data(data->client, US5182D_REG_DARK_AUTO_EN,
426 US5182D_REG_DARK_AUTO_EN_DEFAULT);
427}
428
429static int us5182d_probe(struct i2c_client *client,
430 const struct i2c_device_id *id)
431{
432 struct us5182d_data *data;
433 struct iio_dev *indio_dev;
434 int ret;
435
436 indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
437 if (!indio_dev)
438 return -ENOMEM;
439
440 data = iio_priv(indio_dev);
441 i2c_set_clientdata(client, indio_dev);
442 data->client = client;
443
444 mutex_init(&data->lock);
445
446 indio_dev->dev.parent = &client->dev;
447 indio_dev->info = &us5182d_info;
448 indio_dev->name = US5182D_DRV_NAME;
449 indio_dev->channels = us5182d_channels;
450 indio_dev->num_channels = ARRAY_SIZE(us5182d_channels);
451 indio_dev->modes = INDIO_DIRECT_MODE;
452
453 ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CHIPID);
454 if (ret != US5182D_CHIPID) {
455 dev_err(&data->client->dev,
456 "Failed to detect US5182 light chip\n");
457 return (ret < 0) ? ret : -ENODEV;
458 }
459
460 us5182d_get_platform_data(indio_dev);
461 ret = us5182d_init(indio_dev);
462 if (ret < 0)
463 return ret;
464
465 ret = us5182d_dark_gain_config(indio_dev);
466 if (ret < 0)
467 return ret;
468
469 return iio_device_register(indio_dev);
470}
471
472static int us5182d_remove(struct i2c_client *client)
473{
474 iio_device_unregister(i2c_get_clientdata(client));
475 return i2c_smbus_write_byte_data(client, US5182D_REG_CFG0,
476 US5182D_CFG0_SHUTDOWN_EN);
477}
478
479static const struct acpi_device_id us5182d_acpi_match[] = {
480 { "USD5182", 0},
481 {}
482};
483
484MODULE_DEVICE_TABLE(acpi, us5182d_acpi_match);
485
486static const struct i2c_device_id us5182d_id[] = {
487 {"usd5182", 0},
488 {}
489};
490
491MODULE_DEVICE_TABLE(i2c, us5182d_id);
492
493static struct i2c_driver us5182d_driver = {
494 .driver = {
495 .name = US5182D_DRV_NAME,
496 .acpi_match_table = ACPI_PTR(us5182d_acpi_match),
497 },
498 .probe = us5182d_probe,
499 .remove = us5182d_remove,
500 .id_table = us5182d_id,
501
502};
503module_i2c_driver(us5182d_driver);
504
505MODULE_AUTHOR("Adriana Reus <adriana.reus@intel.com>");
506MODULE_DESCRIPTION("Driver for us5182d Proximity and Light Sensor");
507MODULE_LICENSE("GPL v2");