2 * tsl2550.c - Linux kernel modules for ambient light sensor
4 * Copyright (C) 2007 Rodolfo Giometti <giometti@linux.it>
5 * Copyright (C) 2007 Eurotech S.p.A. <info@eurotech.it>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 #include <linux/module.h>
23 #include <linux/init.h>
24 #include <linux/slab.h>
25 #include <linux/i2c.h>
26 #include <linux/mutex.h>
27 #include <linux/delay.h>
29 #define TSL2550_DRV_NAME "tsl2550"
30 #define DRIVER_VERSION "1.1.1"
36 #define TSL2550_POWER_DOWN 0x00
37 #define TSL2550_POWER_UP 0x03
38 #define TSL2550_STANDARD_RANGE 0x18
39 #define TSL2550_EXTENDED_RANGE 0x1d
40 #define TSL2550_READ_ADC0 0x43
41 #define TSL2550_READ_ADC1 0x83
48 struct i2c_client *client;
49 struct mutex update_lock;
51 unsigned int power_state : 1;
52 unsigned int operating_mode : 1;
59 static const u8 TSL2550_MODE_RANGE[2] = {
60 TSL2550_STANDARD_RANGE, TSL2550_EXTENDED_RANGE,
64 * Management functions
67 static int tsl2550_set_operating_mode(struct i2c_client *client, int mode)
69 struct tsl2550_data *data = i2c_get_clientdata(client);
71 int ret = i2c_smbus_write_byte(client, TSL2550_MODE_RANGE[mode]);
73 data->operating_mode = mode;
78 static int tsl2550_set_power_state(struct i2c_client *client, int state)
80 struct tsl2550_data *data = i2c_get_clientdata(client);
84 ret = i2c_smbus_write_byte(client, TSL2550_POWER_DOWN);
86 ret = i2c_smbus_write_byte(client, TSL2550_POWER_UP);
88 /* On power up we should reset operating mode also... */
89 tsl2550_set_operating_mode(client, data->operating_mode);
92 data->power_state = state;
97 static int tsl2550_get_adc_value(struct i2c_client *client, u8 cmd)
100 int loop = 0, ret = 0;
103 * Read ADC channel waiting at most 400ms (see data sheet for further
105 * To avoid long busy wait we spin for few milliseconds then
108 end = jiffies + msecs_to_jiffies(400);
109 while (time_before(jiffies, end)) {
110 i2c_smbus_write_byte(client, cmd);
117 ret = i2c_smbus_read_byte(client);
120 else if (ret & 0x0080)
125 return ret & 0x7f; /* remove the "valid" bit */
132 #define TSL2550_MAX_LUX 1846
134 static const u8 ratio_lut[] = {
135 100, 100, 100, 100, 100, 100, 100, 100,
136 100, 100, 100, 100, 100, 100, 99, 99,
137 99, 99, 99, 99, 99, 99, 99, 99,
138 99, 99, 99, 98, 98, 98, 98, 98,
139 98, 98, 97, 97, 97, 97, 97, 96,
140 96, 96, 96, 95, 95, 95, 94, 94,
141 93, 93, 93, 92, 92, 91, 91, 90,
142 89, 89, 88, 87, 87, 86, 85, 84,
143 83, 82, 81, 80, 79, 78, 77, 75,
144 74, 73, 71, 69, 68, 66, 64, 62,
145 60, 58, 56, 54, 52, 49, 47, 44,
146 42, 41, 40, 40, 39, 39, 38, 38,
147 37, 37, 37, 36, 36, 36, 35, 35,
148 35, 35, 34, 34, 34, 34, 33, 33,
149 33, 33, 32, 32, 32, 32, 32, 31,
150 31, 31, 31, 31, 30, 30, 30, 30,
154 static const u16 count_lut[] = {
155 0, 1, 2, 3, 4, 5, 6, 7,
156 8, 9, 10, 11, 12, 13, 14, 15,
157 16, 18, 20, 22, 24, 26, 28, 30,
158 32, 34, 36, 38, 40, 42, 44, 46,
159 49, 53, 57, 61, 65, 69, 73, 77,
160 81, 85, 89, 93, 97, 101, 105, 109,
161 115, 123, 131, 139, 147, 155, 163, 171,
162 179, 187, 195, 203, 211, 219, 227, 235,
163 247, 263, 279, 295, 311, 327, 343, 359,
164 375, 391, 407, 423, 439, 455, 471, 487,
165 511, 543, 575, 607, 639, 671, 703, 735,
166 767, 799, 831, 863, 895, 927, 959, 991,
167 1039, 1103, 1167, 1231, 1295, 1359, 1423, 1487,
168 1551, 1615, 1679, 1743, 1807, 1871, 1935, 1999,
169 2095, 2223, 2351, 2479, 2607, 2735, 2863, 2991,
170 3119, 3247, 3375, 3503, 3631, 3759, 3887, 4015,
174 * This function is described into Taos TSL2550 Designer's Notebook
177 static int tsl2550_calculate_lux(u8 ch0, u8 ch1)
181 /* Look up count from channel values */
182 u16 c0 = count_lut[ch0];
183 u16 c1 = count_lut[ch1];
187 * Note: the "128" is a scaling factor
191 /* Avoid division by 0 and count 1 cannot be greater than count 0 */
192 if (c0 && (c1 <= c0))
198 lux = ((c0 - c1) * ratio_lut[r]) / 256;
200 /* LUX range check */
201 return lux > TSL2550_MAX_LUX ? TSL2550_MAX_LUX : lux;
208 static ssize_t tsl2550_show_power_state(struct device *dev,
209 struct device_attribute *attr, char *buf)
211 struct tsl2550_data *data = i2c_get_clientdata(to_i2c_client(dev));
213 return sprintf(buf, "%u\n", data->power_state);
216 static ssize_t tsl2550_store_power_state(struct device *dev,
217 struct device_attribute *attr, const char *buf, size_t count)
219 struct i2c_client *client = to_i2c_client(dev);
220 struct tsl2550_data *data = i2c_get_clientdata(client);
221 unsigned long val = simple_strtoul(buf, NULL, 10);
224 if (val < 0 || val > 1)
227 mutex_lock(&data->update_lock);
228 ret = tsl2550_set_power_state(client, val);
229 mutex_unlock(&data->update_lock);
237 static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO,
238 tsl2550_show_power_state, tsl2550_store_power_state);
240 static ssize_t tsl2550_show_operating_mode(struct device *dev,
241 struct device_attribute *attr, char *buf)
243 struct tsl2550_data *data = i2c_get_clientdata(to_i2c_client(dev));
245 return sprintf(buf, "%u\n", data->operating_mode);
248 static ssize_t tsl2550_store_operating_mode(struct device *dev,
249 struct device_attribute *attr, const char *buf, size_t count)
251 struct i2c_client *client = to_i2c_client(dev);
252 struct tsl2550_data *data = i2c_get_clientdata(client);
253 unsigned long val = simple_strtoul(buf, NULL, 10);
256 if (val < 0 || val > 1)
259 if (data->power_state == 0)
262 mutex_lock(&data->update_lock);
263 ret = tsl2550_set_operating_mode(client, val);
264 mutex_unlock(&data->update_lock);
272 static DEVICE_ATTR(operating_mode, S_IWUSR | S_IRUGO,
273 tsl2550_show_operating_mode, tsl2550_store_operating_mode);
275 static ssize_t __tsl2550_show_lux(struct i2c_client *client, char *buf)
280 ret = tsl2550_get_adc_value(client, TSL2550_READ_ADC0);
287 ret = tsl2550_get_adc_value(client, TSL2550_READ_ADC1);
293 ret = tsl2550_calculate_lux(ch0, ch1);
297 return sprintf(buf, "%d\n", ret);
300 static ssize_t tsl2550_show_lux1_input(struct device *dev,
301 struct device_attribute *attr, char *buf)
303 struct i2c_client *client = to_i2c_client(dev);
304 struct tsl2550_data *data = i2c_get_clientdata(client);
307 /* No LUX data if not operational */
308 if (!data->power_state)
311 mutex_lock(&data->update_lock);
312 ret = __tsl2550_show_lux(client, buf);
313 mutex_unlock(&data->update_lock);
318 static DEVICE_ATTR(lux1_input, S_IRUGO,
319 tsl2550_show_lux1_input, NULL);
321 static struct attribute *tsl2550_attributes[] = {
322 &dev_attr_power_state.attr,
323 &dev_attr_operating_mode.attr,
324 &dev_attr_lux1_input.attr,
328 static const struct attribute_group tsl2550_attr_group = {
329 .attrs = tsl2550_attributes,
333 * Initialization function
336 static int tsl2550_init_client(struct i2c_client *client)
338 struct tsl2550_data *data = i2c_get_clientdata(client);
342 * Probe the chip. To do so we try to power up the device and then to
343 * read back the 0x03 code
345 err = i2c_smbus_write_byte(client, TSL2550_POWER_UP);
349 if (i2c_smbus_read_byte(client) != TSL2550_POWER_UP)
351 data->power_state = 1;
353 /* Set the default operating mode */
354 err = i2c_smbus_write_byte(client,
355 TSL2550_MODE_RANGE[data->operating_mode]);
363 * I2C init/probing/exit functions
366 static struct i2c_driver tsl2550_driver;
367 static int __devinit tsl2550_probe(struct i2c_client *client,
368 const struct i2c_device_id *id)
370 struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
371 struct tsl2550_data *data;
372 int *opmode, err = 0;
374 if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) {
379 data = kzalloc(sizeof(struct tsl2550_data), GFP_KERNEL);
384 data->client = client;
385 i2c_set_clientdata(client, data);
387 /* Check platform data */
388 opmode = client->dev.platform_data;
390 if (*opmode < 0 || *opmode > 1) {
391 dev_err(&client->dev, "invalid operating_mode (%d)\n",
396 data->operating_mode = *opmode;
398 data->operating_mode = 0; /* default mode is standard */
399 dev_info(&client->dev, "%s operating mode\n",
400 data->operating_mode ? "extended" : "standard");
402 mutex_init(&data->update_lock);
404 /* Initialize the TSL2550 chip */
405 err = tsl2550_init_client(client);
409 /* Register sysfs hooks */
410 err = sysfs_create_group(&client->dev.kobj, &tsl2550_attr_group);
414 dev_info(&client->dev, "support ver. %s enabled\n", DRIVER_VERSION);
424 static int __devexit tsl2550_remove(struct i2c_client *client)
426 sysfs_remove_group(&client->dev.kobj, &tsl2550_attr_group);
428 /* Power down the device */
429 tsl2550_set_power_state(client, 0);
431 kfree(i2c_get_clientdata(client));
438 static int tsl2550_suspend(struct i2c_client *client, pm_message_t mesg)
440 return tsl2550_set_power_state(client, 0);
443 static int tsl2550_resume(struct i2c_client *client)
445 return tsl2550_set_power_state(client, 1);
450 #define tsl2550_suspend NULL
451 #define tsl2550_resume NULL
453 #endif /* CONFIG_PM */
455 static const struct i2c_device_id tsl2550_id[] = {
459 MODULE_DEVICE_TABLE(i2c, tsl2550_id);
461 static struct i2c_driver tsl2550_driver = {
463 .name = TSL2550_DRV_NAME,
464 .owner = THIS_MODULE,
466 .suspend = tsl2550_suspend,
467 .resume = tsl2550_resume,
468 .probe = tsl2550_probe,
469 .remove = __devexit_p(tsl2550_remove),
470 .id_table = tsl2550_id,
473 static int __init tsl2550_init(void)
475 return i2c_add_driver(&tsl2550_driver);
478 static void __exit tsl2550_exit(void)
480 i2c_del_driver(&tsl2550_driver);
483 MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
484 MODULE_DESCRIPTION("TSL2550 ambient light sensor driver");
485 MODULE_LICENSE("GPL");
486 MODULE_VERSION(DRIVER_VERSION);
488 module_init(tsl2550_init);
489 module_exit(tsl2550_exit);