aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Mack <daniel@caiaq.de>2009-03-31 18:23:53 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2009-04-01 11:59:18 -0400
commit3cdbbeebb77348176bd6a03fd86e11bc281c529e (patch)
tree079d84c58cdb0e6f4d43eee69d42d26d5a48af35
parent891f7d73ea30f925596b90bcf21020bfc5d90f3f (diff)
drivers/misc/isl29003.c: driver for the ISL29003 ambient light sensor
Add a driver for Intersil's ISL29003 ambient light sensor device plus some documentation. Inspired by tsl2550.c, a driver for a similar device. It is put in drivers/misc for now until the industrial I/O framework gets merged. Signed-off-by: Daniel Mack <daniel@caiaq.de> Acked-by: Jonathan Cameron <jic23@cam.ac.uk> Cc: Jean Delvare <khali@linux-fr.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--Documentation/misc-devices/isl2900362
-rw-r--r--drivers/misc/Kconfig10
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/isl29003.c470
4 files changed, 543 insertions, 0 deletions
diff --git a/Documentation/misc-devices/isl29003 b/Documentation/misc-devices/isl29003
new file mode 100644
index 000000000000..c4ff5f38e010
--- /dev/null
+++ b/Documentation/misc-devices/isl29003
@@ -0,0 +1,62 @@
1Kernel driver isl29003
2=====================
3
4Supported chips:
5* Intersil ISL29003
6Prefix: 'isl29003'
7Addresses scanned: none
8Datasheet:
9http://www.intersil.com/data/fn/fn7464.pdf
10
11Author: Daniel Mack <daniel@caiaq.de>
12
13
14Description
15-----------
16The ISL29003 is an integrated light sensor with a 16-bit integrating type
17ADC, I2C user programmable lux range select for optimized counts/lux, and
18I2C multi-function control and monitoring capabilities. The internal ADC
19provides 16-bit resolution while rejecting 50Hz and 60Hz flicker caused by
20artificial light sources.
21
22The driver allows to set the lux range, the bit resolution, the operational
23mode (see below) and the power state of device and can read the current lux
24value, of course.
25
26
27Detection
28---------
29
30The ISL29003 does not have an ID register which could be used to identify
31it, so the detection routine will just try to read from the configured I2C
32addess and consider the device to be present as soon as it ACKs the
33transfer.
34
35
36Sysfs entries
37-------------
38
39range:
40 0: 0 lux to 1000 lux (default)
41 1: 0 lux to 4000 lux
42 2: 0 lux to 16,000 lux
43 3: 0 lux to 64,000 lux
44
45resolution:
46 0: 2^16 cycles (default)
47 1: 2^12 cycles
48 2: 2^8 cycles
49 3: 2^4 cycles
50
51mode:
52 0: diode1's current (unsigned 16bit) (default)
53 1: diode1's current (unsigned 16bit)
54 2: difference between diodes (l1 - l2, signed 15bit)
55
56power_state:
57 0: device is disabled (default)
58 1: device is enabled
59
60lux (read only):
61 returns the value from the last sensor reading
62
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 1c484084ed4f..5f3bff434621 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -223,6 +223,16 @@ config DELL_LAPTOP
223 This driver adds support for rfkill and backlight control to Dell 223 This driver adds support for rfkill and backlight control to Dell
224 laptops. 224 laptops.
225 225
226config ISL29003
227 tristate "Intersil ISL29003 ambient light sensor"
228 depends on I2C && SYSFS
229 help
230 If you say yes here you get support for the Intersil ISL29003
231 ambient light sensor.
232
233 This driver can also be built as a module. If so, the module
234 will be called isl29003.
235
226source "drivers/misc/c2port/Kconfig" 236source "drivers/misc/c2port/Kconfig"
227source "drivers/misc/eeprom/Kconfig" 237source "drivers/misc/eeprom/Kconfig"
228 238
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index bc1199830554..7871f05dcb9b 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -18,5 +18,6 @@ obj-$(CONFIG_KGDB_TESTS) += kgdbts.o
18obj-$(CONFIG_SGI_XP) += sgi-xp/ 18obj-$(CONFIG_SGI_XP) += sgi-xp/
19obj-$(CONFIG_SGI_GRU) += sgi-gru/ 19obj-$(CONFIG_SGI_GRU) += sgi-gru/
20obj-$(CONFIG_HP_ILO) += hpilo.o 20obj-$(CONFIG_HP_ILO) += hpilo.o
21obj-$(CONFIG_ISL29003) += isl29003.o
21obj-$(CONFIG_C2PORT) += c2port/ 22obj-$(CONFIG_C2PORT) += c2port/
22obj-y += eeprom/ 23obj-y += eeprom/
diff --git a/drivers/misc/isl29003.c b/drivers/misc/isl29003.c
new file mode 100644
index 000000000000..2e2a5923d4c2
--- /dev/null
+++ b/drivers/misc/isl29003.c
@@ -0,0 +1,470 @@
1/*
2 * isl29003.c - Linux kernel module for
3 * Intersil ISL29003 ambient light sensor
4 *
5 * See file:Documentation/misc-devices/isl29003
6 *
7 * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
8 *
9 * Based on code written by
10 * Rodolfo Giometti <giometti@linux.it>
11 * Eurotech S.p.A. <info@eurotech.it>
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 */
27
28#include <linux/module.h>
29#include <linux/init.h>
30#include <linux/slab.h>
31#include <linux/i2c.h>
32#include <linux/mutex.h>
33#include <linux/delay.h>
34
35#define ISL29003_DRV_NAME "isl29003"
36#define DRIVER_VERSION "1.0"
37
38#define ISL29003_REG_COMMAND 0x00
39#define ISL29003_ADC_ENABLED (1 << 7)
40#define ISL29003_ADC_PD (1 << 6)
41#define ISL29003_TIMING_INT (1 << 5)
42#define ISL29003_MODE_SHIFT (2)
43#define ISL29003_MODE_MASK (0x3 << ISL29003_MODE_SHIFT)
44#define ISL29003_RES_SHIFT (0)
45#define ISL29003_RES_MASK (0x3 << ISL29003_RES_SHIFT)
46
47#define ISL29003_REG_CONTROL 0x01
48#define ISL29003_INT_FLG (1 << 5)
49#define ISL29003_RANGE_SHIFT (2)
50#define ISL29003_RANGE_MASK (0x3 << ISL29003_RANGE_SHIFT)
51#define ISL29003_INT_PERSISTS_SHIFT (0)
52#define ISL29003_INT_PERSISTS_MASK (0xf << ISL29003_INT_PERSISTS_SHIFT)
53
54#define ISL29003_REG_IRQ_THRESH_HI 0x02
55#define ISL29003_REG_IRQ_THRESH_LO 0x03
56#define ISL29003_REG_LSB_SENSOR 0x04
57#define ISL29003_REG_MSB_SENSOR 0x05
58#define ISL29003_REG_LSB_TIMER 0x06
59#define ISL29003_REG_MSB_TIMER 0x07
60
61#define ISL29003_NUM_CACHABLE_REGS 4
62
63struct isl29003_data {
64 struct i2c_client *client;
65 struct mutex lock;
66 u8 reg_cache[ISL29003_NUM_CACHABLE_REGS];
67};
68
69static int gain_range[] = {
70 1000, 4000, 16000, 64000
71};
72
73/*
74 * register access helpers
75 */
76
77static int __isl29003_read_reg(struct i2c_client *client,
78 u32 reg, u8 mask, u8 shift)
79{
80 struct isl29003_data *data = i2c_get_clientdata(client);
81 return (data->reg_cache[reg] & mask) >> shift;
82}
83
84static int __isl29003_write_reg(struct i2c_client *client,
85 u32 reg, u8 mask, u8 shift, u8 val)
86{
87 struct isl29003_data *data = i2c_get_clientdata(client);
88 int ret = 0;
89 u8 tmp;
90
91 if (reg >= ISL29003_NUM_CACHABLE_REGS)
92 return -EINVAL;
93
94 mutex_lock(&data->lock);
95
96 tmp = data->reg_cache[reg];
97 tmp &= ~mask;
98 tmp |= val << shift;
99
100 ret = i2c_smbus_write_byte_data(client, reg, tmp);
101 if (!ret)
102 data->reg_cache[reg] = tmp;
103
104 mutex_unlock(&data->lock);
105 return ret;
106}
107
108/*
109 * internally used functions
110 */
111
112/* range */
113static int isl29003_get_range(struct i2c_client *client)
114{
115 return __isl29003_read_reg(client, ISL29003_REG_CONTROL,
116 ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT);
117}
118
119static int isl29003_set_range(struct i2c_client *client, int range)
120{
121 return __isl29003_write_reg(client, ISL29003_REG_CONTROL,
122 ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT, range);
123}
124
125/* resolution */
126static int isl29003_get_resolution(struct i2c_client *client)
127{
128 return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
129 ISL29003_RES_MASK, ISL29003_RES_SHIFT);
130}
131
132static int isl29003_set_resolution(struct i2c_client *client, int res)
133{
134 return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
135 ISL29003_RES_MASK, ISL29003_RES_SHIFT, res);
136}
137
138/* mode */
139static int isl29003_get_mode(struct i2c_client *client)
140{
141 return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
142 ISL29003_RES_MASK, ISL29003_RES_SHIFT);
143}
144
145static int isl29003_set_mode(struct i2c_client *client, int mode)
146{
147 return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
148 ISL29003_RES_MASK, ISL29003_RES_SHIFT, mode);
149}
150
151/* power_state */
152static int isl29003_set_power_state(struct i2c_client *client, int state)
153{
154 return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
155 ISL29003_ADC_ENABLED | ISL29003_ADC_PD, 0,
156 state ? ISL29003_ADC_ENABLED : ISL29003_ADC_PD);
157}
158
159static int isl29003_get_power_state(struct i2c_client *client)
160{
161 struct isl29003_data *data = i2c_get_clientdata(client);
162 u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
163 return ~cmdreg & ISL29003_ADC_PD;
164}
165
166static int isl29003_get_adc_value(struct i2c_client *client)
167{
168 struct isl29003_data *data = i2c_get_clientdata(client);
169 int lsb, msb, range, bitdepth;
170
171 mutex_lock(&data->lock);
172 lsb = i2c_smbus_read_byte_data(client, ISL29003_REG_LSB_SENSOR);
173
174 if (lsb < 0) {
175 mutex_unlock(&data->lock);
176 return lsb;
177 }
178
179 msb = i2c_smbus_read_byte_data(client, ISL29003_REG_MSB_SENSOR);
180 mutex_unlock(&data->lock);
181
182 if (msb < 0)
183 return msb;
184
185 range = isl29003_get_range(client);
186 bitdepth = (4 - isl29003_get_resolution(client)) * 4;
187 return (((msb << 8) | lsb) * gain_range[range]) >> bitdepth;
188}
189
190/*
191 * sysfs layer
192 */
193
194/* range */
195static ssize_t isl29003_show_range(struct device *dev,
196 struct device_attribute *attr, char *buf)
197{
198 struct i2c_client *client = to_i2c_client(dev);
199 return sprintf(buf, "%i\n", isl29003_get_range(client));
200}
201
202static ssize_t isl29003_store_range(struct device *dev,
203 struct device_attribute *attr,
204 const char *buf, size_t count)
205{
206 struct i2c_client *client = to_i2c_client(dev);
207 unsigned long val;
208 int ret;
209
210 if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
211 return -EINVAL;
212
213 ret = isl29003_set_range(client, val);
214 if (ret < 0)
215 return ret;
216
217 return count;
218}
219
220static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
221 isl29003_show_range, isl29003_store_range);
222
223
224/* resolution */
225static ssize_t isl29003_show_resolution(struct device *dev,
226 struct device_attribute *attr,
227 char *buf)
228{
229 struct i2c_client *client = to_i2c_client(dev);
230 return sprintf(buf, "%d\n", isl29003_get_resolution(client));
231}
232
233static ssize_t isl29003_store_resolution(struct device *dev,
234 struct device_attribute *attr,
235 const char *buf, size_t count)
236{
237 struct i2c_client *client = to_i2c_client(dev);
238 unsigned long val;
239 int ret;
240
241 if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
242 return -EINVAL;
243
244 ret = isl29003_set_resolution(client, val);
245 if (ret < 0)
246 return ret;
247
248 return count;
249}
250
251static DEVICE_ATTR(resolution, S_IWUSR | S_IRUGO,
252 isl29003_show_resolution, isl29003_store_resolution);
253
254/* mode */
255static ssize_t isl29003_show_mode(struct device *dev,
256 struct device_attribute *attr, char *buf)
257{
258 struct i2c_client *client = to_i2c_client(dev);
259 return sprintf(buf, "%d\n", isl29003_get_mode(client));
260}
261
262static ssize_t isl29003_store_mode(struct device *dev,
263 struct device_attribute *attr, const char *buf, size_t count)
264{
265 struct i2c_client *client = to_i2c_client(dev);
266 unsigned long val;
267 int ret;
268
269 if ((strict_strtoul(buf, 10, &val) < 0) || (val > 2))
270 return -EINVAL;
271
272 ret = isl29003_set_mode(client, val);
273 if (ret < 0)
274 return ret;
275
276 return count;
277}
278
279static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
280 isl29003_show_mode, isl29003_store_mode);
281
282
283/* power state */
284static ssize_t isl29003_show_power_state(struct device *dev,
285 struct device_attribute *attr,
286 char *buf)
287{
288 struct i2c_client *client = to_i2c_client(dev);
289 return sprintf(buf, "%d\n", isl29003_get_power_state(client));
290}
291
292static ssize_t isl29003_store_power_state(struct device *dev,
293 struct device_attribute *attr,
294 const char *buf, size_t count)
295{
296 struct i2c_client *client = to_i2c_client(dev);
297 unsigned long val;
298 int ret;
299
300 if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
301 return -EINVAL;
302
303 ret = isl29003_set_power_state(client, val);
304 return ret ? ret : count;
305}
306
307static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO,
308 isl29003_show_power_state, isl29003_store_power_state);
309
310
311/* lux */
312static ssize_t isl29003_show_lux(struct device *dev,
313 struct device_attribute *attr, char *buf)
314{
315 struct i2c_client *client = to_i2c_client(dev);
316
317 /* No LUX data if not operational */
318 if (!isl29003_get_power_state(client))
319 return -EBUSY;
320
321 return sprintf(buf, "%d\n", isl29003_get_adc_value(client));
322}
323
324static DEVICE_ATTR(lux, S_IRUGO, isl29003_show_lux, NULL);
325
326static struct attribute *isl29003_attributes[] = {
327 &dev_attr_range.attr,
328 &dev_attr_resolution.attr,
329 &dev_attr_mode.attr,
330 &dev_attr_power_state.attr,
331 &dev_attr_lux.attr,
332 NULL
333};
334
335static const struct attribute_group isl29003_attr_group = {
336 .attrs = isl29003_attributes,
337};
338
339static int isl29003_init_client(struct i2c_client *client)
340{
341 struct isl29003_data *data = i2c_get_clientdata(client);
342 int i;
343
344 /* read all the registers once to fill the cache.
345 * if one of the reads fails, we consider the init failed */
346 for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++) {
347 int v = i2c_smbus_read_byte_data(client, i);
348 if (v < 0)
349 return -ENODEV;
350
351 data->reg_cache[i] = v;
352 }
353
354 /* set defaults */
355 isl29003_set_range(client, 0);
356 isl29003_set_resolution(client, 0);
357 isl29003_set_mode(client, 0);
358 isl29003_set_power_state(client, 0);
359
360 return 0;
361}
362
363/*
364 * I2C layer
365 */
366
367static int __devinit isl29003_probe(struct i2c_client *client,
368 const struct i2c_device_id *id)
369{
370 struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
371 struct isl29003_data *data;
372 int err = 0;
373
374 if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
375 return -EIO;
376
377 data = kzalloc(sizeof(struct isl29003_data), GFP_KERNEL);
378 if (!data)
379 return -ENOMEM;
380
381 data->client = client;
382 i2c_set_clientdata(client, data);
383 mutex_init(&data->lock);
384
385 /* initialize the ISL29003 chip */
386 err = isl29003_init_client(client);
387 if (err)
388 goto exit_kfree;
389
390 /* register sysfs hooks */
391 err = sysfs_create_group(&client->dev.kobj, &isl29003_attr_group);
392 if (err)
393 goto exit_kfree;
394
395 dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);
396 return 0;
397
398exit_kfree:
399 kfree(data);
400 return err;
401}
402
403static int __devexit isl29003_remove(struct i2c_client *client)
404{
405 sysfs_remove_group(&client->dev.kobj, &isl29003_attr_group);
406 isl29003_set_power_state(client, 0);
407 kfree(i2c_get_clientdata(client));
408 return 0;
409}
410
411#ifdef CONFIG_PM
412static int isl29003_suspend(struct i2c_client *client, pm_message_t mesg)
413{
414 return isl29003_set_power_state(client, 0);
415}
416
417static int isl29003_resume(struct i2c_client *client)
418{
419 int i;
420 struct isl29003_data *data = i2c_get_clientdata(client);
421
422 /* restore registers from cache */
423 for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
424 if (!i2c_smbus_write_byte_data(client, i, data->reg_cache[i]))
425 return -EIO;
426
427 return 0;
428}
429
430#else
431#define isl29003_suspend NULL
432#define isl29003_resume NULL
433#endif /* CONFIG_PM */
434
435static const struct i2c_device_id isl29003_id[] = {
436 { "isl29003", 0 },
437 {}
438};
439MODULE_DEVICE_TABLE(i2c, isl29003_id);
440
441static struct i2c_driver isl29003_driver = {
442 .driver = {
443 .name = ISL29003_DRV_NAME,
444 .owner = THIS_MODULE,
445 },
446 .suspend = isl29003_suspend,
447 .resume = isl29003_resume,
448 .probe = isl29003_probe,
449 .remove = __devexit_p(isl29003_remove),
450 .id_table = isl29003_id,
451};
452
453static int __init isl29003_init(void)
454{
455 return i2c_add_driver(&isl29003_driver);
456}
457
458static void __exit isl29003_exit(void)
459{
460 i2c_del_driver(&isl29003_driver);
461}
462
463MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
464MODULE_DESCRIPTION("ISL29003 ambient light sensor driver");
465MODULE_LICENSE("GPL v2");
466MODULE_VERSION(DRIVER_VERSION);
467
468module_init(isl29003_init);
469module_exit(isl29003_exit);
470