diff options
Diffstat (limited to 'drivers/misc/tsl2550.c')
| -rw-r--r-- | drivers/misc/tsl2550.c | 473 | 
1 files changed, 473 insertions, 0 deletions
diff --git a/drivers/misc/tsl2550.c b/drivers/misc/tsl2550.c new file mode 100644 index 00000000000..483ae5f7f68 --- /dev/null +++ b/drivers/misc/tsl2550.c @@ -0,0 +1,473 @@ +/* + *  tsl2550.c - Linux kernel modules for ambient light sensor + * + *  Copyright (C) 2007 Rodolfo Giometti <giometti@linux.it> + *  Copyright (C) 2007 Eurotech S.p.A. <info@eurotech.it> + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; either version 2 of the License, or + *  (at your option) any later version. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program; if not, write to the Free Software + *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/mutex.h> + +#define TSL2550_DRV_NAME	"tsl2550" +#define DRIVER_VERSION		"1.2" + +/* + * Defines + */ + +#define TSL2550_POWER_DOWN		0x00 +#define TSL2550_POWER_UP		0x03 +#define TSL2550_STANDARD_RANGE		0x18 +#define TSL2550_EXTENDED_RANGE		0x1d +#define TSL2550_READ_ADC0		0x43 +#define TSL2550_READ_ADC1		0x83 + +/* + * Structs + */ + +struct tsl2550_data { +	struct i2c_client *client; +	struct mutex update_lock; + +	unsigned int power_state:1; +	unsigned int operating_mode:1; +}; + +/* + * Global data + */ + +static const u8 TSL2550_MODE_RANGE[2] = { +	TSL2550_STANDARD_RANGE, TSL2550_EXTENDED_RANGE, +}; + +/* + * Management functions + */ + +static int tsl2550_set_operating_mode(struct i2c_client *client, int mode) +{ +	struct tsl2550_data *data = i2c_get_clientdata(client); + +	int ret = i2c_smbus_write_byte(client, TSL2550_MODE_RANGE[mode]); + +	data->operating_mode = mode; + +	return ret; +} + +static int tsl2550_set_power_state(struct i2c_client *client, int state) +{ +	struct tsl2550_data *data = i2c_get_clientdata(client); +	int ret; + +	if (state == 0) +		ret = i2c_smbus_write_byte(client, TSL2550_POWER_DOWN); +	else { +		ret = i2c_smbus_write_byte(client, TSL2550_POWER_UP); + +		/* On power up we should reset operating mode also... */ +		tsl2550_set_operating_mode(client, data->operating_mode); +	} + +	data->power_state = state; + +	return ret; +} + +static int tsl2550_get_adc_value(struct i2c_client *client, u8 cmd) +{ +	int ret; + +	ret = i2c_smbus_read_byte_data(client, cmd); +	if (ret < 0) +		return ret; +	if (!(ret & 0x80)) +		return -EAGAIN; +	return ret & 0x7f;	/* remove the "valid" bit */ +} + +/* + * LUX calculation + */ + +#define	TSL2550_MAX_LUX		1846 + +static const u8 ratio_lut[] = { +	100, 100, 100, 100, 100, 100, 100, 100, +	100, 100, 100, 100, 100, 100, 99, 99, +	99, 99, 99, 99, 99, 99, 99, 99, +	99, 99, 99, 98, 98, 98, 98, 98, +	98, 98, 97, 97, 97, 97, 97, 96, +	96, 96, 96, 95, 95, 95, 94, 94, +	93, 93, 93, 92, 92, 91, 91, 90, +	89, 89, 88, 87, 87, 86, 85, 84, +	83, 82, 81, 80, 79, 78, 77, 75, +	74, 73, 71, 69, 68, 66, 64, 62, +	60, 58, 56, 54, 52, 49, 47, 44, +	42, 41, 40, 40, 39, 39, 38, 38, +	37, 37, 37, 36, 36, 36, 35, 35, +	35, 35, 34, 34, 34, 34, 33, 33, +	33, 33, 32, 32, 32, 32, 32, 31, +	31, 31, 31, 31, 30, 30, 30, 30, +	30, +}; + +static const u16 count_lut[] = { +	0, 1, 2, 3, 4, 5, 6, 7, +	8, 9, 10, 11, 12, 13, 14, 15, +	16, 18, 20, 22, 24, 26, 28, 30, +	32, 34, 36, 38, 40, 42, 44, 46, +	49, 53, 57, 61, 65, 69, 73, 77, +	81, 85, 89, 93, 97, 101, 105, 109, +	115, 123, 131, 139, 147, 155, 163, 171, +	179, 187, 195, 203, 211, 219, 227, 235, +	247, 263, 279, 295, 311, 327, 343, 359, +	375, 391, 407, 423, 439, 455, 471, 487, +	511, 543, 575, 607, 639, 671, 703, 735, +	767, 799, 831, 863, 895, 927, 959, 991, +	1039, 1103, 1167, 1231, 1295, 1359, 1423, 1487, +	1551, 1615, 1679, 1743, 1807, 1871, 1935, 1999, +	2095, 2223, 2351, 2479, 2607, 2735, 2863, 2991, +	3119, 3247, 3375, 3503, 3631, 3759, 3887, 4015, +}; + +/* + * This function is described into Taos TSL2550 Designer's Notebook + * pages 2, 3. + */ +static int tsl2550_calculate_lux(u8 ch0, u8 ch1) +{ +	unsigned int lux; + +	/* Look up count from channel values */ +	u16 c0 = count_lut[ch0]; +	u16 c1 = count_lut[ch1]; + +	/* +	 * Calculate ratio. +	 * Note: the "128" is a scaling factor +	 */ +	u8 r = 128; + +	/* Avoid division by 0 and count 1 cannot be greater than count 0 */ +	if (c1 <= c0) +		if (c0) { +			r = c1 * 128 / c0; + +			/* Calculate LUX */ +			lux = ((c0 - c1) * ratio_lut[r]) / 256; +		} else +			lux = 0; +	else +		return -EAGAIN; + +	/* LUX range check */ +	return lux > TSL2550_MAX_LUX ? TSL2550_MAX_LUX : lux; +} + +/* + * SysFS support + */ + +static ssize_t tsl2550_show_power_state(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct tsl2550_data *data = i2c_get_clientdata(to_i2c_client(dev)); + +	return sprintf(buf, "%u\n", data->power_state); +} + +static ssize_t tsl2550_store_power_state(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t count) +{ +	struct i2c_client *client = to_i2c_client(dev); +	struct tsl2550_data *data = i2c_get_clientdata(client); +	unsigned long val = simple_strtoul(buf, NULL, 10); +	int ret; + +	if (val < 0 || val > 1) +		return -EINVAL; + +	mutex_lock(&data->update_lock); +	ret = tsl2550_set_power_state(client, val); +	mutex_unlock(&data->update_lock); + +	if (ret < 0) +		return ret; + +	return count; +} + +static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO, +		   tsl2550_show_power_state, tsl2550_store_power_state); + +static ssize_t tsl2550_show_operating_mode(struct device *dev, +		struct device_attribute *attr, char *buf) +{ +	struct tsl2550_data *data = i2c_get_clientdata(to_i2c_client(dev)); + +	return sprintf(buf, "%u\n", data->operating_mode); +} + +static ssize_t tsl2550_store_operating_mode(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t count) +{ +	struct i2c_client *client = to_i2c_client(dev); +	struct tsl2550_data *data = i2c_get_clientdata(client); +	unsigned long val = simple_strtoul(buf, NULL, 10); +	int ret; + +	if (val < 0 || val > 1) +		return -EINVAL; + +	if (data->power_state == 0) +		return -EBUSY; + +	mutex_lock(&data->update_lock); +	ret = tsl2550_set_operating_mode(client, val); +	mutex_unlock(&data->update_lock); + +	if (ret < 0) +		return ret; + +	return count; +} + +static DEVICE_ATTR(operating_mode, S_IWUSR | S_IRUGO, +		   tsl2550_show_operating_mode, tsl2550_store_operating_mode); + +static ssize_t __tsl2550_show_lux(struct i2c_client *client, char *buf) +{ +	struct tsl2550_data *data = i2c_get_clientdata(client); +	u8 ch0, ch1; +	int ret; + +	ret = tsl2550_get_adc_value(client, TSL2550_READ_ADC0); +	if (ret < 0) +		return ret; +	ch0 = ret; + +	ret = tsl2550_get_adc_value(client, TSL2550_READ_ADC1); +	if (ret < 0) +		return ret; +	ch1 = ret; + +	/* Do the job */ +	ret = tsl2550_calculate_lux(ch0, ch1); +	if (ret < 0) +		return ret; +	if (data->operating_mode == 1) +		ret *= 5; + +	return sprintf(buf, "%d\n", ret); +} + +static ssize_t tsl2550_show_lux1_input(struct device *dev, +			struct device_attribute *attr, char *buf) +{ +	struct i2c_client *client = to_i2c_client(dev); +	struct tsl2550_data *data = i2c_get_clientdata(client); +	int ret; + +	/* No LUX data if not operational */ +	if (!data->power_state) +		return -EBUSY; + +	mutex_lock(&data->update_lock); +	ret = __tsl2550_show_lux(client, buf); +	mutex_unlock(&data->update_lock); + +	return ret; +} + +static DEVICE_ATTR(lux1_input, S_IRUGO, +		   tsl2550_show_lux1_input, NULL); + +static struct attribute *tsl2550_attributes[] = { +	&dev_attr_power_state.attr, +	&dev_attr_operating_mode.attr, +	&dev_attr_lux1_input.attr, +	NULL +}; + +static const struct attribute_group tsl2550_attr_group = { +	.attrs = tsl2550_attributes, +}; + +/* + * Initialization function + */ + +static int tsl2550_init_client(struct i2c_client *client) +{ +	struct tsl2550_data *data = i2c_get_clientdata(client); +	int err; + +	/* +	 * Probe the chip. To do so we try to power up the device and then to +	 * read back the 0x03 code +	 */ +	err = i2c_smbus_read_byte_data(client, TSL2550_POWER_UP); +	if (err < 0) +		return err; +	if (err != TSL2550_POWER_UP) +		return -ENODEV; +	data->power_state = 1; + +	/* Set the default operating mode */ +	err = i2c_smbus_write_byte(client, +				   TSL2550_MODE_RANGE[data->operating_mode]); +	if (err < 0) +		return err; + +	return 0; +} + +/* + * I2C init/probing/exit functions + */ + +static struct i2c_driver tsl2550_driver; +static int __devinit tsl2550_probe(struct i2c_client *client, +				   const struct i2c_device_id *id) +{ +	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); +	struct tsl2550_data *data; +	int *opmode, err = 0; + +	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE +					    | I2C_FUNC_SMBUS_READ_BYTE_DATA)) { +		err = -EIO; +		goto exit; +	} + +	data = kzalloc(sizeof(struct tsl2550_data), GFP_KERNEL); +	if (!data) { +		err = -ENOMEM; +		goto exit; +	} +	data->client = client; +	i2c_set_clientdata(client, data); + +	/* Check platform data */ +	opmode = client->dev.platform_data; +	if (opmode) { +		if (*opmode < 0 || *opmode > 1) { +			dev_err(&client->dev, "invalid operating_mode (%d)\n", +					*opmode); +			err = -EINVAL; +			goto exit_kfree; +		} +		data->operating_mode = *opmode; +	} else +		data->operating_mode = 0;	/* default mode is standard */ +	dev_info(&client->dev, "%s operating mode\n", +			data->operating_mode ? "extended" : "standard"); + +	mutex_init(&data->update_lock); + +	/* Initialize the TSL2550 chip */ +	err = tsl2550_init_client(client); +	if (err) +		goto exit_kfree; + +	/* Register sysfs hooks */ +	err = sysfs_create_group(&client->dev.kobj, &tsl2550_attr_group); +	if (err) +		goto exit_kfree; + +	dev_info(&client->dev, "support ver. %s enabled\n", DRIVER_VERSION); + +	return 0; + +exit_kfree: +	kfree(data); +exit: +	return err; +} + +static int __devexit tsl2550_remove(struct i2c_client *client) +{ +	sysfs_remove_group(&client->dev.kobj, &tsl2550_attr_group); + +	/* Power down the device */ +	tsl2550_set_power_state(client, 0); + +	kfree(i2c_get_clientdata(client)); + +	return 0; +} + +#ifdef CONFIG_PM + +static int tsl2550_suspend(struct i2c_client *client, pm_message_t mesg) +{ +	return tsl2550_set_power_state(client, 0); +} + +static int tsl2550_resume(struct i2c_client *client) +{ +	return tsl2550_set_power_state(client, 1); +} + +#else + +#define tsl2550_suspend		NULL +#define tsl2550_resume		NULL + +#endif /* CONFIG_PM */ + +static const struct i2c_device_id tsl2550_id[] = { +	{ "tsl2550", 0 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, tsl2550_id); + +static struct i2c_driver tsl2550_driver = { +	.driver = { +		.name	= TSL2550_DRV_NAME, +		.owner	= THIS_MODULE, +	}, +	.suspend = tsl2550_suspend, +	.resume	= tsl2550_resume, +	.probe	= tsl2550_probe, +	.remove	= __devexit_p(tsl2550_remove), +	.id_table = tsl2550_id, +}; + +static int __init tsl2550_init(void) +{ +	return i2c_add_driver(&tsl2550_driver); +} + +static void __exit tsl2550_exit(void) +{ +	i2c_del_driver(&tsl2550_driver); +} + +MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); +MODULE_DESCRIPTION("TSL2550 ambient light sensor driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); + +module_init(tsl2550_init); +module_exit(tsl2550_exit);  |