All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-02-25 20:10 Daniel Mack
       [not found] ` <1235592618-10880-1-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-02-25 20:10 UTC (permalink / raw)
  To: linux-i2c-u79uwXL29TY76Z2rM5mHXA; +Cc: Daniel Mack

This patch adds a driver for Intersil's ISL29003 ambient light sensor
device plus some documentation. Inspired by tsl2550.c, a driver for a
similar device.

Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
---
 Documentation/i2c/chips/isl29003 |   62 +++++
 drivers/i2c/chips/Kconfig        |   10 +
 drivers/i2c/chips/Makefile       |    1 +
 drivers/i2c/chips/isl29003.c     |  517 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 590 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/i2c/chips/isl29003
 create mode 100644 drivers/i2c/chips/isl29003.c

diff --git a/Documentation/i2c/chips/isl29003 b/Documentation/i2c/chips/isl29003
new file mode 100644
index 0000000..f808141
--- /dev/null
+++ b/Documentation/i2c/chips/isl29003
@@ -0,0 +1,62 @@
+Kernel driver isl29003
+=====================
+
+Supported chips:
+* Intersil ISL29003
+Prefix: 'isl29003'
+Addresses scanned: none
+Datasheet:
+http://www.intersil.com&data/fn/fn7464.pdf
+
+Author: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
+
+
+Description
+-----------
+The ISL29003 is an integrated light sensors with a 16-bit integrating type
+ADC, I2C user programmable lux range select for optimized counts/lux, and
+I2C multi-function control and monitoring capabilities. The internal ADC
+provides 16-bit resolution while rejecting 50Hz and 60Hz flicker caused by
+artificial light sources.
+
+The driver allows to set the lux range, the bit resolution, the operational
+mode (see below) and the power state of device and can read the current lux
+value, of course.
+
+
+Detection
+---------
+
+The ISL29003 does not have an ID register which could be used to identify
+it, so the detection routing will just try to read from the configured I2C
+addess and consider the device to be present as soon as it ACKs the
+transfer.
+
+
+Sysfs entries
+-------------
+
+range:
+	0: 0 lux to 1000 lux (default)
+	1: 0 lux to 4000 lux
+	2: 0 lux to 16,000 lux
+	3: 0 lux to 64,000 lux
+
+resolution:
+	0: 2^16 cycles (default)
+	1: 2^12 cycles
+	2: 2^8 cycles
+	3: 2^4 cycles
+
+mode:
+	0: diode1's current (unsigned 16bit) (default)
+	1: diode1's current (unsigned 16bit)
+	2: difference between diodes (l1 - l2, signed 15bit)
+
+power_state:
+	0: device is disabled (default)
+	1: device is enabled
+
+value (read only):
+	returns the value from the last sensor reading
+
diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index c80312c..d10f515 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -102,4 +102,14 @@ config SENSORS_TSL2550
 	  This driver can also be built as a module.  If so, the module
 	  will be called tsl2550.
 
+config SENSORS_ISL29003
+	tristate "Intersil ISL29003 ambient light sensor"
+	depends on EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Intersil ISL29003
+	  ambient light sensor.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called isl29003.
+
 endmenu
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
index d142f23..c94c8a6 100644
--- a/drivers/i2c/chips/Makefile
+++ b/drivers/i2c/chips/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_PCF8574)	+= pcf8574.o
 obj-$(CONFIG_PCF8575)		+= pcf8575.o
 obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
 obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
+obj-$(CONFIG_SENSORS_ISL29003)	+= isl29003.o
 
 ifeq ($(CONFIG_I2C_DEBUG_CHIP),y)
 EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/i2c/chips/isl29003.c b/drivers/i2c/chips/isl29003.c
new file mode 100644
index 0000000..757a74f
--- /dev/null
+++ b/drivers/i2c/chips/isl29003.c
@@ -0,0 +1,517 @@
+/*
+ *  isl29003.c - Linux kernel modules for
+ * 	Intersil ISL29003 ambient light sensor
+ *
+ *  See file://Documentation/i2c/chips/isl29003
+ *
+ *  Copyright (c) 2009 Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
+ *
+ *  Based on code written by
+ *  	Rodolfo Giometti <giometti-k2GhghHVRtY@public.gmane.org>
+ *  	Eurotech S.p.A. <info-ymFC7zkAqBI1GQ1Ptb7lUw@public.gmane.org>
+ *
+ *  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>
+#include <linux/delay.h>
+
+#define ISL29003_DRV_NAME	"isl29003"
+#define DRIVER_VERSION		"1.0"
+
+#define ISL29003_REG_COMMAND		0x00
+#define ISL29003_ADC_ENABLED		(1 << 7)
+#define ISL29003_ADC_PD			(1 << 6)
+#define ISL29003_TIMING_INT		(1 << 5)
+#define ISL29003_MODE_SHIFT		(2)
+#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
+#define ISL29003_RES_SHIFT		(0)
+#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)
+
+#define ISL29003_REG_CONTROL		0x01
+#define ISL29003_INT_FLG		(1 << 5)
+#define ISL29003_RANGE_SHIFT		(2)
+#define ISL29003_RANGE_MASK		(0x3 << ISL29003_RANGE_SHIFT)
+#define ISL29003_INT_PERSISTS_SHIFT	(0)
+#define ISL29003_INT_PERSISTS_MASK	(0xf << ISL29003_INT_PERSISTS_SHIFT)
+
+#define ISL29003_REG_IRQ_THRESH_HI	0x02
+#define ISL29003_REG_IRQ_THRESH_LO	0x03
+#define ISL29003_REG_LSB_SENSOR		0x04
+#define ISL29003_REG_MSB_SENSOR		0x05
+#define ISL29003_REG_LSB_TIMER		0x06
+#define ISL29003_REG_MSB_TIMER		0x07
+
+struct isl29003_data {
+	struct i2c_client *client;
+	struct mutex update_lock;
+	u8 reg_cache[8];
+};
+
+static int gain_range[4] = {
+	1000, 4000, 16000, 64000
+};
+
+/*
+ * register access helpers
+ */
+
+static int __isl29003_read_reg(struct i2c_client *client,
+			       u32 reg, u8 mask, u8 shift)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	return (data->reg_cache[reg] & mask) >> shift;
+}
+
+static int __isl29003_write_reg(struct i2c_client *client,
+				u32 reg, u8 mask, u8 shift, u8 val)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	u8 tmp = data->reg_cache[reg];
+	int ret;
+
+	tmp &= ~mask;
+	tmp |= val << shift;
+
+	ret = i2c_smbus_write_byte_data(client, reg, tmp);
+	if (ret < 0)
+		return ret;
+
+	data->reg_cache[reg] = tmp;
+	return 0;
+}
+
+/*
+ * internally used functions
+ */
+
+/* range */
+static int isl29003_get_range(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT);
+}
+
+static int isl29003_set_range(struct i2c_client *client, int range)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT, range);
+}
+
+/* resolution */
+static int isl29003_get_resolution(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_resolution(struct i2c_client *client, int range)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, range);
+}
+
+/* mode */
+static int isl29003_get_mode(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_mode(struct i2c_client *client, int mode)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, mode);
+}
+
+/* power_state */
+static int isl29003_set_power_state(struct i2c_client *client, int state)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int ret;
+	u8 v = data->reg_cache[ISL29003_REG_COMMAND];
+
+	if (state == 0) {
+		v &= ~ISL29003_ADC_ENABLED;
+		v |= ISL29003_ADC_PD;
+	} else {
+		v |= ISL29003_ADC_ENABLED;
+		v &= ~ISL29003_ADC_PD;
+	}
+
+	ret = i2c_smbus_write_byte_data(client, ISL29003_REG_COMMAND, v);
+	if (ret < 0)
+		return ret;
+
+	data->reg_cache[ISL29003_REG_COMMAND] = v;
+	return 0;
+}
+
+static int isl29003_get_power_state(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
+	return (cmdreg & ISL29003_ADC_ENABLED) && (~cmdreg & ISL29003_ADC_PD);
+}
+
+static unsigned int isl29003_get_adc_value(struct i2c_client *client)
+{
+	int lsb, msb, range, bitdepth;
+
+	lsb = i2c_smbus_read_byte_data(client, ISL29003_REG_LSB_SENSOR);
+	if (lsb < 0)
+		return lsb;
+
+	msb = i2c_smbus_read_byte_data(client, ISL29003_REG_MSB_SENSOR);
+	if (msb < 0)
+		return msb;
+
+	range = isl29003_get_range(client);
+	bitdepth = (4 - isl29003_get_resolution(client)) * 4;
+	return (((msb << 8) | lsb) * gain_range[range]) >> bitdepth;
+}
+
+/*
+ * sysfs layer
+ */
+
+/* range */
+static ssize_t isl29003_show_range(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_range(client));
+}
+
+static ssize_t isl29003_store_range(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int ret;
+
+	if (strict_strtoul(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (val < 0 || val > 3)
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	mutex_lock(&data->update_lock);
+	ret = isl29003_set_range(client, val);
+	mutex_unlock(&data->update_lock);
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
+		   isl29003_show_range, isl29003_store_range);
+
+
+/* resolution */
+static ssize_t isl29003_show_resolution(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_resolution(client));
+}
+
+static ssize_t isl29003_store_resolution(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int ret;
+
+	if (strict_strtoul(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (val < 0 || val > 3)
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	mutex_lock(&data->update_lock);
+	ret = isl29003_set_resolution(client, val);
+	mutex_unlock(&data->update_lock);
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(resolution, S_IWUSR | S_IRUGO,
+		   isl29003_show_resolution, isl29003_store_resolution);
+
+/* mode */
+static ssize_t isl29003_show_mode(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_mode(client));
+}
+
+static ssize_t isl29003_store_mode(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int ret;
+
+	if (strict_strtoul(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (val < 0 || val > 2)
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	mutex_lock(&data->update_lock);
+	ret = isl29003_set_mode(client, val);
+	mutex_unlock(&data->update_lock);
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
+		   isl29003_show_mode, isl29003_store_mode);
+
+
+/* power state */
+static ssize_t isl29003_show_power_state(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%u\n", isl29003_get_power_state(client));
+}
+
+static ssize_t isl29003_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 isl29003_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int ret;
+
+	if (strict_strtoul(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (val < 0 || val > 1)
+		return -EINVAL;
+
+	mutex_lock(&data->update_lock);
+	ret = isl29003_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,
+		   isl29003_show_power_state, isl29003_store_power_state);
+
+
+static ssize_t isl29003_show_lux(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int ret;
+
+	/* No LUX data if not operational */
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	mutex_lock(&data->update_lock);
+	ret = sprintf(buf, "%d\n", isl29003_get_adc_value(client));
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+static DEVICE_ATTR(lux, S_IRUGO, isl29003_show_lux, NULL);
+
+static struct attribute *isl29003_attributes[] = {
+	&dev_attr_range.attr,
+	&dev_attr_resolution.attr,
+	&dev_attr_mode.attr,
+	&dev_attr_power_state.attr,
+	&dev_attr_lux.attr,
+	NULL
+};
+
+static const struct attribute_group isl29003_attr_group = {
+	.attrs = isl29003_attributes,
+};
+
+static int isl29003_init_client(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int i;
+
+	/* read all the registers once to fill the cache.
+	 * if a read fails, we consider the init failed */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++) {
+		int v = i2c_smbus_read_byte_data(client, i);
+		if (v < 0)
+			return -ENODEV;
+		data->reg_cache[i] = v;
+	}
+
+	/* set defaults */
+	isl29003_set_range(client, 0);
+	isl29003_set_resolution(client, 0);
+	isl29003_set_mode(client, 0);
+	isl29003_set_power_state(client, 0);
+
+	return 0;
+}
+
+/*
+ * I2C layer
+ */
+
+static int __devinit isl29003_probe(struct i2c_client *client,
+				   const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct isl29003_data *data;
+	int err = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) {
+		err = -EIO;
+		goto exit;
+	}
+
+	data = kzalloc(sizeof(struct isl29003_data), GFP_KERNEL);
+	if (!data) {
+		err = -ENOMEM;
+		goto exit;
+	}
+	data->client = client;
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->update_lock);
+
+	/* initialize the ISL29003 chip */
+	err = isl29003_init_client(client);
+	if (err)
+		goto exit_kfree;
+
+	/* register sysfs hooks */
+	err = sysfs_create_group(&client->dev.kobj, &isl29003_attr_group);
+	if (err)
+		goto exit_kfree;
+
+	dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);
+	return 0;
+
+exit_kfree:
+	kfree(data);
+exit:
+	return err;
+}
+
+static int __devexit isl29003_remove(struct i2c_client *client)
+{
+	sysfs_remove_group(&client->dev.kobj, &isl29003_attr_group);
+	isl29003_set_power_state(client, 0);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int isl29003_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	return isl29003_set_power_state(client, 0);
+}
+
+static int isl29003_resume(struct i2c_client *client)
+{
+	int i;
+
+	/* restore registers from cache */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
+		if (i2c_smbus_read_byte_data(client, i, data->reg_cache[i]) < 0)
+			return -ENODEV;
+
+	return 0;
+}
+
+#else
+#define isl29003_suspend	NULL
+#define isl29003_resume		NULL
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id isl29003_id[] = {
+	{ "isl29003", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, isl29003_id);
+
+static struct i2c_driver isl29003_driver = {
+	.driver = {
+		.name	= ISL29003_DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+	.suspend = isl29003_suspend,
+	.resume	= isl29003_resume,
+	.probe	= isl29003_probe,
+	.remove	= __devexit_p(isl29003_remove),
+	.id_table = isl29003_id,
+};
+
+static int __init isl29003_init(void)
+{
+	return i2c_add_driver(&isl29003_driver);
+}
+
+static void __exit isl29003_exit(void)
+{
+	i2c_del_driver(&isl29003_driver);
+}
+
+MODULE_AUTHOR("Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>");
+MODULE_DESCRIPTION("ISL29003 ambient light sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(isl29003_init);
+module_exit(isl29003_exit);
+
-- 
1.6.1.3

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH] Added driver for ISL29003 ambient light sensor
       [not found] ` <1235592618-10880-1-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
@ 2009-02-25 20:19   ` Daniel Mack
  2009-02-25 20:21   ` Jean Delvare
  1 sibling, 0 replies; 49+ messages in thread
From: Daniel Mack @ 2009-02-25 20:19 UTC (permalink / raw)
  To: linux-i2c-u79uwXL29TY76Z2rM5mHXA; +Cc: Daniel Mack

This patch adds a driver for Intersil's ISL29003 ambient light sensor
device plus some documentation. Inspired by tsl2550.c, a driver for a
similar device.

Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
---
 Fixed some typos in the documentation.

 Documentation/i2c/chips/isl29003 |   62 +++++
 drivers/i2c/chips/Kconfig        |   10 +
 drivers/i2c/chips/Makefile       |    1 +
 drivers/i2c/chips/isl29003.c     |  517 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 590 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/i2c/chips/isl29003
 create mode 100644 drivers/i2c/chips/isl29003.c

diff --git a/Documentation/i2c/chips/isl29003 b/Documentation/i2c/chips/isl29003
new file mode 100644
index 0000000..f808141
--- /dev/null
+++ b/Documentation/i2c/chips/isl29003
@@ -0,0 +1,62 @@
+Kernel driver isl29003
+=====================
+
+Supported chips:
+* Intersil ISL29003
+Prefix: 'isl29003'
+Addresses scanned: none
+Datasheet:
+http://www.intersil.com/data/fn/fn7464.pdf
+
+Author: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
+
+
+Description
+-----------
+The ISL29003 is an integrated light sensor with a 16-bit integrating type
+ADC, I2C user programmable lux range select for optimized counts/lux, and
+I2C multi-function control and monitoring capabilities. The internal ADC
+provides 16-bit resolution while rejecting 50Hz and 60Hz flicker caused by
+artificial light sources.
+
+The driver allows to set the lux range, the bit resolution, the operational
+mode (see below) and the power state of device and can read the current lux
+value, of course.
+
+
+Detection
+---------
+
+The ISL29003 does not have an ID register which could be used to identify
+it, so the detection routing will just try to read from the configured I2C
+addess and consider the device to be present as soon as it ACKs the
+transfer.
+
+
+Sysfs entries
+-------------
+
+range:
+	0: 0 lux to 1000 lux (default)
+	1: 0 lux to 4000 lux
+	2: 0 lux to 16,000 lux
+	3: 0 lux to 64,000 lux
+
+resolution:
+	0: 2^16 cycles (default)
+	1: 2^12 cycles
+	2: 2^8 cycles
+	3: 2^4 cycles
+
+mode:
+	0: diode1's current (unsigned 16bit) (default)
+	1: diode1's current (unsigned 16bit)
+	2: difference between diodes (l1 - l2, signed 15bit)
+
+power_state:
+	0: device is disabled (default)
+	1: device is enabled
+
+lux (read only):
+	returns the value from the last sensor reading in lux
+
diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index c80312c..d10f515 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -102,4 +102,14 @@ config SENSORS_TSL2550
 	  This driver can also be built as a module.  If so, the module
 	  will be called tsl2550.
 
+config SENSORS_ISL29003
+	tristate "Intersil ISL29003 ambient light sensor"
+	depends on EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Intersil ISL29003
+	  ambient light sensor.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called isl29003.
+
 endmenu
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
index d142f23..c94c8a6 100644
--- a/drivers/i2c/chips/Makefile
+++ b/drivers/i2c/chips/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_PCF8574)	+= pcf8574.o
 obj-$(CONFIG_PCF8575)		+= pcf8575.o
 obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
 obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
+obj-$(CONFIG_SENSORS_ISL29003)	+= isl29003.o
 
 ifeq ($(CONFIG_I2C_DEBUG_CHIP),y)
 EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/i2c/chips/isl29003.c b/drivers/i2c/chips/isl29003.c
new file mode 100644
index 0000000..757a74f
--- /dev/null
+++ b/drivers/i2c/chips/isl29003.c
@@ -0,0 +1,517 @@
+/*
+ *  isl29003.c - Linux kernel module for
+ * 	Intersil ISL29003 ambient light sensor
+ *
+ *  See file://Documentation/i2c/chips/isl29003
+ *
+ *  Copyright (c) 2009 Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
+ *
+ *  Based on code written by
+ *  	Rodolfo Giometti <giometti-k2GhghHVRtY@public.gmane.org>
+ *  	Eurotech S.p.A. <info-ymFC7zkAqBI1GQ1Ptb7lUw@public.gmane.org>
+ *
+ *  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>
+#include <linux/delay.h>
+
+#define ISL29003_DRV_NAME	"isl29003"
+#define DRIVER_VERSION		"1.0"
+
+#define ISL29003_REG_COMMAND		0x00
+#define ISL29003_ADC_ENABLED		(1 << 7)
+#define ISL29003_ADC_PD			(1 << 6)
+#define ISL29003_TIMING_INT		(1 << 5)
+#define ISL29003_MODE_SHIFT		(2)
+#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
+#define ISL29003_RES_SHIFT		(0)
+#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)
+
+#define ISL29003_REG_CONTROL		0x01
+#define ISL29003_INT_FLG		(1 << 5)
+#define ISL29003_RANGE_SHIFT		(2)
+#define ISL29003_RANGE_MASK		(0x3 << ISL29003_RANGE_SHIFT)
+#define ISL29003_INT_PERSISTS_SHIFT	(0)
+#define ISL29003_INT_PERSISTS_MASK	(0xf << ISL29003_INT_PERSISTS_SHIFT)
+
+#define ISL29003_REG_IRQ_THRESH_HI	0x02
+#define ISL29003_REG_IRQ_THRESH_LO	0x03
+#define ISL29003_REG_LSB_SENSOR		0x04
+#define ISL29003_REG_MSB_SENSOR		0x05
+#define ISL29003_REG_LSB_TIMER		0x06
+#define ISL29003_REG_MSB_TIMER		0x07
+
+struct isl29003_data {
+	struct i2c_client *client;
+	struct mutex update_lock;
+	u8 reg_cache[8];
+};
+
+static int gain_range[4] = {
+	1000, 4000, 16000, 64000
+};
+
+/*
+ * register access helpers
+ */
+
+static int __isl29003_read_reg(struct i2c_client *client,
+			       u32 reg, u8 mask, u8 shift)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	return (data->reg_cache[reg] & mask) >> shift;
+}
+
+static int __isl29003_write_reg(struct i2c_client *client,
+				u32 reg, u8 mask, u8 shift, u8 val)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	u8 tmp = data->reg_cache[reg];
+	int ret;
+
+	tmp &= ~mask;
+	tmp |= val << shift;
+
+	ret = i2c_smbus_write_byte_data(client, reg, tmp);
+	if (ret < 0)
+		return ret;
+
+	data->reg_cache[reg] = tmp;
+	return 0;
+}
+
+/*
+ * internally used functions
+ */
+
+/* range */
+static int isl29003_get_range(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT);
+}
+
+static int isl29003_set_range(struct i2c_client *client, int range)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT, range);
+}
+
+/* resolution */
+static int isl29003_get_resolution(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_resolution(struct i2c_client *client, int range)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, range);
+}
+
+/* mode */
+static int isl29003_get_mode(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_mode(struct i2c_client *client, int mode)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, mode);
+}
+
+/* power_state */
+static int isl29003_set_power_state(struct i2c_client *client, int state)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int ret;
+	u8 v = data->reg_cache[ISL29003_REG_COMMAND];
+
+	if (state == 0) {
+		v &= ~ISL29003_ADC_ENABLED;
+		v |= ISL29003_ADC_PD;
+	} else {
+		v |= ISL29003_ADC_ENABLED;
+		v &= ~ISL29003_ADC_PD;
+	}
+
+	ret = i2c_smbus_write_byte_data(client, ISL29003_REG_COMMAND, v);
+	if (ret < 0)
+		return ret;
+
+	data->reg_cache[ISL29003_REG_COMMAND] = v;
+	return 0;
+}
+
+static int isl29003_get_power_state(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
+	return (cmdreg & ISL29003_ADC_ENABLED) && (~cmdreg & ISL29003_ADC_PD);
+}
+
+static unsigned int isl29003_get_adc_value(struct i2c_client *client)
+{
+	int lsb, msb, range, bitdepth;
+
+	lsb = i2c_smbus_read_byte_data(client, ISL29003_REG_LSB_SENSOR);
+	if (lsb < 0)
+		return lsb;
+
+	msb = i2c_smbus_read_byte_data(client, ISL29003_REG_MSB_SENSOR);
+	if (msb < 0)
+		return msb;
+
+	range = isl29003_get_range(client);
+	bitdepth = (4 - isl29003_get_resolution(client)) * 4;
+	return (((msb << 8) | lsb) * gain_range[range]) >> bitdepth;
+}
+
+/*
+ * sysfs layer
+ */
+
+/* range */
+static ssize_t isl29003_show_range(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_range(client));
+}
+
+static ssize_t isl29003_store_range(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int ret;
+
+	if (strict_strtoul(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (val < 0 || val > 3)
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	mutex_lock(&data->update_lock);
+	ret = isl29003_set_range(client, val);
+	mutex_unlock(&data->update_lock);
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
+		   isl29003_show_range, isl29003_store_range);
+
+
+/* resolution */
+static ssize_t isl29003_show_resolution(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_resolution(client));
+}
+
+static ssize_t isl29003_store_resolution(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int ret;
+
+	if (strict_strtoul(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (val < 0 || val > 3)
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	mutex_lock(&data->update_lock);
+	ret = isl29003_set_resolution(client, val);
+	mutex_unlock(&data->update_lock);
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(resolution, S_IWUSR | S_IRUGO,
+		   isl29003_show_resolution, isl29003_store_resolution);
+
+/* mode */
+static ssize_t isl29003_show_mode(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_mode(client));
+}
+
+static ssize_t isl29003_store_mode(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int ret;
+
+	if (strict_strtoul(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (val < 0 || val > 2)
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	mutex_lock(&data->update_lock);
+	ret = isl29003_set_mode(client, val);
+	mutex_unlock(&data->update_lock);
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
+		   isl29003_show_mode, isl29003_store_mode);
+
+
+/* power state */
+static ssize_t isl29003_show_power_state(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%u\n", isl29003_get_power_state(client));
+}
+
+static ssize_t isl29003_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 isl29003_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int ret;
+
+	if (strict_strtoul(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (val < 0 || val > 1)
+		return -EINVAL;
+
+	mutex_lock(&data->update_lock);
+	ret = isl29003_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,
+		   isl29003_show_power_state, isl29003_store_power_state);
+
+
+static ssize_t isl29003_show_lux(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int ret;
+
+	/* No LUX data if not operational */
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	mutex_lock(&data->update_lock);
+	ret = sprintf(buf, "%d\n", isl29003_get_adc_value(client));
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+static DEVICE_ATTR(lux, S_IRUGO, isl29003_show_lux, NULL);
+
+static struct attribute *isl29003_attributes[] = {
+	&dev_attr_range.attr,
+	&dev_attr_resolution.attr,
+	&dev_attr_mode.attr,
+	&dev_attr_power_state.attr,
+	&dev_attr_lux.attr,
+	NULL
+};
+
+static const struct attribute_group isl29003_attr_group = {
+	.attrs = isl29003_attributes,
+};
+
+static int isl29003_init_client(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int i;
+
+	/* read all the registers once to fill the cache.
+	 * if a read fails, we consider the init failed */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++) {
+		int v = i2c_smbus_read_byte_data(client, i);
+		if (v < 0)
+			return -ENODEV;
+		data->reg_cache[i] = v;
+	}
+
+	/* set defaults */
+	isl29003_set_range(client, 0);
+	isl29003_set_resolution(client, 0);
+	isl29003_set_mode(client, 0);
+	isl29003_set_power_state(client, 0);
+
+	return 0;
+}
+
+/*
+ * I2C layer
+ */
+
+static int __devinit isl29003_probe(struct i2c_client *client,
+				   const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct isl29003_data *data;
+	int err = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) {
+		err = -EIO;
+		goto exit;
+	}
+
+	data = kzalloc(sizeof(struct isl29003_data), GFP_KERNEL);
+	if (!data) {
+		err = -ENOMEM;
+		goto exit;
+	}
+	data->client = client;
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->update_lock);
+
+	/* initialize the ISL29003 chip */
+	err = isl29003_init_client(client);
+	if (err)
+		goto exit_kfree;
+
+	/* register sysfs hooks */
+	err = sysfs_create_group(&client->dev.kobj, &isl29003_attr_group);
+	if (err)
+		goto exit_kfree;
+
+	dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);
+	return 0;
+
+exit_kfree:
+	kfree(data);
+exit:
+	return err;
+}
+
+static int __devexit isl29003_remove(struct i2c_client *client)
+{
+	sysfs_remove_group(&client->dev.kobj, &isl29003_attr_group);
+	isl29003_set_power_state(client, 0);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int isl29003_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	return isl29003_set_power_state(client, 0);
+}
+
+static int isl29003_resume(struct i2c_client *client)
+{
+	int i;
+
+	/* restore registers from cache */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
+		if (i2c_smbus_read_byte_data(client, i, data->reg_cache[i]) < 0)
+			return -ENODEV;
+
+	return 0;
+}
+
+#else
+#define isl29003_suspend	NULL
+#define isl29003_resume		NULL
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id isl29003_id[] = {
+	{ "isl29003", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, isl29003_id);
+
+static struct i2c_driver isl29003_driver = {
+	.driver = {
+		.name	= ISL29003_DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+	.suspend = isl29003_suspend,
+	.resume	= isl29003_resume,
+	.probe	= isl29003_probe,
+	.remove	= __devexit_p(isl29003_remove),
+	.id_table = isl29003_id,
+};
+
+static int __init isl29003_init(void)
+{
+	return i2c_add_driver(&isl29003_driver);
+}
+
+static void __exit isl29003_exit(void)
+{
+	i2c_del_driver(&isl29003_driver);
+}
+
+MODULE_AUTHOR("Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>");
+MODULE_DESCRIPTION("ISL29003 ambient light sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(isl29003_init);
+module_exit(isl29003_exit);
+
-- 
1.6.1.3

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found] ` <1235592618-10880-1-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
  2009-02-25 20:19   ` Daniel Mack
@ 2009-02-25 20:21   ` Jean Delvare
       [not found]     ` <20090225212119.67402391-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
  1 sibling, 1 reply; 49+ messages in thread
From: Jean Delvare @ 2009-02-25 20:21 UTC (permalink / raw)
  To: Daniel Mack; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA

Hi Daniel,

On Wed, 25 Feb 2009 21:10:18 +0100, Daniel Mack wrote:
> This patch adds a driver for Intersil's ISL29003 ambient light sensor
> device plus some documentation. Inspired by tsl2550.c, a driver for a
> similar device.
> 
> Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> ---
>  Documentation/i2c/chips/isl29003 |   62 +++++
>  drivers/i2c/chips/Kconfig        |   10 +
>  drivers/i2c/chips/Makefile       |    1 +

Please read the header of these 2 files again.

>  drivers/i2c/chips/isl29003.c     |  517 ++++++++++++++++++++++++++++++++++++++
>  4 files changed, 590 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/i2c/chips/isl29003
>  create mode 100644 drivers/i2c/chips/isl29003.c

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]     ` <20090225212119.67402391-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
@ 2009-02-25 23:40       ` Daniel Mack
       [not found]         ` <20090225234004.GA19039-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
  2009-02-25 23:57       ` Daniel Mack
  1 sibling, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-02-25 23:40 UTC (permalink / raw)
  To: Jean Delvare; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA

Hi Jean,

On Wed, Feb 25, 2009 at 09:21:19PM +0100, Jean Delvare wrote:
> On Wed, 25 Feb 2009 21:10:18 +0100, Daniel Mack wrote:
> > This patch adds a driver for Intersil's ISL29003 ambient light sensor
> > device plus some documentation. Inspired by tsl2550.c, a driver for a
> > similar device.
> > 
> > Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> > ---
> >  Documentation/i2c/chips/isl29003 |   62 +++++
> >  drivers/i2c/chips/Kconfig        |   10 +
> >  drivers/i2c/chips/Makefile       |    1 +
> 
> Please read the header of these 2 files again.

Ok, I missed that. But drivers for I2C based devices will still be
submitted via this list?

Daniel

^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]     ` <20090225212119.67402391-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
  2009-02-25 23:40       ` Daniel Mack
@ 2009-02-25 23:57       ` Daniel Mack
       [not found]         ` <1235606248-13004-1-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
  1 sibling, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-02-25 23:57 UTC (permalink / raw)
  To: linux-i2c-u79uwXL29TY76Z2rM5mHXA; +Cc: Daniel Mack

This patch adds a driver for Intersil's ISL29003 ambient light sensor
device plus some documentation. Inspired by tsl2550.c, a driver for a
similar device.

Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
---
Moved the driver to drivers/hwmon

 Documentation/hwmon/isl29003 |   62 +++++
 drivers/hwmon/Kconfig        |   10 +
 drivers/hwmon/Makefile       |    1 +
 drivers/hwmon/isl29003.c     |  517 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 590 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/hwmon/isl29003
 create mode 100644 drivers/hwmon/isl29003.c

diff --git a/Documentation/hwmon/isl29003 b/Documentation/hwmon/isl29003
new file mode 100644
index 0000000..c4ff5f3
--- /dev/null
+++ b/Documentation/hwmon/isl29003
@@ -0,0 +1,62 @@
+Kernel driver isl29003
+=====================
+
+Supported chips:
+* Intersil ISL29003
+Prefix: 'isl29003'
+Addresses scanned: none
+Datasheet:
+http://www.intersil.com/data/fn/fn7464.pdf
+
+Author: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
+
+
+Description
+-----------
+The ISL29003 is an integrated light sensor with a 16-bit integrating type
+ADC, I2C user programmable lux range select for optimized counts/lux, and
+I2C multi-function control and monitoring capabilities. The internal ADC
+provides 16-bit resolution while rejecting 50Hz and 60Hz flicker caused by
+artificial light sources.
+
+The driver allows to set the lux range, the bit resolution, the operational
+mode (see below) and the power state of device and can read the current lux
+value, of course.
+
+
+Detection
+---------
+
+The ISL29003 does not have an ID register which could be used to identify
+it, so the detection routine will just try to read from the configured I2C
+addess and consider the device to be present as soon as it ACKs the
+transfer.
+
+
+Sysfs entries
+-------------
+
+range:
+	0: 0 lux to 1000 lux (default)
+	1: 0 lux to 4000 lux
+	2: 0 lux to 16,000 lux
+	3: 0 lux to 64,000 lux
+
+resolution:
+	0: 2^16 cycles (default)
+	1: 2^12 cycles
+	2: 2^8 cycles
+	3: 2^4 cycles
+
+mode:
+	0: diode1's current (unsigned 16bit) (default)
+	1: diode1's current (unsigned 16bit)
+	2: difference between diodes (l1 - l2, signed 15bit)
+
+power_state:
+	0: device is disabled (default)
+	1: device is enabled
+
+lux (read only):
+	returns the value from the last sensor reading
+
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index b84bf06..ae9b93a 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -414,6 +414,16 @@ config SENSORS_IBMPEX
 	  This driver can also be built as a module.  If so, the module
 	  will be called ibmpex.
 
+config SENSORS_ISL29003
+	tristate "Intersil ISL29003 ambient light sensor"
+	depends on I2C
+	help
+	  If you say yes here you get support for the Intersil ISL29003
+	  ambient light sensor.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called isl29003.
+
 config SENSORS_IT87
 	tristate "ITE IT87xx and compatibles"
 	select HWMON_VID
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 2e80f37..933874f 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_SENSORS_HDAPS)	+= hdaps.o
 obj-$(CONFIG_SENSORS_I5K_AMB)	+= i5k_amb.o
 obj-$(CONFIG_SENSORS_IBMAEM)	+= ibmaem.o
 obj-$(CONFIG_SENSORS_IBMPEX)	+= ibmpex.o
+obj-$(CONFIG_SENSORS_ISL29003)	+= isl29003.o
 obj-$(CONFIG_SENSORS_IT87)	+= it87.o
 obj-$(CONFIG_SENSORS_K8TEMP)	+= k8temp.o
 obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o
diff --git a/drivers/hwmon/isl29003.c b/drivers/hwmon/isl29003.c
new file mode 100644
index 0000000..0ad3eae
--- /dev/null
+++ b/drivers/hwmon/isl29003.c
@@ -0,0 +1,517 @@
+/*
+ *  isl29003.c - Linux kernel modules for
+ * 	Intersil ISL29003 ambient light sensor
+ *
+ *  See file://Documentation/hwmon/isl29003
+ *
+ *  Copyright (c) 2009 Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
+ *
+ *  Based on code written by
+ *  	Rodolfo Giometti <giometti-k2GhghHVRtY@public.gmane.org>
+ *  	Eurotech S.p.A. <info-ymFC7zkAqBI1GQ1Ptb7lUw@public.gmane.org>
+ *
+ *  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>
+#include <linux/delay.h>
+
+#define ISL29003_DRV_NAME	"isl29003"
+#define DRIVER_VERSION		"1.0"
+
+#define ISL29003_REG_COMMAND		0x00
+#define ISL29003_ADC_ENABLED		(1 << 7)
+#define ISL29003_ADC_PD			(1 << 6)
+#define ISL29003_TIMING_INT		(1 << 5)
+#define ISL29003_MODE_SHIFT		(2)
+#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
+#define ISL29003_RES_SHIFT		(0)
+#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)
+
+#define ISL29003_REG_CONTROL		0x01
+#define ISL29003_INT_FLG		(1 << 5)
+#define ISL29003_RANGE_SHIFT		(2)
+#define ISL29003_RANGE_MASK		(0x3 << ISL29003_RANGE_SHIFT)
+#define ISL29003_INT_PERSISTS_SHIFT	(0)
+#define ISL29003_INT_PERSISTS_MASK	(0xf << ISL29003_INT_PERSISTS_SHIFT)
+
+#define ISL29003_REG_IRQ_THRESH_HI	0x02
+#define ISL29003_REG_IRQ_THRESH_LO	0x03
+#define ISL29003_REG_LSB_SENSOR		0x04
+#define ISL29003_REG_MSB_SENSOR		0x05
+#define ISL29003_REG_LSB_TIMER		0x06
+#define ISL29003_REG_MSB_TIMER		0x07
+
+struct isl29003_data {
+	struct i2c_client *client;
+	struct mutex update_lock;
+	u8 reg_cache[8];
+};
+
+static int gain_range[4] = {
+	1000, 4000, 16000, 64000
+};
+
+/*
+ * register access helpers
+ */
+
+static int __isl29003_read_reg(struct i2c_client *client,
+			       u32 reg, u8 mask, u8 shift)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	return (data->reg_cache[reg] & mask) >> shift;
+}
+
+static int __isl29003_write_reg(struct i2c_client *client,
+				u32 reg, u8 mask, u8 shift, u8 val)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	u8 tmp = data->reg_cache[reg];
+	int ret;
+
+	tmp &= ~mask;
+	tmp |= val << shift;
+
+	ret = i2c_smbus_write_byte_data(client, reg, tmp);
+	if (ret < 0)
+		return ret;
+
+	data->reg_cache[reg] = tmp;
+	return 0;
+}
+
+/*
+ * internally used functions
+ */
+
+/* range */
+static int isl29003_get_range(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT);
+}
+
+static int isl29003_set_range(struct i2c_client *client, int range)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT, range);
+}
+
+/* resolution */
+static int isl29003_get_resolution(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_resolution(struct i2c_client *client, int range)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, range);
+}
+
+/* mode */
+static int isl29003_get_mode(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_mode(struct i2c_client *client, int mode)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, mode);
+}
+
+/* power_state */
+static int isl29003_set_power_state(struct i2c_client *client, int state)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int ret;
+	u8 v = data->reg_cache[ISL29003_REG_COMMAND];
+
+	if (state == 0) {
+		v &= ~ISL29003_ADC_ENABLED;
+		v |= ISL29003_ADC_PD;
+	} else {
+		v |= ISL29003_ADC_ENABLED;
+		v &= ~ISL29003_ADC_PD;
+	}
+
+	ret = i2c_smbus_write_byte_data(client, ISL29003_REG_COMMAND, v);
+	if (ret < 0)
+		return ret;
+
+	data->reg_cache[ISL29003_REG_COMMAND] = v;
+	return 0;
+}
+
+static int isl29003_get_power_state(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
+	return (cmdreg & ISL29003_ADC_ENABLED) && (~cmdreg & ISL29003_ADC_PD);
+}
+
+static unsigned int isl29003_get_adc_value(struct i2c_client *client)
+{
+	int lsb, msb, range, bitdepth;
+
+	lsb = i2c_smbus_read_byte_data(client, ISL29003_REG_LSB_SENSOR);
+	if (lsb < 0)
+		return lsb;
+
+	msb = i2c_smbus_read_byte_data(client, ISL29003_REG_MSB_SENSOR);
+	if (msb < 0)
+		return msb;
+
+	range = isl29003_get_range(client);
+	bitdepth = (4 - isl29003_get_resolution(client)) * 4;
+	return (((msb << 8) | lsb) * gain_range[range]) >> bitdepth;
+}
+
+/*
+ * sysfs layer
+ */
+
+/* range */
+static ssize_t isl29003_show_range(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_range(client));
+}
+
+static ssize_t isl29003_store_range(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int ret;
+
+	if (strict_strtoul(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (val < 0 || val > 3)
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	mutex_lock(&data->update_lock);
+	ret = isl29003_set_range(client, val);
+	mutex_unlock(&data->update_lock);
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
+		   isl29003_show_range, isl29003_store_range);
+
+
+/* resolution */
+static ssize_t isl29003_show_resolution(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_resolution(client));
+}
+
+static ssize_t isl29003_store_resolution(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int ret;
+
+	if (strict_strtoul(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (val < 0 || val > 3)
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	mutex_lock(&data->update_lock);
+	ret = isl29003_set_resolution(client, val);
+	mutex_unlock(&data->update_lock);
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(resolution, S_IWUSR | S_IRUGO,
+		   isl29003_show_resolution, isl29003_store_resolution);
+
+/* mode */
+static ssize_t isl29003_show_mode(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_mode(client));
+}
+
+static ssize_t isl29003_store_mode(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int ret;
+
+	if (strict_strtoul(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (val < 0 || val > 2)
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	mutex_lock(&data->update_lock);
+	ret = isl29003_set_mode(client, val);
+	mutex_unlock(&data->update_lock);
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
+		   isl29003_show_mode, isl29003_store_mode);
+
+
+/* power state */
+static ssize_t isl29003_show_power_state(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%u\n", isl29003_get_power_state(client));
+}
+
+static ssize_t isl29003_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 isl29003_data *data = i2c_get_clientdata(client);
+	unsigned long val;
+	int ret;
+
+	if (strict_strtoul(buf, 10, &val) < 0)
+		return -EINVAL;
+
+	if (val < 0 || val > 1)
+		return -EINVAL;
+
+	mutex_lock(&data->update_lock);
+	ret = isl29003_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,
+		   isl29003_show_power_state, isl29003_store_power_state);
+
+
+static ssize_t isl29003_show_lux(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int ret;
+
+	/* No LUX data if not operational */
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	mutex_lock(&data->update_lock);
+	ret = sprintf(buf, "%d\n", isl29003_get_adc_value(client));
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+static DEVICE_ATTR(lux, S_IRUGO, isl29003_show_lux, NULL);
+
+static struct attribute *isl29003_attributes[] = {
+	&dev_attr_range.attr,
+	&dev_attr_resolution.attr,
+	&dev_attr_mode.attr,
+	&dev_attr_power_state.attr,
+	&dev_attr_lux.attr,
+	NULL
+};
+
+static const struct attribute_group isl29003_attr_group = {
+	.attrs = isl29003_attributes,
+};
+
+static int isl29003_init_client(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int i;
+
+	/* read all the registers once to fill the cache.
+	 * if a read fails, we consider the init failed */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++) {
+		int v = i2c_smbus_read_byte_data(client, i);
+		if (v < 0)
+			return -ENODEV;
+		data->reg_cache[i] = v;
+	}
+
+	/* set defaults */
+	isl29003_set_range(client, 0);
+	isl29003_set_resolution(client, 0);
+	isl29003_set_mode(client, 0);
+	isl29003_set_power_state(client, 0);
+
+	return 0;
+}
+
+/*
+ * I2C layer
+ */
+
+static int __devinit isl29003_probe(struct i2c_client *client,
+				   const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct isl29003_data *data;
+	int err = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) {
+		err = -EIO;
+		goto exit;
+	}
+
+	data = kzalloc(sizeof(struct isl29003_data), GFP_KERNEL);
+	if (!data) {
+		err = -ENOMEM;
+		goto exit;
+	}
+	data->client = client;
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->update_lock);
+
+	/* initialize the ISL29003 chip */
+	err = isl29003_init_client(client);
+	if (err)
+		goto exit_kfree;
+
+	/* register sysfs hooks */
+	err = sysfs_create_group(&client->dev.kobj, &isl29003_attr_group);
+	if (err)
+		goto exit_kfree;
+
+	dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);
+	return 0;
+
+exit_kfree:
+	kfree(data);
+exit:
+	return err;
+}
+
+static int __devexit isl29003_remove(struct i2c_client *client)
+{
+	sysfs_remove_group(&client->dev.kobj, &isl29003_attr_group);
+	isl29003_set_power_state(client, 0);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int isl29003_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	return isl29003_set_power_state(client, 0);
+}
+
+static int isl29003_resume(struct i2c_client *client)
+{
+	int i;
+
+	/* restore registers from cache */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
+		if (i2c_smbus_read_byte_data(client, i, data->reg_cache[i]) < 0)
+			return -ENODEV;
+
+	return 0;
+}
+
+#else
+#define isl29003_suspend	NULL
+#define isl29003_resume		NULL
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id isl29003_id[] = {
+	{ "isl29003", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, isl29003_id);
+
+static struct i2c_driver isl29003_driver = {
+	.driver = {
+		.name	= ISL29003_DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+	.suspend = isl29003_suspend,
+	.resume	= isl29003_resume,
+	.probe	= isl29003_probe,
+	.remove	= __devexit_p(isl29003_remove),
+	.id_table = isl29003_id,
+};
+
+static int __init isl29003_init(void)
+{
+	return i2c_add_driver(&isl29003_driver);
+}
+
+static void __exit isl29003_exit(void)
+{
+	i2c_del_driver(&isl29003_driver);
+}
+
+MODULE_AUTHOR("Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>");
+MODULE_DESCRIPTION("ISL29003 ambient light sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(isl29003_init);
+module_exit(isl29003_exit);
+
-- 
1.6.1.3

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]         ` <1235606248-13004-1-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
@ 2009-02-26  8:48           ` Jean Delvare
       [not found]             ` <20090226094857.50d0710d-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Jean Delvare @ 2009-02-26  8:48 UTC (permalink / raw)
  To: Daniel Mack; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Thu, 26 Feb 2009 00:57:28 +0100, Daniel Mack wrote:
> This patch adds a driver for Intersil's ISL29003 ambient light sensor
> device plus some documentation. Inspired by tsl2550.c, a driver for a
> similar device.
> 
> Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> ---
> Moved the driver to drivers/hwmon
> 
>  Documentation/hwmon/isl29003 |   62 +++++
>  drivers/hwmon/Kconfig        |   10 +
>  drivers/hwmon/Makefile       |    1 +
>  drivers/hwmon/isl29003.c     |  517 ++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 590 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/hwmon/isl29003
>  create mode 100644 drivers/hwmon/isl29003.c

Still no luck, sorry. Light sensors have nothing to do with hardware
monitoring.

I think we need a new subsystem for light sensors. drivers/light maybe?
Or maybe this fits into Jonathan Cameron's industrial I/O subsystem.
Jonathan, what do you think? If not, maybe a more general subsystem for
various sensor types (light, pressure, noise, whatever.)

Me, I don't really care where these drivers go as long as it's not in
drivers/i2c/chips.

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]         ` <20090225234004.GA19039-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
@ 2009-02-26  8:50           ` Jean Delvare
  0 siblings, 0 replies; 49+ messages in thread
From: Jean Delvare @ 2009-02-26  8:50 UTC (permalink / raw)
  To: Daniel Mack; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA

On Thu, 26 Feb 2009 00:40:04 +0100, Daniel Mack wrote:
> Hi Jean,
> 
> On Wed, Feb 25, 2009 at 09:21:19PM +0100, Jean Delvare wrote:
> > On Wed, 25 Feb 2009 21:10:18 +0100, Daniel Mack wrote:
> > > This patch adds a driver for Intersil's ISL29003 ambient light sensor
> > > device plus some documentation. Inspired by tsl2550.c, a driver for a
> > > similar device.
> > > 
> > > Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> > > ---
> > >  Documentation/i2c/chips/isl29003 |   62 +++++
> > >  drivers/i2c/chips/Kconfig        |   10 +
> > >  drivers/i2c/chips/Makefile       |    1 +
> > 
> > Please read the header of these 2 files again.
> 
> Ok, I missed that. But drivers for I2C based devices will still be
> submitted via this list?

In last resort only. Ideally all drivers would fit in a subsystem and
that subsystem's mailing list is the best place to post. Unless you
have I2C-specific problems, of course, in which case you can ask here.

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]             ` <20090226094857.50d0710d-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
@ 2009-02-26  9:06               ` Daniel Mack
       [not found]                 ` <20090226090651.GA20531-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
  2009-03-06 16:00               ` [PATCH] Added driver for ISL29003 ambient light sensor Daniel Mack
  1 sibling, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-02-26  9:06 UTC (permalink / raw)
  To: Jean Delvare; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Thu, Feb 26, 2009 at 09:48:57AM +0100, Jean Delvare wrote:
> On Thu, 26 Feb 2009 00:57:28 +0100, Daniel Mack wrote:
> > This patch adds a driver for Intersil's ISL29003 ambient light sensor
> > device plus some documentation. Inspired by tsl2550.c, a driver for a
> > similar device.
> > 
> > Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> > ---
> > Moved the driver to drivers/hwmon
> > 
> >  Documentation/hwmon/isl29003 |   62 +++++
> >  drivers/hwmon/Kconfig        |   10 +
> >  drivers/hwmon/Makefile       |    1 +
> >  drivers/hwmon/isl29003.c     |  517 ++++++++++++++++++++++++++++++++++++++++++
> >  4 files changed, 590 insertions(+), 0 deletions(-)
> >  create mode 100644 Documentation/hwmon/isl29003
> >  create mode 100644 drivers/hwmon/isl29003.c
> 
> Still no luck, sorry. Light sensors have nothing to do with hardware
> monitoring.

I decided to put it there because of 

config SENSORS_APPLESMC
        tristate "Apple SMC (Motion sensor, light sensor, keyboard
	backlight)"

> I think we need a new subsystem for light sensors. drivers/light maybe?
> Or maybe this fits into Jonathan Cameron's industrial I/O subsystem.
> Jonathan, what do you think? If not, maybe a more general subsystem for
> various sensor types (light, pressure, noise, whatever.)
> 
> Me, I don't really care where these drivers go as long as it's not in
> drivers/i2c/chips.

I don't care eighter - who's deciding it then? Just let me know where
you want it and I'll post a new patch.

Thanks,
Daniel

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                 ` <20090226090651.GA20531-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
@ 2009-02-26  9:42                   ` Jean Delvare
       [not found]                     ` <20090226104204.21dd9616-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Jean Delvare @ 2009-02-26  9:42 UTC (permalink / raw)
  To: Daniel Mack; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Thu, 26 Feb 2009 10:06:51 +0100, Daniel Mack wrote:
> On Thu, Feb 26, 2009 at 09:48:57AM +0100, Jean Delvare wrote:
> > On Thu, 26 Feb 2009 00:57:28 +0100, Daniel Mack wrote:
> > > This patch adds a driver for Intersil's ISL29003 ambient light sensor
> > > device plus some documentation. Inspired by tsl2550.c, a driver for a
> > > similar device.
> > > 
> > > Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> > > ---
> > > Moved the driver to drivers/hwmon
> > > 
> > >  Documentation/hwmon/isl29003 |   62 +++++
> > >  drivers/hwmon/Kconfig        |   10 +
> > >  drivers/hwmon/Makefile       |    1 +
> > >  drivers/hwmon/isl29003.c     |  517 ++++++++++++++++++++++++++++++++++++++++++
> > >  4 files changed, 590 insertions(+), 0 deletions(-)
> > >  create mode 100644 Documentation/hwmon/isl29003
> > >  create mode 100644 drivers/hwmon/isl29003.c
> > 
> > Still no luck, sorry. Light sensors have nothing to do with hardware
> > monitoring.
> 
> I decided to put it there because of 
> 
> config SENSORS_APPLESMC
>         tristate "Apple SMC (Motion sensor, light sensor, keyboard
> 	backlight)"

That driver is in hwmon for the motion sensor, and the temperature
sensors, not the light sensor. That being said I was never happy to see
these accelerometer drivers in hwmon, they don't have much in common
with traditional hardware monitoring chips so they should have their
own subsystem. Or they could go to drivers/mfd as these chips usually
have many functions (as outlined by the above summary line.)

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                     ` <20090226104204.21dd9616-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
@ 2009-02-26  9:57                       ` Daniel Mack
       [not found]                         ` <20090226095718.GA24575-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-02-26  9:57 UTC (permalink / raw)
  To: Jean Delvare; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Thu, Feb 26, 2009 at 10:42:04AM +0100, Jean Delvare wrote:
> > > >  create mode 100644 Documentation/hwmon/isl29003
> > > >  create mode 100644 drivers/hwmon/isl29003.c
> > > 
> > > Still no luck, sorry. Light sensors have nothing to do with hardware
> > > monitoring.
> > 
> > I decided to put it there because of 
> > 
> > config SENSORS_APPLESMC
> >         tristate "Apple SMC (Motion sensor, light sensor, keyboard
> > 	backlight)"
> 
> That driver is in hwmon for the motion sensor, and the temperature
> sensors, not the light sensor. 

Agreed. I was just looking for an appropriate place for it and the one
above is what came closest.

What about adding drivers/sensors and start moving things there? I'd
post a patch to move the tsl2550, the thing my driver derived from.

Does that sound suitable?

Daniel

^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 1/3] Added empty directories for hardware sensors
       [not found]                         ` <20090226095718.GA24575-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
@ 2009-02-26 13:24                           ` Daniel Mack
       [not found]                             ` <1235654648-3450-1-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-02-26 13:24 UTC (permalink / raw)
  To: linux-i2c-u79uwXL29TY76Z2rM5mHXA; +Cc: Daniel Mack

This adds empty directories and the neccessary entries to Makefiles and
Kconfigs in order to provide a place for hardware sensors which are not
meant to be based in drivers/hwmon.

Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
---
 How about this approach? Take it as RFC ...

 arch/arm/Kconfig         |    2 ++
 drivers/Kconfig          |    2 ++
 drivers/Makefile         |    1 +
 drivers/sensors/Kconfig  |   20 ++++++++++++++++++++
 drivers/sensors/Makefile |    4 ++++
 5 files changed, 29 insertions(+), 0 deletions(-)
 create mode 100644 drivers/sensors/Kconfig
 create mode 100644 drivers/sensors/Makefile

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index dbfdf87..91a6c55 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -1283,6 +1283,8 @@ source "drivers/power/Kconfig"
 
 source "drivers/hwmon/Kconfig"
 
+source "drivers/sensors/Kconfig"
+
 source "drivers/thermal/Kconfig"
 
 source "drivers/watchdog/Kconfig"
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 00cf955..57a9578 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -60,6 +60,8 @@ source "drivers/power/Kconfig"
 
 source "drivers/hwmon/Kconfig"
 
+source "drivers/sensors/Kconfig"
+
 source "drivers/thermal/Kconfig"
 
 source "drivers/watchdog/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index c1bf417..207a0d5 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -74,6 +74,7 @@ obj-y				+= i2c/
 obj-$(CONFIG_W1)		+= w1/
 obj-$(CONFIG_POWER_SUPPLY)	+= power/
 obj-$(CONFIG_HWMON)		+= hwmon/
+obj-$(CONFIG_SENSORS)		+= sensors/
 obj-$(CONFIG_THERMAL)		+= thermal/
 obj-$(CONFIG_WATCHDOG)		+= watchdog/
 obj-$(CONFIG_PHONE)		+= telephony/
diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig
new file mode 100644
index 0000000..bc85c90
--- /dev/null
+++ b/drivers/sensors/Kconfig
@@ -0,0 +1,20 @@
+#
+# Sensor drivers
+#
+
+menuconfig SENSORS
+	tristate "Sensor drivers"
+	depends on HAS_IOMEM
+	default n
+	help
+	  Sensor drivers are for special purpose devices that can monitor
+	  ambient light, motion, temperature and the like.
+
+	  Check file:Documentation/sensors to see which devices are
+	  currently supported.
+
+
+if SENSORS
+
+endif # SENSORS
+
diff --git a/drivers/sensors/Makefile b/drivers/sensors/Makefile
new file mode 100644
index 0000000..4c629c2
--- /dev/null
+++ b/drivers/sensors/Makefile
@@ -0,0 +1,4 @@
+#
+# Makefile for sensor chip drivers.
+#
+
-- 
1.6.1.3

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH 2/3] Added driver for ISL29003 ambient light sensor
       [not found]                             ` <1235654648-3450-1-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
@ 2009-02-26 13:24                               ` Daniel Mack
       [not found]                                 ` <1235654648-3450-2-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
  2009-02-28 12:40                               ` [PATCH 1/3] Added empty directories for hardware sensors Daniel Mack
  1 sibling, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-02-26 13:24 UTC (permalink / raw)
  To: linux-i2c-u79uwXL29TY76Z2rM5mHXA; +Cc: Daniel Mack

This patch adds a driver for Intersil's ISL29003 ambient light sensor
device plus some documentation. Inspired by tsl2550.c, a driver for a
similar device.

Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
---
 Documentation/sensors/isl29003 |   62 +++++
 drivers/sensors/Kconfig        |   12 +
 drivers/sensors/Makefile       |    2 +
 drivers/sensors/isl29003.c     |  486 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 562 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/sensors/isl29003
 create mode 100644 drivers/sensors/isl29003.c

diff --git a/Documentation/sensors/isl29003 b/Documentation/sensors/isl29003
new file mode 100644
index 0000000..c4ff5f3
--- /dev/null
+++ b/Documentation/sensors/isl29003
@@ -0,0 +1,62 @@
+Kernel driver isl29003
+=====================
+
+Supported chips:
+* Intersil ISL29003
+Prefix: 'isl29003'
+Addresses scanned: none
+Datasheet:
+http://www.intersil.com/data/fn/fn7464.pdf
+
+Author: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
+
+
+Description
+-----------
+The ISL29003 is an integrated light sensor with a 16-bit integrating type
+ADC, I2C user programmable lux range select for optimized counts/lux, and
+I2C multi-function control and monitoring capabilities. The internal ADC
+provides 16-bit resolution while rejecting 50Hz and 60Hz flicker caused by
+artificial light sources.
+
+The driver allows to set the lux range, the bit resolution, the operational
+mode (see below) and the power state of device and can read the current lux
+value, of course.
+
+
+Detection
+---------
+
+The ISL29003 does not have an ID register which could be used to identify
+it, so the detection routine will just try to read from the configured I2C
+addess and consider the device to be present as soon as it ACKs the
+transfer.
+
+
+Sysfs entries
+-------------
+
+range:
+	0: 0 lux to 1000 lux (default)
+	1: 0 lux to 4000 lux
+	2: 0 lux to 16,000 lux
+	3: 0 lux to 64,000 lux
+
+resolution:
+	0: 2^16 cycles (default)
+	1: 2^12 cycles
+	2: 2^8 cycles
+	3: 2^4 cycles
+
+mode:
+	0: diode1's current (unsigned 16bit) (default)
+	1: diode1's current (unsigned 16bit)
+	2: difference between diodes (l1 - l2, signed 15bit)
+
+power_state:
+	0: device is disabled (default)
+	1: device is enabled
+
+lux (read only):
+	returns the value from the last sensor reading
+
diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig
index bc85c90..8cf9409 100644
--- a/drivers/sensors/Kconfig
+++ b/drivers/sensors/Kconfig
@@ -16,5 +16,17 @@ menuconfig SENSORS
 
 if SENSORS
 
+config SENSORS_ISL29003
+	tristate "Intersil ISL29003 ambient light sensor"
+	depends on I2C
+	default n
+	help
+	  If you say yes here you get support for the Intersil ISL29003
+	  ambient light sensor.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called isl29003.
+
+
 endif # SENSORS
 
diff --git a/drivers/sensors/Makefile b/drivers/sensors/Makefile
index 4c629c2..1ebe5f2 100644
--- a/drivers/sensors/Makefile
+++ b/drivers/sensors/Makefile
@@ -2,3 +2,5 @@
 # Makefile for sensor chip drivers.
 #
 
+obj-$(CONFIG_SENSORS_ISL29003)  += isl29003.o
+
diff --git a/drivers/sensors/isl29003.c b/drivers/sensors/isl29003.c
new file mode 100644
index 0000000..dc8d998
--- /dev/null
+++ b/drivers/sensors/isl29003.c
@@ -0,0 +1,486 @@
+/*
+ *  isl29003.c - Linux kernel modules for
+ * 	Intersil ISL29003 ambient light sensor
+ *
+ *  See file:Documentation/sensors/isl29003
+ *
+ *  Copyright (c) 2009 Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
+ *
+ *  Based on code written by
+ *  	Rodolfo Giometti <giometti-k2GhghHVRtY@public.gmane.org>
+ *  	Eurotech S.p.A. <info-ymFC7zkAqBI1GQ1Ptb7lUw@public.gmane.org>
+ *
+ *  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>
+#include <linux/delay.h>
+
+#define ISL29003_DRV_NAME	"isl29003"
+#define DRIVER_VERSION		"1.0"
+
+#define ISL29003_REG_COMMAND		0x00
+#define ISL29003_ADC_ENABLED		(1 << 7)
+#define ISL29003_ADC_PD			(1 << 6)
+#define ISL29003_TIMING_INT		(1 << 5)
+#define ISL29003_MODE_SHIFT		(2)
+#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
+#define ISL29003_RES_SHIFT		(0)
+#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)
+
+#define ISL29003_REG_CONTROL		0x01
+#define ISL29003_INT_FLG		(1 << 5)
+#define ISL29003_RANGE_SHIFT		(2)
+#define ISL29003_RANGE_MASK		(0x3 << ISL29003_RANGE_SHIFT)
+#define ISL29003_INT_PERSISTS_SHIFT	(0)
+#define ISL29003_INT_PERSISTS_MASK	(0xf << ISL29003_INT_PERSISTS_SHIFT)
+
+#define ISL29003_REG_IRQ_THRESH_HI	0x02
+#define ISL29003_REG_IRQ_THRESH_LO	0x03
+#define ISL29003_REG_LSB_SENSOR		0x04
+#define ISL29003_REG_MSB_SENSOR		0x05
+#define ISL29003_REG_LSB_TIMER		0x06
+#define ISL29003_REG_MSB_TIMER		0x07
+
+struct isl29003_data {
+	struct i2c_client *client;
+	struct mutex lock;
+	u8 reg_cache[8];
+};
+
+static int gain_range[] = {
+	1000, 4000, 16000, 64000
+};
+
+/*
+ * register access helpers
+ */
+
+static int __isl29003_read_reg(struct i2c_client *client,
+			       u32 reg, u8 mask, u8 shift)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	return (data->reg_cache[reg] & mask) >> shift;
+}
+
+static int __isl29003_write_reg(struct i2c_client *client,
+				u32 reg, u8 mask, u8 shift, u8 val)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int ret = 0;
+	u8 tmp;
+
+	mutex_lock(&data->lock);
+
+	tmp = data->reg_cache[reg];
+	tmp &= ~mask;
+	tmp |= val << shift;
+
+	ret = i2c_smbus_write_byte_data(client, reg, tmp);
+	if (ret >= 0)
+		data->reg_cache[reg] = tmp;
+
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+/*
+ * internally used functions
+ */
+
+/* range */
+static int isl29003_get_range(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT);
+}
+
+static int isl29003_set_range(struct i2c_client *client, int range)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT, range);
+}
+
+/* resolution */
+static int isl29003_get_resolution(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_resolution(struct i2c_client *client, int res)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, res);
+}
+
+/* mode */
+static int isl29003_get_mode(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_mode(struct i2c_client *client, int mode)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, mode);
+}
+
+/* power_state */
+static int isl29003_set_power_state(struct i2c_client *client, int state)
+{
+	int ret;
+
+	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_ADC_ENABLED, 0, state);
+	if (ret < 0)
+		return ret;
+
+	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_ADC_PD, 0, ~state);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int isl29003_get_power_state(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
+	return (cmdreg & ISL29003_ADC_ENABLED) && (~cmdreg & ISL29003_ADC_PD);
+}
+
+static int isl29003_get_adc_value(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int lsb, msb, range, bitdepth;
+
+	mutex_lock(&data->lock);
+	lsb = i2c_smbus_read_byte_data(client, ISL29003_REG_LSB_SENSOR);
+
+	if (lsb < 0) {
+		mutex_unlock(&data->lock);
+		return lsb;
+	}
+
+	msb = i2c_smbus_read_byte_data(client, ISL29003_REG_MSB_SENSOR);
+	mutex_unlock(&data->lock);
+
+	if (msb < 0)
+		return msb;
+
+	range = isl29003_get_range(client);
+	bitdepth = (4 - isl29003_get_resolution(client)) * 4;
+	return (((msb << 8) | lsb) * gain_range[range]) >> bitdepth;
+}
+
+/*
+ * sysfs layer
+ */
+
+/* range */
+static ssize_t isl29003_show_range(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_range(client));
+}
+
+static ssize_t isl29003_store_range(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	ret = isl29003_set_range(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
+		   isl29003_show_range, isl29003_store_range);
+
+
+/* resolution */
+static ssize_t isl29003_show_resolution(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_resolution(client));
+}
+
+static ssize_t isl29003_store_resolution(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	ret = isl29003_set_resolution(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(resolution, S_IWUSR | S_IRUGO,
+		   isl29003_show_resolution, isl29003_store_resolution);
+
+/* mode */
+static ssize_t isl29003_show_mode(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_mode(client));
+}
+
+static ssize_t isl29003_store_mode(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 2))
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	ret = isl29003_set_mode(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
+		   isl29003_show_mode, isl29003_store_mode);
+
+
+/* power state */
+static ssize_t isl29003_show_power_state(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_power_state(client));
+}
+
+static ssize_t isl29003_store_power_state(struct device *dev,
+					  struct device_attribute *attr,
+					  const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
+		return -EINVAL;
+
+	ret = isl29003_set_power_state(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO,
+		   isl29003_show_power_state, isl29003_store_power_state);
+
+
+/* lux */
+static ssize_t isl29003_show_lux(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	/* No LUX data if not operational */
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	return sprintf(buf, "%d\n", isl29003_get_adc_value(client));
+}
+
+static DEVICE_ATTR(lux, S_IRUGO, isl29003_show_lux, NULL);
+
+static struct attribute *isl29003_attributes[] = {
+	&dev_attr_range.attr,
+	&dev_attr_resolution.attr,
+	&dev_attr_mode.attr,
+	&dev_attr_power_state.attr,
+	&dev_attr_lux.attr,
+	NULL
+};
+
+static const struct attribute_group isl29003_attr_group = {
+	.attrs = isl29003_attributes,
+};
+
+static int isl29003_init_client(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int i;
+
+	/* read all the registers once to fill the cache.
+	 * if one of the reads fails, we consider the init failed */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++) {
+		int v = i2c_smbus_read_byte_data(client, i);
+		if (v < 0)
+			return -ENODEV;
+
+		data->reg_cache[i] = v;
+	}
+
+	/* set defaults */
+	isl29003_set_range(client, 0);
+	isl29003_set_resolution(client, 0);
+	isl29003_set_mode(client, 0);
+	isl29003_set_power_state(client, 0);
+
+	return 0;
+}
+
+/*
+ * I2C layer
+ */
+
+static int __devinit isl29003_probe(struct i2c_client *client,
+				    const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct isl29003_data *data;
+	int err = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+		return -EIO;
+
+	data = kzalloc(sizeof(struct isl29003_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->client = client;
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->lock);
+
+	/* initialize the ISL29003 chip */
+	err = isl29003_init_client(client);
+	if (err)
+		goto exit_kfree;
+
+	/* register sysfs hooks */
+	err = sysfs_create_group(&client->dev.kobj, &isl29003_attr_group);
+	if (err)
+		goto exit_kfree;
+
+	dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);
+	return 0;
+
+exit_kfree:
+	kfree(data);
+	return err;
+}
+
+static int __devexit isl29003_remove(struct i2c_client *client)
+{
+	sysfs_remove_group(&client->dev.kobj, &isl29003_attr_group);
+	isl29003_set_power_state(client, 0);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int isl29003_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	return isl29003_set_power_state(client, 0);
+}
+
+static int isl29003_resume(struct i2c_client *client)
+{
+	int i;
+
+	/* restore registers from cache */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
+		if (i2c_smbus_read_byte_data(client, i, data->reg_cache[i]) < 0)
+			return -ENODEV;
+
+	return 0;
+}
+
+#else
+#define isl29003_suspend	NULL
+#define isl29003_resume		NULL
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id isl29003_id[] = {
+	{ "isl29003", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, isl29003_id);
+
+static struct i2c_driver isl29003_driver = {
+	.driver = {
+		.name	= ISL29003_DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+	.suspend = isl29003_suspend,
+	.resume	= isl29003_resume,
+	.probe	= isl29003_probe,
+	.remove	= __devexit_p(isl29003_remove),
+	.id_table = isl29003_id,
+};
+
+static int __init isl29003_init(void)
+{
+	return i2c_add_driver(&isl29003_driver);
+}
+
+static void __exit isl29003_exit(void)
+{
+	i2c_del_driver(&isl29003_driver);
+}
+
+MODULE_AUTHOR("Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>");
+MODULE_DESCRIPTION("ISL29003 ambient light sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(isl29003_init);
+module_exit(isl29003_exit);
+
-- 
1.6.1.3

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH 3/3] move tsl2550.c to drivers/sensors
       [not found]                                 ` <1235654648-3450-2-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
@ 2009-02-26 13:24                                   ` Daniel Mack
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel Mack @ 2009-02-26 13:24 UTC (permalink / raw)
  To: linux-i2c-u79uwXL29TY76Z2rM5mHXA; +Cc: Daniel Mack

As drivers/i2c(chips is deprecated, move tsl2550.c to the newly
allocated place drivers/sensors.

Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
---
 drivers/i2c/chips/Kconfig   |   10 -
 drivers/i2c/chips/Makefile  |    1 -
 drivers/i2c/chips/tsl2550.c |  489 -------------------------------------------
 drivers/sensors/Kconfig     |    9 +
 drivers/sensors/Makefile    |    3 +-
 drivers/sensors/tsl2550.c   |  489 +++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 500 insertions(+), 501 deletions(-)
 delete mode 100644 drivers/i2c/chips/tsl2550.c
 create mode 100644 drivers/sensors/tsl2550.c

diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index c80312c..5fdaa33 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -92,14 +92,4 @@ config SENSORS_MAX6875
 	  This driver can also be built as a module.  If so, the module
 	  will be called max6875.
 
-config SENSORS_TSL2550
-	tristate "Taos TSL2550 ambient light sensor"
-	depends on EXPERIMENTAL
-	help
-	  If you say yes here you get support for the Taos TSL2550
-	  ambient light sensor.
-
-	  This driver can also be built as a module.  If so, the module
-	  will be called tsl2550.
-
 endmenu
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
index d142f23..c4e55b9 100644
--- a/drivers/i2c/chips/Makefile
+++ b/drivers/i2c/chips/Makefile
@@ -16,7 +16,6 @@ obj-$(CONFIG_SENSORS_PCA9539)	+= pca9539.o
 obj-$(CONFIG_SENSORS_PCF8574)	+= pcf8574.o
 obj-$(CONFIG_PCF8575)		+= pcf8575.o
 obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
-obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
 
 ifeq ($(CONFIG_I2C_DEBUG_CHIP),y)
 EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/i2c/chips/tsl2550.c b/drivers/i2c/chips/tsl2550.c
deleted file mode 100644
index 1a9cc13..0000000
--- a/drivers/i2c/chips/tsl2550.c
+++ /dev/null
@@ -1,489 +0,0 @@
-/*
- *  tsl2550.c - Linux kernel modules for ambient light sensor
- *
- *  Copyright (C) 2007 Rodolfo Giometti <giometti-k2GhghHVRtY@public.gmane.org>
- *  Copyright (C) 2007 Eurotech S.p.A. <info-ymFC7zkAqBI1GQ1Ptb7lUw@public.gmane.org>
- *
- *  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>
-#include <linux/delay.h>
-
-#define TSL2550_DRV_NAME	"tsl2550"
-#define DRIVER_VERSION		"1.1.1"
-
-/*
- * 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)
-{
-	unsigned long end;
-	int loop = 0, ret = 0;
-
-	/*
-	 * Read ADC channel waiting at most 400ms (see data sheet for further
-	 * info).
-	 * To avoid long busy wait we spin for few milliseconds then
-	 * start sleeping.
-	 */
-	end = jiffies + msecs_to_jiffies(400);
-	while (time_before(jiffies, end)) {
-		i2c_smbus_write_byte(client, cmd);
-
-		if (loop++ < 5)
-			mdelay(1);
-		else
-			msleep(1);
-
-		ret = i2c_smbus_read_byte(client);
-		if (ret < 0)
-			return ret;
-		else if (ret & 0x0080)
-			break;
-	}
-	if (!(ret & 0x80))
-		return -EIO;
-	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 (c0 && (c1 <= c0))
-		r = c1 * 128 / c0;
-	else
-		return -1;
-
-	/* Calculate LUX */
-	lux = ((c0 - c1) * ratio_lut[r]) / 256;
-
-	/* 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)
-{
-	u8 ch0, ch1;
-	int ret;
-
-	ret = tsl2550_get_adc_value(client, TSL2550_READ_ADC0);
-	if (ret < 0)
-		return ret;
-	ch0 = ret;
-
-	mdelay(1);
-
-	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;
-
-	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_write_byte(client, TSL2550_POWER_UP);
-	if (err < 0)
-		return err;
-	mdelay(1);
-	if (i2c_smbus_read_byte(client) != 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_BYTE)) {
-		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-k2GhghHVRtY@public.gmane.org>");
-MODULE_DESCRIPTION("TSL2550 ambient light sensor driver");
-MODULE_LICENSE("GPL");
-MODULE_VERSION(DRIVER_VERSION);
-
-module_init(tsl2550_init);
-module_exit(tsl2550_exit);
diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig
index 8cf9409..a5d1c2e 100644
--- a/drivers/sensors/Kconfig
+++ b/drivers/sensors/Kconfig
@@ -27,6 +27,15 @@ config SENSORS_ISL29003
 	  This driver can also be built as a module.  If so, the module
 	  will be called isl29003.
 
+config SENSORS_TSL2550
+	tristate "Taos TSL2550 ambient light sensor"
+	depends on I2C
+	help
+	  If you say yes here you get support for the Taos TSL2550
+	  ambient light sensor.
+          
+	  This driver can also be built as a module.  If so, the module
+	  will be called tsl2550.
 
 endif # SENSORS
 
diff --git a/drivers/sensors/Makefile b/drivers/sensors/Makefile
index 1ebe5f2..874877d 100644
--- a/drivers/sensors/Makefile
+++ b/drivers/sensors/Makefile
@@ -2,5 +2,6 @@
 # Makefile for sensor chip drivers.
 #
 
-obj-$(CONFIG_SENSORS_ISL29003)  += isl29003.o
+obj-$(CONFIG_SENSORS_ISL29003)	+= isl29003.o
+obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
 
diff --git a/drivers/sensors/tsl2550.c b/drivers/sensors/tsl2550.c
new file mode 100644
index 0000000..1a9cc13
--- /dev/null
+++ b/drivers/sensors/tsl2550.c
@@ -0,0 +1,489 @@
+/*
+ *  tsl2550.c - Linux kernel modules for ambient light sensor
+ *
+ *  Copyright (C) 2007 Rodolfo Giometti <giometti-k2GhghHVRtY@public.gmane.org>
+ *  Copyright (C) 2007 Eurotech S.p.A. <info-ymFC7zkAqBI1GQ1Ptb7lUw@public.gmane.org>
+ *
+ *  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>
+#include <linux/delay.h>
+
+#define TSL2550_DRV_NAME	"tsl2550"
+#define DRIVER_VERSION		"1.1.1"
+
+/*
+ * 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)
+{
+	unsigned long end;
+	int loop = 0, ret = 0;
+
+	/*
+	 * Read ADC channel waiting at most 400ms (see data sheet for further
+	 * info).
+	 * To avoid long busy wait we spin for few milliseconds then
+	 * start sleeping.
+	 */
+	end = jiffies + msecs_to_jiffies(400);
+	while (time_before(jiffies, end)) {
+		i2c_smbus_write_byte(client, cmd);
+
+		if (loop++ < 5)
+			mdelay(1);
+		else
+			msleep(1);
+
+		ret = i2c_smbus_read_byte(client);
+		if (ret < 0)
+			return ret;
+		else if (ret & 0x0080)
+			break;
+	}
+	if (!(ret & 0x80))
+		return -EIO;
+	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 (c0 && (c1 <= c0))
+		r = c1 * 128 / c0;
+	else
+		return -1;
+
+	/* Calculate LUX */
+	lux = ((c0 - c1) * ratio_lut[r]) / 256;
+
+	/* 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)
+{
+	u8 ch0, ch1;
+	int ret;
+
+	ret = tsl2550_get_adc_value(client, TSL2550_READ_ADC0);
+	if (ret < 0)
+		return ret;
+	ch0 = ret;
+
+	mdelay(1);
+
+	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;
+
+	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_write_byte(client, TSL2550_POWER_UP);
+	if (err < 0)
+		return err;
+	mdelay(1);
+	if (i2c_smbus_read_byte(client) != 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_BYTE)) {
+		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-k2GhghHVRtY@public.gmane.org>");
+MODULE_DESCRIPTION("TSL2550 ambient light sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(tsl2550_init);
+module_exit(tsl2550_exit);
-- 
1.6.1.3

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [PATCH 1/3] Added empty directories for hardware sensors
       [not found]                             ` <1235654648-3450-1-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
  2009-02-26 13:24                               ` [PATCH 2/3] Added driver for ISL29003 ambient light sensor Daniel Mack
@ 2009-02-28 12:40                               ` Daniel Mack
       [not found]                                 ` <20090228124048.GB18336-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
  1 sibling, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-02-28 12:40 UTC (permalink / raw)
  To: linux-i2c-u79uwXL29TY76Z2rM5mHXA

On Thu, Feb 26, 2009 at 02:24:06PM +0100, Daniel Mack wrote:
> This adds empty directories and the neccessary entries to Makefiles and
> Kconfigs in order to provide a place for hardware sensors which are not
> meant to be based in drivers/hwmon.
> 
> Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> ---
>  How about this approach? Take it as RFC ...

Any oppinion on this, anyone? I'm currently writing a driver for an
accelerometer and will have the same uncertainty of where to put it in
the end.

Daniel



> 
>  arch/arm/Kconfig         |    2 ++
>  drivers/Kconfig          |    2 ++
>  drivers/Makefile         |    1 +
>  drivers/sensors/Kconfig  |   20 ++++++++++++++++++++
>  drivers/sensors/Makefile |    4 ++++
>  5 files changed, 29 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/sensors/Kconfig
>  create mode 100644 drivers/sensors/Makefile
> 
> diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
> index dbfdf87..91a6c55 100644
> --- a/arch/arm/Kconfig
> +++ b/arch/arm/Kconfig
> @@ -1283,6 +1283,8 @@ source "drivers/power/Kconfig"
>  
>  source "drivers/hwmon/Kconfig"
>  
> +source "drivers/sensors/Kconfig"
> +
>  source "drivers/thermal/Kconfig"
>  
>  source "drivers/watchdog/Kconfig"
> diff --git a/drivers/Kconfig b/drivers/Kconfig
> index 00cf955..57a9578 100644
> --- a/drivers/Kconfig
> +++ b/drivers/Kconfig
> @@ -60,6 +60,8 @@ source "drivers/power/Kconfig"
>  
>  source "drivers/hwmon/Kconfig"
>  
> +source "drivers/sensors/Kconfig"
> +
>  source "drivers/thermal/Kconfig"
>  
>  source "drivers/watchdog/Kconfig"
> diff --git a/drivers/Makefile b/drivers/Makefile
> index c1bf417..207a0d5 100644
> --- a/drivers/Makefile
> +++ b/drivers/Makefile
> @@ -74,6 +74,7 @@ obj-y				+= i2c/
>  obj-$(CONFIG_W1)		+= w1/
>  obj-$(CONFIG_POWER_SUPPLY)	+= power/
>  obj-$(CONFIG_HWMON)		+= hwmon/
> +obj-$(CONFIG_SENSORS)		+= sensors/
>  obj-$(CONFIG_THERMAL)		+= thermal/
>  obj-$(CONFIG_WATCHDOG)		+= watchdog/
>  obj-$(CONFIG_PHONE)		+= telephony/
> diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig
> new file mode 100644
> index 0000000..bc85c90
> --- /dev/null
> +++ b/drivers/sensors/Kconfig
> @@ -0,0 +1,20 @@
> +#
> +# Sensor drivers
> +#
> +
> +menuconfig SENSORS
> +	tristate "Sensor drivers"
> +	depends on HAS_IOMEM
> +	default n
> +	help
> +	  Sensor drivers are for special purpose devices that can monitor
> +	  ambient light, motion, temperature and the like.
> +
> +	  Check file:Documentation/sensors to see which devices are
> +	  currently supported.
> +
> +
> +if SENSORS
> +
> +endif # SENSORS
> +
> diff --git a/drivers/sensors/Makefile b/drivers/sensors/Makefile
> new file mode 100644
> index 0000000..4c629c2
> --- /dev/null
> +++ b/drivers/sensors/Makefile
> @@ -0,0 +1,4 @@
> +#
> +# Makefile for sensor chip drivers.
> +#
> +
> -- 
> 1.6.1.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 1/3] Added empty directories for hardware sensors
       [not found]                                 ` <20090228124048.GB18336-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
@ 2009-03-02  5:22                                   ` Trilok Soni
       [not found]                                     ` <5d5443650903012122p795d51cdr18c1a79c606cd496-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Trilok Soni @ 2009-03-02  5:22 UTC (permalink / raw)
  To: Daniel Mack; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA

Hi Daniel,

On Sat, Feb 28, 2009 at 6:10 PM, Daniel Mack <daniel-rDUAYElUppE@public.gmane.org> wrote:
> On Thu, Feb 26, 2009 at 02:24:06PM +0100, Daniel Mack wrote:
>> This adds empty directories and the neccessary entries to Makefiles and
>> Kconfigs in order to provide a place for hardware sensors which are not
>> meant to be based in drivers/hwmon.
>>
>> Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
>> ---
>>  How about this approach? Take it as RFC ...
>
> Any oppinion on this, anyone? I'm currently writing a driver for an
> accelerometer and will have the same uncertainty of where to put it in
> the end.

Better to submit this patch to LKML too, as it adds new directory. For
accelerometer, few patches were submitted as "Industrial IO" framework
from Jonathan Cameron. If you grep in LKML archive you will get the
idea, also another place for accelerometer could be drivers/hwmon
directory too if it fits in your driver requirements.

-- 
---Trilok Soni
http://triloksoni.wordpress.com
http://www.linkedin.com/in/triloksoni

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 1/3] Added empty directories for hardware sensors
       [not found]                                     ` <5d5443650903012122p795d51cdr18c1a79c606cd496-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2009-03-02 10:12                                       ` Daniel Mack
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel Mack @ 2009-03-02 10:12 UTC (permalink / raw)
  To: Trilok Soni; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA

Hi Trilok,

On Mon, Mar 02, 2009 at 10:52:38AM +0530, Trilok Soni wrote:
> >> This adds empty directories and the neccessary entries to Makefiles and
> >> Kconfigs in order to provide a place for hardware sensors which are not
> >> meant to be based in drivers/hwmon.
> >>
> >> Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> >> ---
> >>  How about this approach? Take it as RFC ...
> >
> > Any oppinion on this, anyone? I'm currently writing a driver for an
> > accelerometer and will have the same uncertainty of where to put it in
> > the end.
> 
> Better to submit this patch to LKML too, as it adds new directory. For

Did that yesterday :)

> accelerometer, few patches were submitted as "Industrial IO" framework
> from Jonathan Cameron. If you grep in LKML archive you will get the
> idea, also another place for accelerometer could be drivers/hwmon
> directory too if it fits in your driver requirements.

I realized that there is a driver for this accellerometer device
already, it's just not usable with i2c/spi accessors. I'll discuss that
now on the lkml, the question that started the thread is hence only
left open for the ambient light sensors (my own one and the tps2550 in
drivers/i2c/chips).

Daniel

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]             ` <20090226094857.50d0710d-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
  2009-02-26  9:06               ` Daniel Mack
@ 2009-03-06 16:00               ` Daniel Mack
       [not found]                 ` <20090306160042.GA17800-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
  1 sibling, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-03-06 16:00 UTC (permalink / raw)
  To: Jean Delvare; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Thu, Feb 26, 2009 at 09:48:57AM +0100, Jean Delvare wrote:
> On Thu, 26 Feb 2009 00:57:28 +0100, Daniel Mack wrote:
> > This patch adds a driver for Intersil's ISL29003 ambient light sensor
> > device plus some documentation. Inspired by tsl2550.c, a driver for a
> > similar device.
> > 
> > Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> > ---
> > Moved the driver to drivers/hwmon
> > 
> >  Documentation/hwmon/isl29003 |   62 +++++
> >  drivers/hwmon/Kconfig        |   10 +
> >  drivers/hwmon/Makefile       |    1 +
> >  drivers/hwmon/isl29003.c     |  517 ++++++++++++++++++++++++++++++++++++++++++
> >  4 files changed, 590 insertions(+), 0 deletions(-)
> >  create mode 100644 Documentation/hwmon/isl29003
> >  create mode 100644 drivers/hwmon/isl29003.c
> 
> Still no luck, sorry. Light sensors have nothing to do with hardware
> monitoring.

Sorry to annoy you again with that topic, but I'm a little stuck here.
The 'industrial I/O' thread I was pointed to seems to be orphaned and I
didn't get much response on the LKML about alternatives either.

As hwmon still appears the best existing place for such a driver to live
in, could we just take it and move it somewhere else as soon somebody
comes along with some better idea? I'd just like to avoid it being lost,
and every other approach does not feel very productive currently.

Daniel

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                 ` <20090306160042.GA17800-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
@ 2009-03-06 17:24                   ` Jean Delvare
       [not found]                     ` <20090306182432.338b0244-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Jean Delvare @ 2009-03-06 17:24 UTC (permalink / raw)
  To: Daniel Mack; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

Hi Daniel,

On Fri, 6 Mar 2009 17:00:42 +0100, Daniel Mack wrote:
> On Thu, Feb 26, 2009 at 09:48:57AM +0100, Jean Delvare wrote:
> > On Thu, 26 Feb 2009 00:57:28 +0100, Daniel Mack wrote:
> > > This patch adds a driver for Intersil's ISL29003 ambient light sensor
> > > device plus some documentation. Inspired by tsl2550.c, a driver for a
> > > similar device.
> > > 
> > > Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> > > ---
> > > Moved the driver to drivers/hwmon
> > > 
> > >  Documentation/hwmon/isl29003 |   62 +++++
> > >  drivers/hwmon/Kconfig        |   10 +
> > >  drivers/hwmon/Makefile       |    1 +
> > >  drivers/hwmon/isl29003.c     |  517 ++++++++++++++++++++++++++++++++++++++++++
> > >  4 files changed, 590 insertions(+), 0 deletions(-)
> > >  create mode 100644 Documentation/hwmon/isl29003
> > >  create mode 100644 drivers/hwmon/isl29003.c
> > 
> > Still no luck, sorry. Light sensors have nothing to do with hardware
> > monitoring.
> 
> Sorry to annoy you again with that topic, but I'm a little stuck here.
> The 'industrial I/O' thread I was pointed to seems to be orphaned and I
> didn't get much response on the LKML about alternatives either.

Sorry for the long silence, I have been pretty busy these last days,
and was also hopping for a comment from Jonathan. The industrial I/O
subsystem isn't orphaned, I am fairly certain it will be merged some
day, but it could take some time.

> As hwmon still appears the best existing place for such a driver to live
> in, could we just take it and move it somewhere else as soon somebody
> comes along with some better idea? I'd just like to avoid it being lost,
> and every other approach does not feel very productive currently.

I agree that it doesn't make sense to wait for the industrial I/O
subsystem to be merged. But I also do not want to put in drivers/hwmon
drivers which do not really belong there. So I'd either go the way you
proposed already (drivers/sensors for non-hwmon sensor drivers) or
simply put your driver in drivers/misc if this is only meant as a
temporary home until the industrial I/O subsystem is merged.

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                     ` <20090306182432.338b0244-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
@ 2009-03-06 18:09                       ` Jonathan Cameron
       [not found]                         ` <49B166E2.5060909-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
  2009-03-06 18:14                       ` Daniel Mack
  1 sibling, 1 reply; 49+ messages in thread
From: Jonathan Cameron @ 2009-03-06 18:09 UTC (permalink / raw)
  To: Jean Delvare; +Cc: Daniel Mack, linux-i2c-u79uwXL29TY76Z2rM5mHXA

Jean Delvare wrote:
> Hi Daniel,
> 
> On Fri, 6 Mar 2009 17:00:42 +0100, Daniel Mack wrote:
>> On Thu, Feb 26, 2009 at 09:48:57AM +0100, Jean Delvare wrote:
>>> On Thu, 26 Feb 2009 00:57:28 +0100, Daniel Mack wrote:
>>>> This patch adds a driver for Intersil's ISL29003 ambient light sensor
>>>> device plus some documentation. Inspired by tsl2550.c, a driver for a
>>>> similar device.
>>>>
>>>> Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
>>>> ---
>>>> Moved the driver to drivers/hwmon
>>>>
>>>>  Documentation/hwmon/isl29003 |   62 +++++
>>>>  drivers/hwmon/Kconfig        |   10 +
>>>>  drivers/hwmon/Makefile       |    1 +
>>>>  drivers/hwmon/isl29003.c     |  517 ++++++++++++++++++++++++++++++++++++++++++
>>>>  4 files changed, 590 insertions(+), 0 deletions(-)
>>>>  create mode 100644 Documentation/hwmon/isl29003
>>>>  create mode 100644 drivers/hwmon/isl29003.c
>>> Still no luck, sorry. Light sensors have nothing to do with hardware
>>> monitoring.
>> Sorry to annoy you again with that topic, but I'm a little stuck here.
>> The 'industrial I/O' thread I was pointed to seems to be orphaned and I
>> didn't get much response on the LKML about alternatives either.
> 
> Sorry for the long silence, I have been pretty busy these last days,
> and was also hopping for a comment from Jonathan. The industrial I/O
> subsystem isn't orphaned, I am fairly certain it will be merged some
> day, but it could take some time.
Sorry, I missed this thread entirely until just now.

Jean is indeed correct that things have gotten a little delayed.  The
principal problem has been lack of interested users / testers / driver
developers.
Basically not enough people had relevant hardware.  Things are improving
so hopefully we can make some progress on this soon.  In the meantime
I've mostly just been fixing bugs my local users have been submitting
rather than doing any major new development.

I have a similar light sensor driver which I'll admit I've not widely
distributed at yet (it's in one of my git trees, imote2-linux, but under
the soon to disappear i2c-chips oops) This one has been out of mainline
for a while. It's for the tsl2561.

> 
>> As hwmon still appears the best existing place for such a driver to live
>> in, could we just take it and move it somewhere else as soon somebody
>> comes along with some better idea? I'd just like to avoid it being lost,
>> and every other approach does not feel very productive currently.
> 
> I agree that it doesn't make sense to wait for the industrial I/O
> subsystem to be merged. But I also do not want to put in drivers/hwmon
> drivers which do not really belong there. So I'd either go the way you
> proposed already (drivers/sensors for non-hwmon sensor drivers) or
> simply put your driver in drivers/misc if this is only meant as a
> temporary home until the industrial I/O subsystem is merged.
Jean's suggestion of drivers/misc may make sense in the short term or
I'm happy to do the minor bit of porting needed to merge this (and my tsl
driver) into the industrial i/o tree (held on kernel.org) to ensure it
doesn't disappear into the ether!

---
Jonathan Cameron

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                     ` <20090306182432.338b0244-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
  2009-03-06 18:09                       ` Jonathan Cameron
@ 2009-03-06 18:14                       ` Daniel Mack
       [not found]                         ` <20090306181409.GA20135-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
  1 sibling, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-03-06 18:14 UTC (permalink / raw)
  To: Jean Delvare; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Fri, Mar 06, 2009 at 06:24:32PM +0100, Jean Delvare wrote:
> > Sorry to annoy you again with that topic, but I'm a little stuck here.
> > The 'industrial I/O' thread I was pointed to seems to be orphaned and I
> > didn't get much response on the LKML about alternatives either.
> 
> Sorry for the long silence, I have been pretty busy these last days,
> and was also hopping for a comment from Jonathan. The industrial I/O
> subsystem isn't orphaned, I am fairly certain it will be merged some
> day, but it could take some time.

Haven't seen any follow-up on it, hence the doubt.


> > As hwmon still appears the best existing place for such a driver to live
> > in, could we just take it and move it somewhere else as soon somebody
> > comes along with some better idea? I'd just like to avoid it being lost,
> > and every other approach does not feel very productive currently.
> 
> I agree that it doesn't make sense to wait for the industrial I/O
> subsystem to be merged. But I also do not want to put in drivers/hwmon
> drivers which do not really belong there. So I'd either go the way you
> proposed already (drivers/sensors for non-hwmon sensor drivers) or
> simply put your driver in drivers/misc if this is only meant as a
> temporary home until the industrial I/O subsystem is merged.

Okay, drviers/misc sounds like a good intermediate solution. Do you want
me to post another patch for that or will you move it yourself?

Thanks,
Daniel

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                         ` <49B166E2.5060909-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
@ 2009-03-06 18:54                           ` Daniel Mack
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel Mack @ 2009-03-06 18:54 UTC (permalink / raw)
  To: Jonathan Cameron; +Cc: Jean Delvare, linux-i2c-u79uwXL29TY76Z2rM5mHXA

On Fri, Mar 06, 2009 at 06:09:38PM +0000, Jonathan Cameron wrote:
> > Sorry for the long silence, I have been pretty busy these last days,
> > and was also hopping for a comment from Jonathan. The industrial I/O
> > subsystem isn't orphaned, I am fairly certain it will be merged some
> > day, but it could take some time.
> Sorry, I missed this thread entirely until just now.
> 
> Jean is indeed correct that things have gotten a little delayed.  The
> principal problem has been lack of interested users / testers / driver
> developers.
> Basically not enough people had relevant hardware.  Things are improving
> so hopefully we can make some progress on this soon.  In the meantime
> I've mostly just been fixing bugs my local users have been submitting
> rather than doing any major new development.
> 
> I have a similar light sensor driver which I'll admit I've not widely
> distributed at yet (it's in one of my git trees, imote2-linux, but under
> the soon to disappear i2c-chips oops) This one has been out of mainline
> for a while. It's for the tsl2561.

And there is more in drivers/i2c/chips/ which should also be moved then
(my last patch did so and put it into the proposed drivers/sensors).

> > I agree that it doesn't make sense to wait for the industrial I/O
> > subsystem to be merged. But I also do not want to put in drivers/hwmon
> > drivers which do not really belong there. So I'd either go the way you
> > proposed already (drivers/sensors for non-hwmon sensor drivers) or
> > simply put your driver in drivers/misc if this is only meant as a
> > temporary home until the industrial I/O subsystem is merged.
> Jean's suggestion of drivers/misc may make sense in the short term or
> I'm happy to do the minor bit of porting needed to merge this (and my tsl
> driver) into the industrial i/o tree (held on kernel.org) to ensure it
> doesn't disappear into the ether!

Ok, perfect, do it so. And Cc: me when things get merged :)

Thanks,
Daniel

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                         ` <20090306181409.GA20135-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
@ 2009-03-07 10:54                           ` Jean Delvare
       [not found]                             ` <20090307115449.32f0ab26-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Jean Delvare @ 2009-03-07 10:54 UTC (permalink / raw)
  To: Daniel Mack; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Fri, 6 Mar 2009 19:14:09 +0100, Daniel Mack wrote:
> On Fri, Mar 06, 2009 at 06:24:32PM +0100, Jean Delvare wrote:
> > I agree that it doesn't make sense to wait for the industrial I/O
> > subsystem to be merged. But I also do not want to put in drivers/hwmon
> > drivers which do not really belong there. So I'd either go the way you
> > proposed already (drivers/sensors for non-hwmon sensor drivers) or
> > simply put your driver in drivers/misc if this is only meant as a
> > temporary home until the industrial I/O subsystem is merged.
> 
> Okay, drviers/misc sounds like a good intermediate solution. Do you want
> me to post another patch for that or will you move it yourself?

Please post new patches, I'll review and apply them.

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                             ` <20090307115449.32f0ab26-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
@ 2009-03-07 12:06                               ` Daniel Mack
       [not found]                                 ` <20090307120631.GA25276-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-03-07 12:06 UTC (permalink / raw)
  To: Jean Delvare; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

Hi Jean,

On Sat, Mar 07, 2009 at 11:54:49AM +0100, Jean Delvare wrote:
> > On Fri, Mar 06, 2009 at 06:24:32PM +0100, Jean Delvare wrote:
> > > I agree that it doesn't make sense to wait for the industrial I/O
> > > subsystem to be merged. But I also do not want to put in drivers/hwmon
> > > drivers which do not really belong there. So I'd either go the way you
> > > proposed already (drivers/sensors for non-hwmon sensor drivers) or
> > > simply put your driver in drivers/misc if this is only meant as a
> > > temporary home until the industrial I/O subsystem is merged.
> > 
> > Okay, drviers/misc sounds like a good intermediate solution. Do you want
> > me to post another patch for that or will you move it yourself?
> 
> Please post new patches, I'll review and apply them.

Oh, I thought we agreed now to Jonathan Cameron's idea to merge this
driver into the IIO framework and put it mainline sooner or later? No
extra steps needed from my side - this driver certainly doesn't hurry :)

Thanks,
Daniel

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                                 ` <20090307120631.GA25276-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
@ 2009-03-07 12:08                                   ` Jean Delvare
       [not found]                                     ` <20090307130818.367bcaad-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Jean Delvare @ 2009-03-07 12:08 UTC (permalink / raw)
  To: Daniel Mack; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Sat, 7 Mar 2009 13:06:31 +0100, Daniel Mack wrote:
> Hi Jean,
> 
> On Sat, Mar 07, 2009 at 11:54:49AM +0100, Jean Delvare wrote:
> > > On Fri, Mar 06, 2009 at 06:24:32PM +0100, Jean Delvare wrote:
> > > > I agree that it doesn't make sense to wait for the industrial I/O
> > > > subsystem to be merged. But I also do not want to put in drivers/hwmon
> > > > drivers which do not really belong there. So I'd either go the way you
> > > > proposed already (drivers/sensors for non-hwmon sensor drivers) or
> > > > simply put your driver in drivers/misc if this is only meant as a
> > > > temporary home until the industrial I/O subsystem is merged.
> > > 
> > > Okay, drviers/misc sounds like a good intermediate solution. Do you want
> > > me to post another patch for that or will you move it yourself?
> > 
> > Please post new patches, I'll review and apply them.
> 
> Oh, I thought we agreed now to Jonathan Cameron's idea to merge this
> driver into the IIO framework and put it mainline sooner or later? No
> extra steps needed from my side - this driver certainly doesn't hurry :)

Both ways are equally fine with me.

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                                     ` <20090307130818.367bcaad-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
@ 2009-03-09 21:09                                       ` Daniel Mack
       [not found]                                         ` <20090309210901.GC31367-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-03-09 21:09 UTC (permalink / raw)
  To: Jean Delvare; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Sat, Mar 07, 2009 at 01:08:18PM +0100, Jean Delvare wrote:
> > > Please post new patches, I'll review and apply them.
> > 
> > Oh, I thought we agreed now to Jonathan Cameron's idea to merge this
> > driver into the IIO framework and put it mainline sooner or later? No
> > extra steps needed from my side - this driver certainly doesn't hurry :)
> 
> Both ways are equally fine with me.

Ok, nevermind, just to be sure :) As soon as IIO is merged, I'll send a
patch to (re)move it again ...

Thanks,
Daniel


>From 48f05c0df089909928bf65a3cef29c5cff7ffb28 Mon Sep 17 00:00:00 2001
From: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
Date: Mon, 9 Mar 2009 22:02:23 +0100
Subject: [PATCH] Added driver for ISL29003 ambient light sensor

This patch adds 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 to drivers/misc for now until the industrial I/O framework
gets merged.

Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
Cc: Jean Delvare <khali-PUYAD+kWke1g9hUCZPvPmw@public.gmane.org>
Cc: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
---
 Documentation/i2c/chips/isl29003 |   62 +++++
 drivers/misc/Kconfig             |   10 +
 drivers/misc/Makefile            |    1 +
 drivers/misc/isl29003.c          |  486 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 559 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/i2c/chips/isl29003
 create mode 100644 drivers/misc/isl29003.c

diff --git a/Documentation/i2c/chips/isl29003 b/Documentation/i2c/chips/isl29003
new file mode 100644
index 0000000..c4ff5f3
--- /dev/null
+++ b/Documentation/i2c/chips/isl29003
@@ -0,0 +1,62 @@
+Kernel driver isl29003
+=====================
+
+Supported chips:
+* Intersil ISL29003
+Prefix: 'isl29003'
+Addresses scanned: none
+Datasheet:
+http://www.intersil.com/data/fn/fn7464.pdf
+
+Author: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
+
+
+Description
+-----------
+The ISL29003 is an integrated light sensor with a 16-bit integrating type
+ADC, I2C user programmable lux range select for optimized counts/lux, and
+I2C multi-function control and monitoring capabilities. The internal ADC
+provides 16-bit resolution while rejecting 50Hz and 60Hz flicker caused by
+artificial light sources.
+
+The driver allows to set the lux range, the bit resolution, the operational
+mode (see below) and the power state of device and can read the current lux
+value, of course.
+
+
+Detection
+---------
+
+The ISL29003 does not have an ID register which could be used to identify
+it, so the detection routine will just try to read from the configured I2C
+addess and consider the device to be present as soon as it ACKs the
+transfer.
+
+
+Sysfs entries
+-------------
+
+range:
+	0: 0 lux to 1000 lux (default)
+	1: 0 lux to 4000 lux
+	2: 0 lux to 16,000 lux
+	3: 0 lux to 64,000 lux
+
+resolution:
+	0: 2^16 cycles (default)
+	1: 2^12 cycles
+	2: 2^8 cycles
+	3: 2^4 cycles
+
+mode:
+	0: diode1's current (unsigned 16bit) (default)
+	1: diode1's current (unsigned 16bit)
+	2: difference between diodes (l1 - l2, signed 15bit)
+
+power_state:
+	0: device is disabled (default)
+	1: device is enabled
+
+lux (read only):
+	returns the value from the last sensor reading
+
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c64e679..b883b19 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -223,6 +223,16 @@ config DELL_LAPTOP
 	This driver adds support for rfkill and backlight control to Dell
 	laptops.
 
+config ISL29003
+	tristate "Intersil ISL29003 ambient light sensor"
+	depends on I2C
+	help
+	  If you say yes here you get support for the Intersil ISL29003
+	  ambient light sensor.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called isl29003.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index bc11998..7871f05 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -18,5 +18,6 @@ obj-$(CONFIG_KGDB_TESTS)	+= kgdbts.o
 obj-$(CONFIG_SGI_XP)		+= sgi-xp/
 obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
 obj-$(CONFIG_HP_ILO)		+= hpilo.o
+obj-$(CONFIG_ISL29003)		+= isl29003.o
 obj-$(CONFIG_C2PORT)		+= c2port/
 obj-y				+= eeprom/
diff --git a/drivers/misc/isl29003.c b/drivers/misc/isl29003.c
new file mode 100644
index 0000000..a17e89b
--- /dev/null
+++ b/drivers/misc/isl29003.c
@@ -0,0 +1,486 @@
+/*
+ *  isl29003.c - Linux kernel module for
+ * 	Intersil ISL29003 ambient light sensor
+ *
+ *  See file:Documentation/i2c/chips/isl29003
+ *
+ *  Copyright (c) 2009 Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
+ *
+ *  Based on code written by
+ *  	Rodolfo Giometti <giometti-k2GhghHVRtY@public.gmane.org>
+ *  	Eurotech S.p.A. <info-ymFC7zkAqBI1GQ1Ptb7lUw@public.gmane.org>
+ *
+ *  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>
+#include <linux/delay.h>
+
+#define ISL29003_DRV_NAME	"isl29003"
+#define DRIVER_VERSION		"1.0"
+
+#define ISL29003_REG_COMMAND		0x00
+#define ISL29003_ADC_ENABLED		(1 << 7)
+#define ISL29003_ADC_PD			(1 << 6)
+#define ISL29003_TIMING_INT		(1 << 5)
+#define ISL29003_MODE_SHIFT		(2)
+#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
+#define ISL29003_RES_SHIFT		(0)
+#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)
+
+#define ISL29003_REG_CONTROL		0x01
+#define ISL29003_INT_FLG		(1 << 5)
+#define ISL29003_RANGE_SHIFT		(2)
+#define ISL29003_RANGE_MASK		(0x3 << ISL29003_RANGE_SHIFT)
+#define ISL29003_INT_PERSISTS_SHIFT	(0)
+#define ISL29003_INT_PERSISTS_MASK	(0xf << ISL29003_INT_PERSISTS_SHIFT)
+
+#define ISL29003_REG_IRQ_THRESH_HI	0x02
+#define ISL29003_REG_IRQ_THRESH_LO	0x03
+#define ISL29003_REG_LSB_SENSOR		0x04
+#define ISL29003_REG_MSB_SENSOR		0x05
+#define ISL29003_REG_LSB_TIMER		0x06
+#define ISL29003_REG_MSB_TIMER		0x07
+
+struct isl29003_data {
+	struct i2c_client *client;
+	struct mutex lock;
+	u8 reg_cache[8];
+};
+
+static int gain_range[] = {
+	1000, 4000, 16000, 64000
+};
+
+/*
+ * register access helpers
+ */
+
+static int __isl29003_read_reg(struct i2c_client *client,
+			       u32 reg, u8 mask, u8 shift)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	return (data->reg_cache[reg] & mask) >> shift;
+}
+
+static int __isl29003_write_reg(struct i2c_client *client,
+				u32 reg, u8 mask, u8 shift, u8 val)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int ret = 0;
+	u8 tmp;
+
+	mutex_lock(&data->lock);
+
+	tmp = data->reg_cache[reg];
+	tmp &= ~mask;
+	tmp |= val << shift;
+
+	ret = i2c_smbus_write_byte_data(client, reg, tmp);
+	if (ret >= 0)
+		data->reg_cache[reg] = tmp;
+
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+/*
+ * internally used functions
+ */
+
+/* range */
+static int isl29003_get_range(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT);
+}
+
+static int isl29003_set_range(struct i2c_client *client, int range)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT, range);
+}
+
+/* resolution */
+static int isl29003_get_resolution(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_resolution(struct i2c_client *client, int res)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, res);
+}
+
+/* mode */
+static int isl29003_get_mode(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_mode(struct i2c_client *client, int mode)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, mode);
+}
+
+/* power_state */
+static int isl29003_set_power_state(struct i2c_client *client, int state)
+{
+	int ret;
+
+	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_ADC_ENABLED, 0, state);
+	if (ret < 0)
+		return ret;
+
+	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_ADC_PD, 0, ~state);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int isl29003_get_power_state(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
+	return (cmdreg & ISL29003_ADC_ENABLED) && (~cmdreg & ISL29003_ADC_PD);
+}
+
+static int isl29003_get_adc_value(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int lsb, msb, range, bitdepth;
+
+	mutex_lock(&data->lock);
+	lsb = i2c_smbus_read_byte_data(client, ISL29003_REG_LSB_SENSOR);
+
+	if (lsb < 0) {
+		mutex_unlock(&data->lock);
+		return lsb;
+	}
+
+	msb = i2c_smbus_read_byte_data(client, ISL29003_REG_MSB_SENSOR);
+	mutex_unlock(&data->lock);
+
+	if (msb < 0)
+		return msb;
+
+	range = isl29003_get_range(client);
+	bitdepth = (4 - isl29003_get_resolution(client)) * 4;
+	return (((msb << 8) | lsb) * gain_range[range]) >> bitdepth;
+}
+
+/*
+ * sysfs layer
+ */
+
+/* range */
+static ssize_t isl29003_show_range(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_range(client));
+}
+
+static ssize_t isl29003_store_range(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	ret = isl29003_set_range(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
+		   isl29003_show_range, isl29003_store_range);
+
+
+/* resolution */
+static ssize_t isl29003_show_resolution(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_resolution(client));
+}
+
+static ssize_t isl29003_store_resolution(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	ret = isl29003_set_resolution(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(resolution, S_IWUSR | S_IRUGO,
+		   isl29003_show_resolution, isl29003_store_resolution);
+
+/* mode */
+static ssize_t isl29003_show_mode(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_mode(client));
+}
+
+static ssize_t isl29003_store_mode(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 2))
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	ret = isl29003_set_mode(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
+		   isl29003_show_mode, isl29003_store_mode);
+
+
+/* power state */
+static ssize_t isl29003_show_power_state(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_power_state(client));
+}
+
+static ssize_t isl29003_store_power_state(struct device *dev,
+					  struct device_attribute *attr,
+					  const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
+		return -EINVAL;
+
+	ret = isl29003_set_power_state(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO,
+		   isl29003_show_power_state, isl29003_store_power_state);
+
+
+/* lux */
+static ssize_t isl29003_show_lux(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	/* No LUX data if not operational */
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	return sprintf(buf, "%d\n", isl29003_get_adc_value(client));
+}
+
+static DEVICE_ATTR(lux, S_IRUGO, isl29003_show_lux, NULL);
+
+static struct attribute *isl29003_attributes[] = {
+	&dev_attr_range.attr,
+	&dev_attr_resolution.attr,
+	&dev_attr_mode.attr,
+	&dev_attr_power_state.attr,
+	&dev_attr_lux.attr,
+	NULL
+};
+
+static const struct attribute_group isl29003_attr_group = {
+	.attrs = isl29003_attributes,
+};
+
+static int isl29003_init_client(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int i;
+
+	/* read all the registers once to fill the cache.
+	 * if one of the reads fails, we consider the init failed */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++) {
+		int v = i2c_smbus_read_byte_data(client, i);
+		if (v < 0)
+			return -ENODEV;
+
+		data->reg_cache[i] = v;
+	}
+
+	/* set defaults */
+	isl29003_set_range(client, 0);
+	isl29003_set_resolution(client, 0);
+	isl29003_set_mode(client, 0);
+	isl29003_set_power_state(client, 0);
+
+	return 0;
+}
+
+/*
+ * I2C layer
+ */
+
+static int __devinit isl29003_probe(struct i2c_client *client,
+				    const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct isl29003_data *data;
+	int err = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+		return -EIO;
+
+	data = kzalloc(sizeof(struct isl29003_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->client = client;
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->lock);
+
+	/* initialize the ISL29003 chip */
+	err = isl29003_init_client(client);
+	if (err)
+		goto exit_kfree;
+
+	/* register sysfs hooks */
+	err = sysfs_create_group(&client->dev.kobj, &isl29003_attr_group);
+	if (err)
+		goto exit_kfree;
+
+	dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);
+	return 0;
+
+exit_kfree:
+	kfree(data);
+	return err;
+}
+
+static int __devexit isl29003_remove(struct i2c_client *client)
+{
+	sysfs_remove_group(&client->dev.kobj, &isl29003_attr_group);
+	isl29003_set_power_state(client, 0);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int isl29003_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	return isl29003_set_power_state(client, 0);
+}
+
+static int isl29003_resume(struct i2c_client *client)
+{
+	int i;
+
+	/* restore registers from cache */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
+		if (i2c_smbus_read_byte_data(client, i, data->reg_cache[i]) < 0)
+			return -ENODEV;
+
+	return 0;
+}
+
+#else
+#define isl29003_suspend	NULL
+#define isl29003_resume		NULL
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id isl29003_id[] = {
+	{ "isl29003", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, isl29003_id);
+
+static struct i2c_driver isl29003_driver = {
+	.driver = {
+		.name	= ISL29003_DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+	.suspend = isl29003_suspend,
+	.resume	= isl29003_resume,
+	.probe	= isl29003_probe,
+	.remove	= __devexit_p(isl29003_remove),
+	.id_table = isl29003_id,
+};
+
+static int __init isl29003_init(void)
+{
+	return i2c_add_driver(&isl29003_driver);
+}
+
+static void __exit isl29003_exit(void)
+{
+	i2c_del_driver(&isl29003_driver);
+}
+
+MODULE_AUTHOR("Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>");
+MODULE_DESCRIPTION("ISL29003 ambient light sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(isl29003_init);
+module_exit(isl29003_exit);
+
-- 
1.6.1.3

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                                         ` <20090309210901.GC31367-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
@ 2009-03-10  8:56                                           ` Jean Delvare
       [not found]                                             ` <20090310095655.3850f0d5-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
  2009-03-10 13:11                                           ` Jonathan Cameron
  1 sibling, 1 reply; 49+ messages in thread
From: Jean Delvare @ 2009-03-10  8:56 UTC (permalink / raw)
  To: Daniel Mack; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Mon, 9 Mar 2009 22:09:01 +0100, Daniel Mack wrote:
> On Sat, Mar 07, 2009 at 01:08:18PM +0100, Jean Delvare wrote:
> > > > Please post new patches, I'll review and apply them.
> > > 
> > > Oh, I thought we agreed now to Jonathan Cameron's idea to merge this
> > > driver into the IIO framework and put it mainline sooner or later? No
> > > extra steps needed from my side - this driver certainly doesn't hurry :)
> > 
> > Both ways are equally fine with me.
> 
> Ok, nevermind, just to be sure :) As soon as IIO is merged, I'll send a
> patch to (re)move it again ...
> 
> Thanks,
> Daniel
> 
> 
> From 48f05c0df089909928bf65a3cef29c5cff7ffb28 Mon Sep 17 00:00:00 2001
> From: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> Date: Mon, 9 Mar 2009 22:02:23 +0100
> Subject: [PATCH] Added driver for ISL29003 ambient light sensor
> 
> This patch adds 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 to drivers/misc for now until the industrial I/O framework
> gets merged.
> 
> Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> Cc: Jean Delvare <khali-PUYAD+kWke1g9hUCZPvPmw@public.gmane.org>
> Cc: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
> ---
>  Documentation/i2c/chips/isl29003 |   62 +++++

Err, obviously not. If drivers/i2c/chips is going away, you can easily
imagine that Documentation/i2c/chips is going away as well...

>  drivers/misc/Kconfig             |   10 +
>  drivers/misc/Makefile            |    1 +
>  drivers/misc/isl29003.c          |  486 ++++++++++++++++++++++++++++++++++++++
>  4 files changed, 559 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/i2c/chips/isl29003
>  create mode 100644 drivers/misc/isl29003.c

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                                             ` <20090310095655.3850f0d5-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
@ 2009-03-10  9:00                                               ` Daniel Mack
       [not found]                                                 ` <20090310090007.GB3263-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-03-10  9:00 UTC (permalink / raw)
  To: Jean Delvare; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Tue, Mar 10, 2009 at 09:56:55AM +0100, Jean Delvare wrote:
> >  Documentation/i2c/chips/isl29003 |   62 +++++
> 
> Err, obviously not. If drivers/i2c/chips is going away, you can easily
> imagine that Documentation/i2c/chips is going away as well...

Agreed. I didn't want to add Documentation/misc just for that single
file, so where would you put it?

(Sorry for causing so much traffic for such a small, dumb driver)

Daniel

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                                                 ` <20090310090007.GB3263-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
@ 2009-03-10 10:10                                                   ` Jean Delvare
       [not found]                                                     ` <20090310111014.523df33e-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Jean Delvare @ 2009-03-10 10:10 UTC (permalink / raw)
  To: Daniel Mack; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Tue, 10 Mar 2009 10:00:07 +0100, Daniel Mack wrote:
> On Tue, Mar 10, 2009 at 09:56:55AM +0100, Jean Delvare wrote:
> > >  Documentation/i2c/chips/isl29003 |   62 +++++
> > 
> > Err, obviously not. If drivers/i2c/chips is going away, you can easily
> > imagine that Documentation/i2c/chips is going away as well...
> 
> Agreed. I didn't want to add Documentation/misc just for that single
> file, so where would you put it?

Honestly, I'm not sure. But I wouldn't mind actually creating
Documentation/misc or similar for it.

> (Sorry for causing so much traffic for such a small, dumb driver)

Not your fault, we're in the middle of restructuring this part of the
driver tree...

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                                                     ` <20090310111014.523df33e-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
@ 2009-03-10 10:25                                                       ` Daniel Mack
       [not found]                                                         ` <20090310102534.GC9564-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-03-10 10:25 UTC (permalink / raw)
  To: Jean Delvare; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Tue, Mar 10, 2009 at 11:10:14AM +0100, Jean Delvare wrote:
> On Tue, 10 Mar 2009 10:00:07 +0100, Daniel Mack wrote:
> > On Tue, Mar 10, 2009 at 09:56:55AM +0100, Jean Delvare wrote:
> > > >  Documentation/i2c/chips/isl29003 |   62 +++++
> > > 
> > > Err, obviously not. If drivers/i2c/chips is going away, you can easily
> > > imagine that Documentation/i2c/chips is going away as well...
> > 
> > Agreed. I didn't want to add Documentation/misc just for that single
> > file, so where would you put it?
> 
> Honestly, I'm not sure. But I wouldn't mind actually creating
> Documentation/misc or similar for it.

Ok, will do. Anything else in the code you would like me to change?

Thanks,
Daniel

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                                                         ` <20090310102534.GC9564-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
@ 2009-03-10 10:40                                                           ` Jean Delvare
       [not found]                                                             ` <20090310114011.012d3fcc-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Jean Delvare @ 2009-03-10 10:40 UTC (permalink / raw)
  To: Daniel Mack; +Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

On Tue, 10 Mar 2009 11:25:34 +0100, Daniel Mack wrote:
> On Tue, Mar 10, 2009 at 11:10:14AM +0100, Jean Delvare wrote:
> > On Tue, 10 Mar 2009 10:00:07 +0100, Daniel Mack wrote:
> > > Agreed. I didn't want to add Documentation/misc just for that single
> > > file, so where would you put it?
> > 
> > Honestly, I'm not sure. But I wouldn't mind actually creating
> > Documentation/misc or similar for it.
> 
> Ok, will do. Anything else in the code you would like me to change?

I did not have the time to review it and will not have the time in a
near future, sorry.

There is a reason why I am trying to move these misc drivers outside of
my maintenance area... Which basically means that you are totally free,
and actually encouraged, to try to get your driver merged by another
maintainer if I am too slow for you.

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                                                             ` <20090310114011.012d3fcc-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
@ 2009-03-10 11:01                                                               ` Trilok Soni
       [not found]                                                                 ` <5d5443650903100401j35f13459kcc03adb7dc7e9297-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 49+ messages in thread
From: Trilok Soni @ 2009-03-10 11:01 UTC (permalink / raw)
  To: Jean Delvare
  Cc: Daniel Mack, linux-i2c-u79uwXL29TY76Z2rM5mHXA, Jonathan Cameron

Hi Daniel,

>
> I did not have the time to review it and will not have the time in a
> near future, sorry.
>
> There is a reason why I am trying to move these misc drivers outside of
> my maintenance area... Which basically means that you are totally free,
> and actually encouraged, to try to get your driver merged by another
> maintainer if I am too slow for you.
>

My suggestion would be get this driver in mainline through -mm tree
(cc akpm and linux-kernel) or Ben does the review and merge it from
his tree. It is better not to wait for IIO to get merged.

Please re-submit your patch to linux-kenel with creating
Documentation/misc directory.

-- 
---Trilok Soni
http://triloksoni.wordpress.com
http://www.linkedin.com/in/triloksoni

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                                                                 ` <5d5443650903100401j35f13459kcc03adb7dc7e9297-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2009-03-10 12:11                                                                   ` Jonathan Cameron
  0 siblings, 0 replies; 49+ messages in thread
From: Jonathan Cameron @ 2009-03-10 12:11 UTC (permalink / raw)
  To: Trilok Soni; +Cc: Jean Delvare, Daniel Mack, linux-i2c-u79uwXL29TY76Z2rM5mHXA

Trilok Soni wrote:
> Hi Daniel,
> 
>> I did not have the time to review it and will not have the time in a
>> near future, sorry.
>>
>> There is a reason why I am trying to move these misc drivers outside of
>> my maintenance area... Which basically means that you are totally free,
>> and actually encouraged, to try to get your driver merged by another
>> maintainer if I am too slow for you.
>>
> 
> My suggestion would be get this driver in mainline through -mm tree
> (cc akpm and linux-kernel) or Ben does the review and merge it from
> his tree. It is better not to wait for IIO to get merged.

Much as I'd like to argue IIO might go in soon, I suspect we'll still
have a bit of an uphill struggle.  As I've said I'll do the conversion
to IIO, I'll be reading your code thoroughly anyway, so I'm happy to
do a review of latest version you've posted.

Jonathan

> Please re-submit your patch to linux-kenel with creating
> Documentation/misc directory.
> 

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
       [not found]                                         ` <20090309210901.GC31367-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
  2009-03-10  8:56                                           ` Jean Delvare
@ 2009-03-10 13:11                                           ` Jonathan Cameron
  2009-03-10 15:15                                               ` Daniel Mack
  1 sibling, 1 reply; 49+ messages in thread
From: Jonathan Cameron @ 2009-03-10 13:11 UTC (permalink / raw)
  To: Daniel Mack; +Cc: Jean Delvare, linux-i2c-u79uwXL29TY76Z2rM5mHXA

Hi Daniel,

Some of the comments below are going to be more general than strictly
relevant to this driver.  More random musings than actual review ;)

I notice that this device is extremely similar to the ISL29004 where only
differences being with i2c address selection (that one has some pins to
allow more than one option).  Worth rolling support for that device in
here?

This device has some interesting interrupt / timing options which will fit
nicely in the IIO framework.  For now the driver sensibly ignores them
entirely. (if you don't need them, why bother?)

Most of the comments below are matters of personal preference (so feel free
to ignore me!) The two big ones I'd like answers to are:

* Why are you caching registers 4-7.  The are read only and dependent on last
reading so this is pointless.

* I'm confused by your resume code.  I would expect you to want to write back
to the device the values last seen in the control registers before the suspend
whereas you are reading the values there upon resume back into your cache.

Otherwise the driver is fine with me.

>>From 48f05c0df089909928bf65a3cef29c5cff7ffb28 Mon Sep 17 00:00:00 2001
> From: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> Date: Mon, 9 Mar 2009 22:02:23 +0100
> Subject: [PATCH] Added driver for ISL29003 ambient light sensor
> 
> This patch adds 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 to drivers/misc for now until the industrial I/O framework
> gets merged.
> 
> Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> Cc: Jean Delvare <khali-PUYAD+kWke1g9hUCZPvPmw@public.gmane.org>
> Cc: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
> ---
>  Documentation/i2c/chips/isl29003 |   62 +++++
>  drivers/misc/Kconfig             |   10 +
>  drivers/misc/Makefile            |    1 +
>  drivers/misc/isl29003.c          |  486 ++++++++++++++++++++++++++++++++++++++
>  4 files changed, 559 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/i2c/chips/isl29003
>  create mode 100644 drivers/misc/isl29003.c
> 
> diff --git a/Documentation/i2c/chips/isl29003 b/Documentation/i2c/chips/isl29003
> new file mode 100644
> index 0000000..c4ff5f3
> --- /dev/null
> +++ b/Documentation/i2c/chips/isl29003
> @@ -0,0 +1,62 @@
> +Kernel driver isl29003
> +=====================
> +
> +Supported chips:
> +* Intersil ISL29003
> +Prefix: 'isl29003'
> +Addresses scanned: none
> +Datasheet:
> +http://www.intersil.com/data/fn/fn7464.pdf
> +
> +Author: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> +
> +
> +Description
> +-----------
> +The ISL29003 is an integrated light sensor with a 16-bit integrating type
> +ADC, I2C user programmable lux range select for optimized counts/lux, and
> +I2C multi-function control and monitoring capabilities. The internal ADC
> +provides 16-bit resolution while rejecting 50Hz and 60Hz flicker caused by
> +artificial light sources.
> +
> +The driver allows to set the lux range, the bit resolution, the operational
> +mode (see below) and the power state of device and can read the current lux
> +value, of course.
> +
> +
> +Detection
> +---------
> +
> +The ISL29003 does not have an ID register which could be used to identify
> +it, so the detection routine will just try to read from the configured I2C
> +addess and consider the device to be present as soon as it ACKs the
> +transfer.
This is a little nasty given the chances of something else sitting on that
address, but not much else you can do.
> +
> +
> +Sysfs entries
> +-------------
> +
> +range:
> +	0: 0 lux to 1000 lux (default)
> +	1: 0 lux to 4000 lux
> +	2: 0 lux to 16,000 lux
> +	3: 0 lux to 64,000 lux
This is going to be awkward to control in a generalized fashion, but
the option here is fine for now.

> +resolution:
> +	0: 2^16 cycles (default)
> +	1: 2^12 cycles
> +	2: 2^8 cycles
> +	3: 2^4 cycles
> +
> +mode:
> +	0: diode1's current (unsigned 16bit) (default)
> +	1: diode1's current (unsigned 16bit)
> +	2: difference between diodes (l1 - l2, signed 15bit)
> +
> +power_state:
> +	0: device is disabled (default)
> +	1: device is enabled
> +
> +lux (read only):
> +	returns the value from the last sensor reading
> +
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index c64e679..b883b19 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -223,6 +223,16 @@ config DELL_LAPTOP
>  	This driver adds support for rfkill and backlight control to Dell
>  	laptops.
>  
> +config ISL29003
> +	tristate "Intersil ISL29003 ambient light sensor"
> +	depends on I2C
> +	help
> +	  If you say yes here you get support for the Intersil ISL29003
> +	  ambient light sensor.
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called isl29003.
> +
>  source "drivers/misc/c2port/Kconfig"
>  source "drivers/misc/eeprom/Kconfig"
>  
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index bc11998..7871f05 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -18,5 +18,6 @@ obj-$(CONFIG_KGDB_TESTS)	+= kgdbts.o
>  obj-$(CONFIG_SGI_XP)		+= sgi-xp/
>  obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
>  obj-$(CONFIG_HP_ILO)		+= hpilo.o
> +obj-$(CONFIG_ISL29003)		+= isl29003.o
>  obj-$(CONFIG_C2PORT)		+= c2port/
>  obj-y				+= eeprom/
> diff --git a/drivers/misc/isl29003.c b/drivers/misc/isl29003.c
> new file mode 100644
> index 0000000..a17e89b
> --- /dev/null
> +++ b/drivers/misc/isl29003.c
> @@ -0,0 +1,486 @@
> +/*
> + *  isl29003.c - Linux kernel module for
> + * 	Intersil ISL29003 ambient light sensor
> + *
> + *  See file:Documentation/i2c/chips/isl29003
> + *
> + *  Copyright (c) 2009 Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> + *
> + *  Based on code written by
> + *  	Rodolfo Giometti <giometti-k2GhghHVRtY@public.gmane.org>
> + *  	Eurotech S.p.A. <info-ymFC7zkAqBI1GQ1Ptb7lUw@public.gmane.org>
> + *
> + *  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>
> +#include <linux/delay.h>
> +
> +#define ISL29003_DRV_NAME	"isl29003"
> +#define DRIVER_VERSION		"1.0"

Some of the following are acting only as documentation. It's
a matter of personal preference whether you specify them.
> +#define ISL29003_REG_COMMAND		0x00
> +#define ISL29003_ADC_ENABLED		(1 << 7)
> +#define ISL29003_ADC_PD			(1 << 6)
> +#define ISL29003_TIMING_INT		(1 << 5)
> +#define ISL29003_MODE_SHIFT		(2)
> +#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
> +#define ISL29003_RES_SHIFT		(0)
> +#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)
> +
> +#define ISL29003_REG_CONTROL		0x01
> +#define ISL29003_INT_FLG		(1 << 5)
> +#define ISL29003_RANGE_SHIFT		(2)
> +#define ISL29003_RANGE_MASK		(0x3 << ISL29003_RANGE_SHIFT)
> +#define ISL29003_INT_PERSISTS_SHIFT	(0)
> +#define ISL29003_INT_PERSISTS_MASK	(0xf << ISL29003_INT_PERSISTS_SHIFT)
> +
> +#define ISL29003_REG_IRQ_THRESH_HI	0x02
> +#define ISL29003_REG_IRQ_THRESH_LO	0x03
> +#define ISL29003_REG_LSB_SENSOR		0x04
> +#define ISL29003_REG_MSB_SENSOR		0x05
> +#define ISL29003_REG_LSB_TIMER		0x06
> +#define ISL29003_REG_MSB_TIMER		0x07
> +
> +struct isl29003_data {
> +	struct i2c_client *client;
> +	struct mutex lock;
> +	u8 reg_cache[8];
> +};
> +
> +static int gain_range[] = {
> +	1000, 4000, 16000, 64000
> +};
> +
> +/*
> + * register access helpers
> + */
> +
> +static int __isl29003_read_reg(struct i2c_client *client,
> +			       u32 reg, u8 mask, u8 shift)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	return (data->reg_cache[reg] & mask) >> shift;
> +}
> +
> +static int __isl29003_write_reg(struct i2c_client *client,
> +				u32 reg, u8 mask, u8 shift, u8 val)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	int ret = 0;
> +	u8 tmp;
> +
> +	mutex_lock(&data->lock);
> +
> +	tmp = data->reg_cache[reg];
> +	tmp &= ~mask;
> +	tmp |= val << shift;
> +
> +	ret = i2c_smbus_write_byte_data(client, reg, tmp);
As i2c_smbus_write_byte_data is defined as returning zero on success
this could simply be, if (!ret).
> +	if (ret >= 0)
> +		data->reg_cache[reg] = tmp;
> +
> +	mutex_unlock(&data->lock);
> +	return ret;
> +}
> +
> +/*
> + * internally used functions
> + */
> +
> +/* range */
> +static int isl29003_get_range(struct i2c_client *client)
> +{
> +	return __isl29003_read_reg(client, ISL29003_REG_CONTROL,
> +		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT);
> +}
> +
> +static int isl29003_set_range(struct i2c_client *client, int range)
> +{
> +	return __isl29003_write_reg(client, ISL29003_REG_CONTROL,
> +		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT, range);
> +}
> +
> +/* resolution */
> +static int isl29003_get_resolution(struct i2c_client *client)
> +{
> +	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
> +}
> +
> +static int isl29003_set_resolution(struct i2c_client *client, int res)
> +{
> +	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_RES_MASK, ISL29003_RES_SHIFT, res);
> +}
> +
> +/* mode */
> +static int isl29003_get_mode(struct i2c_client *client)
> +{
> +	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
> +}
> +
> +static int isl29003_set_mode(struct i2c_client *client, int mode)
> +{
> +	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_RES_MASK, ISL29003_RES_SHIFT, mode);
> +}
> +
> +/* power_state */
> +static int isl29003_set_power_state(struct i2c_client *client, int state)
> +{
> +	int ret;
> +
> +	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_ADC_ENABLED, 0, state);
As this is either 0 or less than zero, again if (ret) will suffice.
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_ADC_PD, 0, ~state);
Just return ret hence losing these 2 lines.
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int isl29003_get_power_state(struct i2c_client *client)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
> +	return (cmdreg & ISL29003_ADC_ENABLED) && (~cmdreg & ISL29003_ADC_PD);
> +}
Unfortunately this chip has gain controlled by combination of the i2c
accessed registers and an external resitor.  I guess this falls
into the category of things to be fixed in userspace.  Perhaps
some documentation to indicate this issue would help?
(table 9 of data sheet)
> +static int isl29003_get_adc_value(struct i2c_client *client)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	int lsb, msb, range, bitdepth;
> +
> +	mutex_lock(&data->lock);
> +	lsb = i2c_smbus_read_byte_data(client, ISL29003_REG_LSB_SENSOR);
> +
> +	if (lsb < 0) {
> +		mutex_unlock(&data->lock);
> +		return lsb;
> +	}
> +
> +	msb = i2c_smbus_read_byte_data(client, ISL29003_REG_MSB_SENSOR);
> +	mutex_unlock(&data->lock);
> +
> +	if (msb < 0)
> +		return msb;
> +
> +	range = isl29003_get_range(client);
> +	bitdepth = (4 - isl29003_get_resolution(client)) * 4;
> +	return (((msb << 8) | lsb) * gain_range[range]) >> bitdepth;
> +}
> +
> +/*
> + * sysfs layer
> + */
> +
> +/* range */
> +static ssize_t isl29003_show_range(struct device *dev,
> +				   struct device_attribute *attr, char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	return sprintf(buf, "%i\n", isl29003_get_range(client));
> +}
> +
> +static ssize_t isl29003_store_range(struct device *dev,
> +				    struct device_attribute *attr,
> +				    const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	unsigned long val;
> +	int ret;
> +
> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
> +		return -EINVAL;
See as there are rather a lot of calls like this, why don't
you consider moving this test into the register write command.
If the device is powered up then it will get copied to the
device. If not, just store it in the cache and it will be
written on resume anyway (assuming my understanding of your
suspend resume code is right!)
> +	if (!isl29003_get_power_state(client))
> +		return -EBUSY;
> +
> +	ret = isl29003_set_range(client, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
> +		   isl29003_show_range, isl29003_store_range);
> +
> +
> +/* resolution */
> +static ssize_t isl29003_show_resolution(struct device *dev,
> +					struct device_attribute *attr,
> +					char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	return sprintf(buf, "%d\n", isl29003_get_resolution(client));
> +}
> +
> +static ssize_t isl29003_store_resolution(struct device *dev,
> +					 struct device_attribute *attr,
> +					 const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	unsigned long val;
> +	int ret;
> +
> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
> +		return -EINVAL;
> +
> +	if (!isl29003_get_power_state(client))
> +		return -EBUSY;
> +
> +	ret = isl29003_set_resolution(client, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR(resolution, S_IWUSR | S_IRUGO,
> +		   isl29003_show_resolution, isl29003_store_resolution);
> +
> +/* mode */
> +static ssize_t isl29003_show_mode(struct device *dev,
> +				  struct device_attribute *attr, char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	return sprintf(buf, "%d\n", isl29003_get_mode(client));
> +}
> +
> +static ssize_t isl29003_store_mode(struct device *dev,
> +		struct device_attribute *attr, const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	unsigned long val;
> +	int ret;
> +
> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 2))
> +		return -EINVAL;
> +
> +	if (!isl29003_get_power_state(client))
> +		return -EBUSY;
> +
> +	ret = isl29003_set_mode(client, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
> +		   isl29003_show_mode, isl29003_store_mode);
> +
> +
> +/* power state */
> +static ssize_t isl29003_show_power_state(struct device *dev,
> +					 struct device_attribute *attr,
> +					 char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	return sprintf(buf, "%d\n", isl29003_get_power_state(client));
> +}
> +
> +static ssize_t isl29003_store_power_state(struct device *dev,
> +					  struct device_attribute *attr,
> +					  const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	unsigned long val;
> +	int ret;
> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
> +		return -EINVAL;
> +
> +	ret = isl29003_set_power_state(client, val);
> +	if (ret < 0)
> +		return ret;
I'd go with more concise "return ret ? ret : count;" but thats down
to personal preference.
> +	return count;
> +}
> +
> +static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO,
> +		   isl29003_show_power_state, isl29003_store_power_state);
> +
> +
> +/* lux */
> +static ssize_t isl29003_show_lux(struct device *dev,
> +				 struct device_attribute *attr, char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +
> +	/* No LUX data if not operational */
> +	if (!isl29003_get_power_state(client))
> +		return -EBUSY;
> +
> +	return sprintf(buf, "%d\n", isl29003_get_adc_value(client));
> +}
> +
> +static DEVICE_ATTR(lux, S_IRUGO, isl29003_show_lux, NULL);
> +
> +static struct attribute *isl29003_attributes[] = {
> +	&dev_attr_range.attr,
> +	&dev_attr_resolution.attr,
> +	&dev_attr_mode.attr,
> +	&dev_attr_power_state.attr,
> +	&dev_attr_lux.attr,
> +	NULL
> +};
> +
> +static const struct attribute_group isl29003_attr_group = {
> +	.attrs = isl29003_attributes,
> +};
> +
> +static int isl29003_init_client(struct i2c_client *client)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	int i;
> +
> +	/* read all the registers once to fill the cache.
> +	 * if one of the reads fails, we consider the init failed */

Why are you caching registers 4-7?  They are read only data registers
and those you use are read on demand anyway.  It's not a problem here,
but worth noting that even the first 2 are not simply read / write
control registers and hence any caching method has to be very careful
  (there is a interrupt flag in control according to the data sheet.)
> +	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++) {
> +		int v = i2c_smbus_read_byte_data(client, i);
> +		if (v < 0)
> +			return -ENODEV;
> +
> +		data->reg_cache[i] = v;
> +	}
> +
> +	/* set defaults */
> +	isl29003_set_range(client, 0);
> +	isl29003_set_resolution(client, 0);
> +	isl29003_set_mode(client, 0);
> +	isl29003_set_power_state(client, 0);
> +
> +	return 0;
> +}
> +
> +/*
> + * I2C layer
> + */
> +
> +static int __devinit isl29003_probe(struct i2c_client *client,
> +				    const struct i2c_device_id *id)
> +{
> +	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
> +	struct isl29003_data *data;
> +	int err = 0;
> +
> +	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
> +		return -EIO;
> +
> +	data = kzalloc(sizeof(struct isl29003_data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	data->client = client;
> +	i2c_set_clientdata(client, data);
> +	mutex_init(&data->lock);
> +
> +	/* initialize the ISL29003 chip */
> +	err = isl29003_init_client(client);
> +	if (err)
> +		goto exit_kfree;
> +
> +	/* register sysfs hooks */
> +	err = sysfs_create_group(&client->dev.kobj, &isl29003_attr_group);
> +	if (err)
> +		goto exit_kfree;
> +
> +	dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);
> +	return 0;
> +
> +exit_kfree:
> +	kfree(data);
> +	return err;
> +}
> +
> +static int __devexit isl29003_remove(struct i2c_client *client)
> +{
> +	sysfs_remove_group(&client->dev.kobj, &isl29003_attr_group);
> +	isl29003_set_power_state(client, 0);
> +	kfree(i2c_get_clientdata(client));
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int isl29003_suspend(struct i2c_client *client, pm_message_t mesg)
> +{
> +	return isl29003_set_power_state(client, 0);
> +}
> +
> +static int isl29003_resume(struct i2c_client *client)
> +{
> +	int i;
> +
> +	/* restore registers from cache */

If we are restoring registers from cache, why are we reading them?

> +	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
> +		if (i2c_smbus_read_byte_data(client, i, data->reg_cache[i]) < 0)
> +			return -ENODEV;
> +
> +	return 0;
> +}
> +
> +#else
> +#define isl29003_suspend	NULL
> +#define isl29003_resume		NULL
> +#endif /* CONFIG_PM */
> +
> +static const struct i2c_device_id isl29003_id[] = {
> +	{ "isl29003", 0 },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(i2c, isl29003_id);
> +
> +static struct i2c_driver isl29003_driver = {
> +	.driver = {
> +		.name	= ISL29003_DRV_NAME,
> +		.owner	= THIS_MODULE,
> +	},
> +	.suspend = isl29003_suspend,
> +	.resume	= isl29003_resume,
> +	.probe	= isl29003_probe,
> +	.remove	= __devexit_p(isl29003_remove),
> +	.id_table = isl29003_id,
> +};
> +
> +static int __init isl29003_init(void)
> +{
> +	return i2c_add_driver(&isl29003_driver);
> +}
> +
> +static void __exit isl29003_exit(void)
> +{
> +	i2c_del_driver(&isl29003_driver);
> +}
> +
> +MODULE_AUTHOR("Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>");
> +MODULE_DESCRIPTION("ISL29003 ambient light sensor driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_VERSION(DRIVER_VERSION);
> +
> +module_init(isl29003_init);
> +module_exit(isl29003_exit);
> +

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-10 15:15                                               ` Daniel Mack
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel Mack @ 2009-03-10 15:15 UTC (permalink / raw)
  To: Jonathan Cameron; +Cc: Jean Delvare, linux-i2c, linux-kernel

Hi Jonathan,

many thanks for your review!

On Tue, Mar 10, 2009 at 01:11:21PM +0000, Jonathan Cameron wrote:
> I notice that this device is extremely similar to the ISL29004 where only
> differences being with i2c address selection (that one has some pins to
> allow more than one option).  Worth rolling support for that device in
> here?

Well, the I2C address isn't coded in the driver but defined in the
i2c_board_info array of the board support file, so there is no reason
why it shouldn't work. Or are you talking about the names of files and
functions that you would like to see reflecting this?

> This device has some interesting interrupt / timing options which will fit
> nicely in the IIO framework.  For now the driver sensibly ignores them
> entirely. (if you don't need them, why bother?)

Right, I don't need them personally, and things are not connected to
the CPU, so I can't test that. I would expect anyone who needs this
functions to add support to the code :)

> > +The ISL29003 does not have an ID register which could be used to identify
> > +it, so the detection routine will just try to read from the configured I2C
> > +addess and consider the device to be present as soon as it ACKs the
> > +transfer.
> This is a little nasty given the chances of something else sitting on that
> address, but not much else you can do.

True.

> Some of the following are acting only as documentation. It's
> a matter of personal preference whether you specify them.

I defined them to make future feature additions easy ...

> > +#define ISL29003_REG_COMMAND		0x00
> > +#define ISL29003_ADC_ENABLED		(1 << 7)
> > +#define ISL29003_ADC_PD			(1 << 6)
> > +#define ISL29003_TIMING_INT		(1 << 5)
> > +#define ISL29003_MODE_SHIFT		(2)
> > +#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
> > +#define ISL29003_RES_SHIFT		(0)
> > +#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)

[...]

> > +static int __isl29003_write_reg(struct i2c_client *client,
> > +				u32 reg, u8 mask, u8 shift, u8 val)
> > +{
> > +	struct isl29003_data *data = i2c_get_clientdata(client);
> > +	int ret = 0;
> > +	u8 tmp;
> > +
> > +	mutex_lock(&data->lock);
> > +
> > +	tmp = data->reg_cache[reg];
> > +	tmp &= ~mask;
> > +	tmp |= val << shift;
> > +
> > +	ret = i2c_smbus_write_byte_data(client, reg, tmp);
> As i2c_smbus_write_byte_data is defined as returning zero on success
> this could simply be, if (!ret).

Fixed.

> > +}
> > +
> > +/* power_state */
> > +static int isl29003_set_power_state(struct i2c_client *client, int state)
> > +{
> > +	int ret;
> > +
> > +	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> > +		ISL29003_ADC_ENABLED, 0, state);
> As this is either 0 or less than zero, again if (ret) will suffice.

Fixed.

> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> > +		ISL29003_ADC_PD, 0, ~state);
> Just return ret hence losing these 2 lines.

Fixed.

> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	return 0;
> > +}
> > +
> > +static int isl29003_get_power_state(struct i2c_client *client)
> > +{
> > +	struct isl29003_data *data = i2c_get_clientdata(client);
> > +	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
> > +	return (cmdreg & ISL29003_ADC_ENABLED) && (~cmdreg & ISL29003_ADC_PD);
> > +}
> Unfortunately this chip has gain controlled by combination of the i2c
> accessed registers and an external resitor.  I guess this falls
> into the category of things to be fixed in userspace.  Perhaps
> some documentation to indicate this issue would help?
> (table 9 of data sheet)

The resistor does not affect the value read from the register - it's
about integration time only. Or did I get it wrong?

> > +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
> > +		return -EINVAL;
> See as there are rather a lot of calls like this, why don't
> you consider moving this test into the register write command.
> If the device is powered up then it will get copied to the
> device. If not, just store it in the cache and it will be
> written on resume anyway (assuming my understanding of your
> suspend resume code is right!)

It's not even necessary to do that - the driver can access all registers
while the PD bit is set. So the only check is to not read sensor data
when this condition is matched.

> > +static ssize_t isl29003_store_power_state(struct device *dev,
> > +					  struct device_attribute *attr,
> > +					  const char *buf, size_t count)
> > +{
> > +	struct i2c_client *client = to_i2c_client(dev);
> > +	unsigned long val;
> > +	int ret;
> > +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
> > +		return -EINVAL;
> > +
> > +	ret = isl29003_set_power_state(client, val);
> > +	if (ret < 0)
> > +		return ret;
> I'd go with more concise "return ret ? ret : count;" but thats down
> to personal preference.

Fixed.

> > +	/* read all the registers once to fill the cache.
> > +	 * if one of the reads fails, we consider the init failed */
> 
> Why are you caching registers 4-7?  They are read only data registers
> and those you use are read on demand anyway.  It's not a problem here,
> but worth noting that even the first 2 are not simply read / write
> control registers and hence any caching method has to be very careful
>   (there is a interrupt flag in control according to the data sheet.)

You're right. I changed the cache to only store the first 4 registers
for now. Interrupt handling will need some extra work anyway, so I'll
leave that for now.

> If we are restoring registers from cache, why are we reading them?
> 
> > +	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
> > +		if (i2c_smbus_read_byte_data(client, i, data->reg_cache[i]) < 0)
> > +			return -ENODEV;

Typo - couldn't test the suspend/resume code yet. Fixed now.

Please see the patch below.

Thanks again,
Daniel


>From 8ee5021834900f312ff26cd2586c18e99af31a5d Mon Sep 17 00:00:00 2001
From: Daniel Mack <daniel@caiaq.de>
Date: Tue, 10 Mar 2009 16:13:07 +0100
Subject: [PATCH] Added driver for ISL29003 ambient light sensor

This patch adds 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 to drivers/misc for now until the industrial I/O framework
gets merged.

Signed-off-by: Daniel Mack <daniel@caiaq.de>
---
 New version with Jonathan Cameron's review notes addressed.

 Documentation/misc-devices/isl29003 |   62 +++++
 drivers/misc/Kconfig                |   10 +
 drivers/misc/Makefile               |    1 +
 drivers/misc/isl29003.c             |  470 +++++++++++++++++++++++++++++++++++
 4 files changed, 543 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/misc-devices/isl29003
 create mode 100644 drivers/misc/isl29003.c

diff --git a/Documentation/misc-devices/isl29003 b/Documentation/misc-devices/isl29003
new file mode 100644
index 0000000..c4ff5f3
--- /dev/null
+++ b/Documentation/misc-devices/isl29003
@@ -0,0 +1,62 @@
+Kernel driver isl29003
+=====================
+
+Supported chips:
+* Intersil ISL29003
+Prefix: 'isl29003'
+Addresses scanned: none
+Datasheet:
+http://www.intersil.com/data/fn/fn7464.pdf
+
+Author: Daniel Mack <daniel@caiaq.de>
+
+
+Description
+-----------
+The ISL29003 is an integrated light sensor with a 16-bit integrating type
+ADC, I2C user programmable lux range select for optimized counts/lux, and
+I2C multi-function control and monitoring capabilities. The internal ADC
+provides 16-bit resolution while rejecting 50Hz and 60Hz flicker caused by
+artificial light sources.
+
+The driver allows to set the lux range, the bit resolution, the operational
+mode (see below) and the power state of device and can read the current lux
+value, of course.
+
+
+Detection
+---------
+
+The ISL29003 does not have an ID register which could be used to identify
+it, so the detection routine will just try to read from the configured I2C
+addess and consider the device to be present as soon as it ACKs the
+transfer.
+
+
+Sysfs entries
+-------------
+
+range:
+	0: 0 lux to 1000 lux (default)
+	1: 0 lux to 4000 lux
+	2: 0 lux to 16,000 lux
+	3: 0 lux to 64,000 lux
+
+resolution:
+	0: 2^16 cycles (default)
+	1: 2^12 cycles
+	2: 2^8 cycles
+	3: 2^4 cycles
+
+mode:
+	0: diode1's current (unsigned 16bit) (default)
+	1: diode1's current (unsigned 16bit)
+	2: difference between diodes (l1 - l2, signed 15bit)
+
+power_state:
+	0: device is disabled (default)
+	1: device is enabled
+
+lux (read only):
+	returns the value from the last sensor reading
+
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c64e679..b883b19 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -223,6 +223,16 @@ config DELL_LAPTOP
 	This driver adds support for rfkill and backlight control to Dell
 	laptops.
 
+config ISL29003
+	tristate "Intersil ISL29003 ambient light sensor"
+	depends on I2C
+	help
+	  If you say yes here you get support for the Intersil ISL29003
+	  ambient light sensor.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called isl29003.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index bc11998..7871f05 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -18,5 +18,6 @@ obj-$(CONFIG_KGDB_TESTS)	+= kgdbts.o
 obj-$(CONFIG_SGI_XP)		+= sgi-xp/
 obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
 obj-$(CONFIG_HP_ILO)		+= hpilo.o
+obj-$(CONFIG_ISL29003)		+= isl29003.o
 obj-$(CONFIG_C2PORT)		+= c2port/
 obj-y				+= eeprom/
diff --git a/drivers/misc/isl29003.c b/drivers/misc/isl29003.c
new file mode 100644
index 0000000..2e2a592
--- /dev/null
+++ b/drivers/misc/isl29003.c
@@ -0,0 +1,470 @@
+/*
+ *  isl29003.c - Linux kernel module for
+ * 	Intersil ISL29003 ambient light sensor
+ *
+ *  See file:Documentation/misc-devices/isl29003
+ *
+ *  Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
+ *
+ *  Based on code written by
+ *  	Rodolfo Giometti <giometti@linux.it>
+ *  	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>
+#include <linux/delay.h>
+
+#define ISL29003_DRV_NAME	"isl29003"
+#define DRIVER_VERSION		"1.0"
+
+#define ISL29003_REG_COMMAND		0x00
+#define ISL29003_ADC_ENABLED		(1 << 7)
+#define ISL29003_ADC_PD			(1 << 6)
+#define ISL29003_TIMING_INT		(1 << 5)
+#define ISL29003_MODE_SHIFT		(2)
+#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
+#define ISL29003_RES_SHIFT		(0)
+#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)
+
+#define ISL29003_REG_CONTROL		0x01
+#define ISL29003_INT_FLG		(1 << 5)
+#define ISL29003_RANGE_SHIFT		(2)
+#define ISL29003_RANGE_MASK		(0x3 << ISL29003_RANGE_SHIFT)
+#define ISL29003_INT_PERSISTS_SHIFT	(0)
+#define ISL29003_INT_PERSISTS_MASK	(0xf << ISL29003_INT_PERSISTS_SHIFT)
+
+#define ISL29003_REG_IRQ_THRESH_HI	0x02
+#define ISL29003_REG_IRQ_THRESH_LO	0x03
+#define ISL29003_REG_LSB_SENSOR		0x04
+#define ISL29003_REG_MSB_SENSOR		0x05
+#define ISL29003_REG_LSB_TIMER		0x06
+#define ISL29003_REG_MSB_TIMER		0x07
+
+#define ISL29003_NUM_CACHABLE_REGS	4
+
+struct isl29003_data {
+	struct i2c_client *client;
+	struct mutex lock;
+	u8 reg_cache[ISL29003_NUM_CACHABLE_REGS];
+};
+
+static int gain_range[] = {
+	1000, 4000, 16000, 64000
+};
+
+/*
+ * register access helpers
+ */
+
+static int __isl29003_read_reg(struct i2c_client *client,
+			       u32 reg, u8 mask, u8 shift)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	return (data->reg_cache[reg] & mask) >> shift;
+}
+
+static int __isl29003_write_reg(struct i2c_client *client,
+				u32 reg, u8 mask, u8 shift, u8 val)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int ret = 0;
+	u8 tmp;
+
+	if (reg >= ISL29003_NUM_CACHABLE_REGS)
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+
+	tmp = data->reg_cache[reg];
+	tmp &= ~mask;
+	tmp |= val << shift;
+
+	ret = i2c_smbus_write_byte_data(client, reg, tmp);
+	if (!ret)
+		data->reg_cache[reg] = tmp;
+
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+/*
+ * internally used functions
+ */
+
+/* range */
+static int isl29003_get_range(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT);
+}
+
+static int isl29003_set_range(struct i2c_client *client, int range)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT, range);
+}
+
+/* resolution */
+static int isl29003_get_resolution(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_resolution(struct i2c_client *client, int res)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, res);
+}
+
+/* mode */
+static int isl29003_get_mode(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_mode(struct i2c_client *client, int mode)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, mode);
+}
+
+/* power_state */
+static int isl29003_set_power_state(struct i2c_client *client, int state)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+				ISL29003_ADC_ENABLED | ISL29003_ADC_PD, 0,
+				state ? ISL29003_ADC_ENABLED : ISL29003_ADC_PD);
+}
+
+static int isl29003_get_power_state(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
+	return ~cmdreg & ISL29003_ADC_PD;
+}
+
+static int isl29003_get_adc_value(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int lsb, msb, range, bitdepth;
+
+	mutex_lock(&data->lock);
+	lsb = i2c_smbus_read_byte_data(client, ISL29003_REG_LSB_SENSOR);
+
+	if (lsb < 0) {
+		mutex_unlock(&data->lock);
+		return lsb;
+	}
+
+	msb = i2c_smbus_read_byte_data(client, ISL29003_REG_MSB_SENSOR);
+	mutex_unlock(&data->lock);
+
+	if (msb < 0)
+		return msb;
+
+	range = isl29003_get_range(client);
+	bitdepth = (4 - isl29003_get_resolution(client)) * 4;
+	return (((msb << 8) | lsb) * gain_range[range]) >> bitdepth;
+}
+
+/*
+ * sysfs layer
+ */
+
+/* range */
+static ssize_t isl29003_show_range(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_range(client));
+}
+
+static ssize_t isl29003_store_range(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
+		return -EINVAL;
+
+	ret = isl29003_set_range(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
+		   isl29003_show_range, isl29003_store_range);
+
+
+/* resolution */
+static ssize_t isl29003_show_resolution(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_resolution(client));
+}
+
+static ssize_t isl29003_store_resolution(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
+		return -EINVAL;
+
+	ret = isl29003_set_resolution(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(resolution, S_IWUSR | S_IRUGO,
+		   isl29003_show_resolution, isl29003_store_resolution);
+
+/* mode */
+static ssize_t isl29003_show_mode(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_mode(client));
+}
+
+static ssize_t isl29003_store_mode(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 2))
+		return -EINVAL;
+
+	ret = isl29003_set_mode(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
+		   isl29003_show_mode, isl29003_store_mode);
+
+
+/* power state */
+static ssize_t isl29003_show_power_state(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_power_state(client));
+}
+
+static ssize_t isl29003_store_power_state(struct device *dev,
+					  struct device_attribute *attr,
+					  const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
+		return -EINVAL;
+
+	ret = isl29003_set_power_state(client, val);
+	return ret ? ret : count;
+}
+
+static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO,
+		   isl29003_show_power_state, isl29003_store_power_state);
+
+
+/* lux */
+static ssize_t isl29003_show_lux(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	/* No LUX data if not operational */
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	return sprintf(buf, "%d\n", isl29003_get_adc_value(client));
+}
+
+static DEVICE_ATTR(lux, S_IRUGO, isl29003_show_lux, NULL);
+
+static struct attribute *isl29003_attributes[] = {
+	&dev_attr_range.attr,
+	&dev_attr_resolution.attr,
+	&dev_attr_mode.attr,
+	&dev_attr_power_state.attr,
+	&dev_attr_lux.attr,
+	NULL
+};
+
+static const struct attribute_group isl29003_attr_group = {
+	.attrs = isl29003_attributes,
+};
+
+static int isl29003_init_client(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int i;
+
+	/* read all the registers once to fill the cache.
+	 * if one of the reads fails, we consider the init failed */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++) {
+		int v = i2c_smbus_read_byte_data(client, i);
+		if (v < 0)
+			return -ENODEV;
+
+		data->reg_cache[i] = v;
+	}
+
+	/* set defaults */
+	isl29003_set_range(client, 0);
+	isl29003_set_resolution(client, 0);
+	isl29003_set_mode(client, 0);
+	isl29003_set_power_state(client, 0);
+
+	return 0;
+}
+
+/*
+ * I2C layer
+ */
+
+static int __devinit isl29003_probe(struct i2c_client *client,
+				    const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct isl29003_data *data;
+	int err = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+		return -EIO;
+
+	data = kzalloc(sizeof(struct isl29003_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->client = client;
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->lock);
+
+	/* initialize the ISL29003 chip */
+	err = isl29003_init_client(client);
+	if (err)
+		goto exit_kfree;
+
+	/* register sysfs hooks */
+	err = sysfs_create_group(&client->dev.kobj, &isl29003_attr_group);
+	if (err)
+		goto exit_kfree;
+
+	dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);
+	return 0;
+
+exit_kfree:
+	kfree(data);
+	return err;
+}
+
+static int __devexit isl29003_remove(struct i2c_client *client)
+{
+	sysfs_remove_group(&client->dev.kobj, &isl29003_attr_group);
+	isl29003_set_power_state(client, 0);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int isl29003_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	return isl29003_set_power_state(client, 0);
+}
+
+static int isl29003_resume(struct i2c_client *client)
+{
+	int i;
+	struct isl29003_data *data = i2c_get_clientdata(client);
+
+	/* restore registers from cache */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
+		if (!i2c_smbus_write_byte_data(client, i, data->reg_cache[i]))
+			return -EIO;
+
+	return 0;
+}
+
+#else
+#define isl29003_suspend	NULL
+#define isl29003_resume		NULL
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id isl29003_id[] = {
+	{ "isl29003", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, isl29003_id);
+
+static struct i2c_driver isl29003_driver = {
+	.driver = {
+		.name	= ISL29003_DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+	.suspend = isl29003_suspend,
+	.resume	= isl29003_resume,
+	.probe	= isl29003_probe,
+	.remove	= __devexit_p(isl29003_remove),
+	.id_table = isl29003_id,
+};
+
+static int __init isl29003_init(void)
+{
+	return i2c_add_driver(&isl29003_driver);
+}
+
+static void __exit isl29003_exit(void)
+{
+	i2c_del_driver(&isl29003_driver);
+}
+
+MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
+MODULE_DESCRIPTION("ISL29003 ambient light sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(isl29003_init);
+module_exit(isl29003_exit);
+
-- 
1.6.2


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-10 15:15                                               ` Daniel Mack
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel Mack @ 2009-03-10 15:15 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Jean Delvare, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

Hi Jonathan,

many thanks for your review!

On Tue, Mar 10, 2009 at 01:11:21PM +0000, Jonathan Cameron wrote:
> I notice that this device is extremely similar to the ISL29004 where only
> differences being with i2c address selection (that one has some pins to
> allow more than one option).  Worth rolling support for that device in
> here?

Well, the I2C address isn't coded in the driver but defined in the
i2c_board_info array of the board support file, so there is no reason
why it shouldn't work. Or are you talking about the names of files and
functions that you would like to see reflecting this?

> This device has some interesting interrupt / timing options which will fit
> nicely in the IIO framework.  For now the driver sensibly ignores them
> entirely. (if you don't need them, why bother?)

Right, I don't need them personally, and things are not connected to
the CPU, so I can't test that. I would expect anyone who needs this
functions to add support to the code :)

> > +The ISL29003 does not have an ID register which could be used to identify
> > +it, so the detection routine will just try to read from the configured I2C
> > +addess and consider the device to be present as soon as it ACKs the
> > +transfer.
> This is a little nasty given the chances of something else sitting on that
> address, but not much else you can do.

True.

> Some of the following are acting only as documentation. It's
> a matter of personal preference whether you specify them.

I defined them to make future feature additions easy ...

> > +#define ISL29003_REG_COMMAND		0x00
> > +#define ISL29003_ADC_ENABLED		(1 << 7)
> > +#define ISL29003_ADC_PD			(1 << 6)
> > +#define ISL29003_TIMING_INT		(1 << 5)
> > +#define ISL29003_MODE_SHIFT		(2)
> > +#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
> > +#define ISL29003_RES_SHIFT		(0)
> > +#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)

[...]

> > +static int __isl29003_write_reg(struct i2c_client *client,
> > +				u32 reg, u8 mask, u8 shift, u8 val)
> > +{
> > +	struct isl29003_data *data = i2c_get_clientdata(client);
> > +	int ret = 0;
> > +	u8 tmp;
> > +
> > +	mutex_lock(&data->lock);
> > +
> > +	tmp = data->reg_cache[reg];
> > +	tmp &= ~mask;
> > +	tmp |= val << shift;
> > +
> > +	ret = i2c_smbus_write_byte_data(client, reg, tmp);
> As i2c_smbus_write_byte_data is defined as returning zero on success
> this could simply be, if (!ret).

Fixed.

> > +}
> > +
> > +/* power_state */
> > +static int isl29003_set_power_state(struct i2c_client *client, int state)
> > +{
> > +	int ret;
> > +
> > +	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> > +		ISL29003_ADC_ENABLED, 0, state);
> As this is either 0 or less than zero, again if (ret) will suffice.

Fixed.

> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> > +		ISL29003_ADC_PD, 0, ~state);
> Just return ret hence losing these 2 lines.

Fixed.

> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	return 0;
> > +}
> > +
> > +static int isl29003_get_power_state(struct i2c_client *client)
> > +{
> > +	struct isl29003_data *data = i2c_get_clientdata(client);
> > +	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
> > +	return (cmdreg & ISL29003_ADC_ENABLED) && (~cmdreg & ISL29003_ADC_PD);
> > +}
> Unfortunately this chip has gain controlled by combination of the i2c
> accessed registers and an external resitor.  I guess this falls
> into the category of things to be fixed in userspace.  Perhaps
> some documentation to indicate this issue would help?
> (table 9 of data sheet)

The resistor does not affect the value read from the register - it's
about integration time only. Or did I get it wrong?

> > +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
> > +		return -EINVAL;
> See as there are rather a lot of calls like this, why don't
> you consider moving this test into the register write command.
> If the device is powered up then it will get copied to the
> device. If not, just store it in the cache and it will be
> written on resume anyway (assuming my understanding of your
> suspend resume code is right!)

It's not even necessary to do that - the driver can access all registers
while the PD bit is set. So the only check is to not read sensor data
when this condition is matched.

> > +static ssize_t isl29003_store_power_state(struct device *dev,
> > +					  struct device_attribute *attr,
> > +					  const char *buf, size_t count)
> > +{
> > +	struct i2c_client *client = to_i2c_client(dev);
> > +	unsigned long val;
> > +	int ret;
> > +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
> > +		return -EINVAL;
> > +
> > +	ret = isl29003_set_power_state(client, val);
> > +	if (ret < 0)
> > +		return ret;
> I'd go with more concise "return ret ? ret : count;" but thats down
> to personal preference.

Fixed.

> > +	/* read all the registers once to fill the cache.
> > +	 * if one of the reads fails, we consider the init failed */
> 
> Why are you caching registers 4-7?  They are read only data registers
> and those you use are read on demand anyway.  It's not a problem here,
> but worth noting that even the first 2 are not simply read / write
> control registers and hence any caching method has to be very careful
>   (there is a interrupt flag in control according to the data sheet.)

You're right. I changed the cache to only store the first 4 registers
for now. Interrupt handling will need some extra work anyway, so I'll
leave that for now.

> If we are restoring registers from cache, why are we reading them?
> 
> > +	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
> > +		if (i2c_smbus_read_byte_data(client, i, data->reg_cache[i]) < 0)
> > +			return -ENODEV;

Typo - couldn't test the suspend/resume code yet. Fixed now.

Please see the patch below.

Thanks again,
Daniel


>From 8ee5021834900f312ff26cd2586c18e99af31a5d Mon Sep 17 00:00:00 2001
From: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
Date: Tue, 10 Mar 2009 16:13:07 +0100
Subject: [PATCH] Added driver for ISL29003 ambient light sensor

This patch adds 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 to drivers/misc for now until the industrial I/O framework
gets merged.

Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
---
 New version with Jonathan Cameron's review notes addressed.

 Documentation/misc-devices/isl29003 |   62 +++++
 drivers/misc/Kconfig                |   10 +
 drivers/misc/Makefile               |    1 +
 drivers/misc/isl29003.c             |  470 +++++++++++++++++++++++++++++++++++
 4 files changed, 543 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/misc-devices/isl29003
 create mode 100644 drivers/misc/isl29003.c

diff --git a/Documentation/misc-devices/isl29003 b/Documentation/misc-devices/isl29003
new file mode 100644
index 0000000..c4ff5f3
--- /dev/null
+++ b/Documentation/misc-devices/isl29003
@@ -0,0 +1,62 @@
+Kernel driver isl29003
+=====================
+
+Supported chips:
+* Intersil ISL29003
+Prefix: 'isl29003'
+Addresses scanned: none
+Datasheet:
+http://www.intersil.com/data/fn/fn7464.pdf
+
+Author: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
+
+
+Description
+-----------
+The ISL29003 is an integrated light sensor with a 16-bit integrating type
+ADC, I2C user programmable lux range select for optimized counts/lux, and
+I2C multi-function control and monitoring capabilities. The internal ADC
+provides 16-bit resolution while rejecting 50Hz and 60Hz flicker caused by
+artificial light sources.
+
+The driver allows to set the lux range, the bit resolution, the operational
+mode (see below) and the power state of device and can read the current lux
+value, of course.
+
+
+Detection
+---------
+
+The ISL29003 does not have an ID register which could be used to identify
+it, so the detection routine will just try to read from the configured I2C
+addess and consider the device to be present as soon as it ACKs the
+transfer.
+
+
+Sysfs entries
+-------------
+
+range:
+	0: 0 lux to 1000 lux (default)
+	1: 0 lux to 4000 lux
+	2: 0 lux to 16,000 lux
+	3: 0 lux to 64,000 lux
+
+resolution:
+	0: 2^16 cycles (default)
+	1: 2^12 cycles
+	2: 2^8 cycles
+	3: 2^4 cycles
+
+mode:
+	0: diode1's current (unsigned 16bit) (default)
+	1: diode1's current (unsigned 16bit)
+	2: difference between diodes (l1 - l2, signed 15bit)
+
+power_state:
+	0: device is disabled (default)
+	1: device is enabled
+
+lux (read only):
+	returns the value from the last sensor reading
+
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c64e679..b883b19 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -223,6 +223,16 @@ config DELL_LAPTOP
 	This driver adds support for rfkill and backlight control to Dell
 	laptops.
 
+config ISL29003
+	tristate "Intersil ISL29003 ambient light sensor"
+	depends on I2C
+	help
+	  If you say yes here you get support for the Intersil ISL29003
+	  ambient light sensor.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called isl29003.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index bc11998..7871f05 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -18,5 +18,6 @@ obj-$(CONFIG_KGDB_TESTS)	+= kgdbts.o
 obj-$(CONFIG_SGI_XP)		+= sgi-xp/
 obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
 obj-$(CONFIG_HP_ILO)		+= hpilo.o
+obj-$(CONFIG_ISL29003)		+= isl29003.o
 obj-$(CONFIG_C2PORT)		+= c2port/
 obj-y				+= eeprom/
diff --git a/drivers/misc/isl29003.c b/drivers/misc/isl29003.c
new file mode 100644
index 0000000..2e2a592
--- /dev/null
+++ b/drivers/misc/isl29003.c
@@ -0,0 +1,470 @@
+/*
+ *  isl29003.c - Linux kernel module for
+ * 	Intersil ISL29003 ambient light sensor
+ *
+ *  See file:Documentation/misc-devices/isl29003
+ *
+ *  Copyright (c) 2009 Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
+ *
+ *  Based on code written by
+ *  	Rodolfo Giometti <giometti-k2GhghHVRtY@public.gmane.org>
+ *  	Eurotech S.p.A. <info-ymFC7zkAqBI1GQ1Ptb7lUw@public.gmane.org>
+ *
+ *  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>
+#include <linux/delay.h>
+
+#define ISL29003_DRV_NAME	"isl29003"
+#define DRIVER_VERSION		"1.0"
+
+#define ISL29003_REG_COMMAND		0x00
+#define ISL29003_ADC_ENABLED		(1 << 7)
+#define ISL29003_ADC_PD			(1 << 6)
+#define ISL29003_TIMING_INT		(1 << 5)
+#define ISL29003_MODE_SHIFT		(2)
+#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
+#define ISL29003_RES_SHIFT		(0)
+#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)
+
+#define ISL29003_REG_CONTROL		0x01
+#define ISL29003_INT_FLG		(1 << 5)
+#define ISL29003_RANGE_SHIFT		(2)
+#define ISL29003_RANGE_MASK		(0x3 << ISL29003_RANGE_SHIFT)
+#define ISL29003_INT_PERSISTS_SHIFT	(0)
+#define ISL29003_INT_PERSISTS_MASK	(0xf << ISL29003_INT_PERSISTS_SHIFT)
+
+#define ISL29003_REG_IRQ_THRESH_HI	0x02
+#define ISL29003_REG_IRQ_THRESH_LO	0x03
+#define ISL29003_REG_LSB_SENSOR		0x04
+#define ISL29003_REG_MSB_SENSOR		0x05
+#define ISL29003_REG_LSB_TIMER		0x06
+#define ISL29003_REG_MSB_TIMER		0x07
+
+#define ISL29003_NUM_CACHABLE_REGS	4
+
+struct isl29003_data {
+	struct i2c_client *client;
+	struct mutex lock;
+	u8 reg_cache[ISL29003_NUM_CACHABLE_REGS];
+};
+
+static int gain_range[] = {
+	1000, 4000, 16000, 64000
+};
+
+/*
+ * register access helpers
+ */
+
+static int __isl29003_read_reg(struct i2c_client *client,
+			       u32 reg, u8 mask, u8 shift)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	return (data->reg_cache[reg] & mask) >> shift;
+}
+
+static int __isl29003_write_reg(struct i2c_client *client,
+				u32 reg, u8 mask, u8 shift, u8 val)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int ret = 0;
+	u8 tmp;
+
+	if (reg >= ISL29003_NUM_CACHABLE_REGS)
+		return -EINVAL;
+
+	mutex_lock(&data->lock);
+
+	tmp = data->reg_cache[reg];
+	tmp &= ~mask;
+	tmp |= val << shift;
+
+	ret = i2c_smbus_write_byte_data(client, reg, tmp);
+	if (!ret)
+		data->reg_cache[reg] = tmp;
+
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+/*
+ * internally used functions
+ */
+
+/* range */
+static int isl29003_get_range(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT);
+}
+
+static int isl29003_set_range(struct i2c_client *client, int range)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT, range);
+}
+
+/* resolution */
+static int isl29003_get_resolution(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_resolution(struct i2c_client *client, int res)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, res);
+}
+
+/* mode */
+static int isl29003_get_mode(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_mode(struct i2c_client *client, int mode)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, mode);
+}
+
+/* power_state */
+static int isl29003_set_power_state(struct i2c_client *client, int state)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+				ISL29003_ADC_ENABLED | ISL29003_ADC_PD, 0,
+				state ? ISL29003_ADC_ENABLED : ISL29003_ADC_PD);
+}
+
+static int isl29003_get_power_state(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
+	return ~cmdreg & ISL29003_ADC_PD;
+}
+
+static int isl29003_get_adc_value(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int lsb, msb, range, bitdepth;
+
+	mutex_lock(&data->lock);
+	lsb = i2c_smbus_read_byte_data(client, ISL29003_REG_LSB_SENSOR);
+
+	if (lsb < 0) {
+		mutex_unlock(&data->lock);
+		return lsb;
+	}
+
+	msb = i2c_smbus_read_byte_data(client, ISL29003_REG_MSB_SENSOR);
+	mutex_unlock(&data->lock);
+
+	if (msb < 0)
+		return msb;
+
+	range = isl29003_get_range(client);
+	bitdepth = (4 - isl29003_get_resolution(client)) * 4;
+	return (((msb << 8) | lsb) * gain_range[range]) >> bitdepth;
+}
+
+/*
+ * sysfs layer
+ */
+
+/* range */
+static ssize_t isl29003_show_range(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_range(client));
+}
+
+static ssize_t isl29003_store_range(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
+		return -EINVAL;
+
+	ret = isl29003_set_range(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
+		   isl29003_show_range, isl29003_store_range);
+
+
+/* resolution */
+static ssize_t isl29003_show_resolution(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_resolution(client));
+}
+
+static ssize_t isl29003_store_resolution(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
+		return -EINVAL;
+
+	ret = isl29003_set_resolution(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(resolution, S_IWUSR | S_IRUGO,
+		   isl29003_show_resolution, isl29003_store_resolution);
+
+/* mode */
+static ssize_t isl29003_show_mode(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_mode(client));
+}
+
+static ssize_t isl29003_store_mode(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 2))
+		return -EINVAL;
+
+	ret = isl29003_set_mode(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
+		   isl29003_show_mode, isl29003_store_mode);
+
+
+/* power state */
+static ssize_t isl29003_show_power_state(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_power_state(client));
+}
+
+static ssize_t isl29003_store_power_state(struct device *dev,
+					  struct device_attribute *attr,
+					  const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
+		return -EINVAL;
+
+	ret = isl29003_set_power_state(client, val);
+	return ret ? ret : count;
+}
+
+static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO,
+		   isl29003_show_power_state, isl29003_store_power_state);
+
+
+/* lux */
+static ssize_t isl29003_show_lux(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	/* No LUX data if not operational */
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	return sprintf(buf, "%d\n", isl29003_get_adc_value(client));
+}
+
+static DEVICE_ATTR(lux, S_IRUGO, isl29003_show_lux, NULL);
+
+static struct attribute *isl29003_attributes[] = {
+	&dev_attr_range.attr,
+	&dev_attr_resolution.attr,
+	&dev_attr_mode.attr,
+	&dev_attr_power_state.attr,
+	&dev_attr_lux.attr,
+	NULL
+};
+
+static const struct attribute_group isl29003_attr_group = {
+	.attrs = isl29003_attributes,
+};
+
+static int isl29003_init_client(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int i;
+
+	/* read all the registers once to fill the cache.
+	 * if one of the reads fails, we consider the init failed */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++) {
+		int v = i2c_smbus_read_byte_data(client, i);
+		if (v < 0)
+			return -ENODEV;
+
+		data->reg_cache[i] = v;
+	}
+
+	/* set defaults */
+	isl29003_set_range(client, 0);
+	isl29003_set_resolution(client, 0);
+	isl29003_set_mode(client, 0);
+	isl29003_set_power_state(client, 0);
+
+	return 0;
+}
+
+/*
+ * I2C layer
+ */
+
+static int __devinit isl29003_probe(struct i2c_client *client,
+				    const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct isl29003_data *data;
+	int err = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+		return -EIO;
+
+	data = kzalloc(sizeof(struct isl29003_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->client = client;
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->lock);
+
+	/* initialize the ISL29003 chip */
+	err = isl29003_init_client(client);
+	if (err)
+		goto exit_kfree;
+
+	/* register sysfs hooks */
+	err = sysfs_create_group(&client->dev.kobj, &isl29003_attr_group);
+	if (err)
+		goto exit_kfree;
+
+	dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);
+	return 0;
+
+exit_kfree:
+	kfree(data);
+	return err;
+}
+
+static int __devexit isl29003_remove(struct i2c_client *client)
+{
+	sysfs_remove_group(&client->dev.kobj, &isl29003_attr_group);
+	isl29003_set_power_state(client, 0);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int isl29003_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	return isl29003_set_power_state(client, 0);
+}
+
+static int isl29003_resume(struct i2c_client *client)
+{
+	int i;
+	struct isl29003_data *data = i2c_get_clientdata(client);
+
+	/* restore registers from cache */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
+		if (!i2c_smbus_write_byte_data(client, i, data->reg_cache[i]))
+			return -EIO;
+
+	return 0;
+}
+
+#else
+#define isl29003_suspend	NULL
+#define isl29003_resume		NULL
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id isl29003_id[] = {
+	{ "isl29003", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, isl29003_id);
+
+static struct i2c_driver isl29003_driver = {
+	.driver = {
+		.name	= ISL29003_DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+	.suspend = isl29003_suspend,
+	.resume	= isl29003_resume,
+	.probe	= isl29003_probe,
+	.remove	= __devexit_p(isl29003_remove),
+	.id_table = isl29003_id,
+};
+
+static int __init isl29003_init(void)
+{
+	return i2c_add_driver(&isl29003_driver);
+}
+
+static void __exit isl29003_exit(void)
+{
+	i2c_del_driver(&isl29003_driver);
+}
+
+MODULE_AUTHOR("Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>");
+MODULE_DESCRIPTION("ISL29003 ambient light sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(isl29003_init);
+module_exit(isl29003_exit);
+
-- 
1.6.2

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-10 17:49                                                 ` Jonathan Cameron
  0 siblings, 0 replies; 49+ messages in thread
From: Jonathan Cameron @ 2009-03-10 17:49 UTC (permalink / raw)
  To: Daniel Mack; +Cc: Jean Delvare, linux-i2c, linux-kernel

Daniel Mack wrote:
> Hi Jonathan,
> 
> many thanks for your review!
You are welcome.
> 
> On Tue, Mar 10, 2009 at 01:11:21PM +0000, Jonathan Cameron wrote:
>> I notice that this device is extremely similar to the ISL29004 where only
>> differences being with i2c address selection (that one has some pins to
>> allow more than one option).  Worth rolling support for that device in
>> here?
> 
> Well, the I2C address isn't coded in the driver but defined in the
> i2c_board_info array of the board support file, so there is no reason
> why it shouldn't work. Or are you talking about the names of files and
> functions that you would like to see reflecting this?
function names and filenames are fine (typically you just pick a random device
that the driver supports and name everything after that).
I think the only addition would be documentation of the patch and and id in
the id table.
> 
>> This device has some interesting interrupt / timing options which will fit
>> nicely in the IIO framework.  For now the driver sensibly ignores them
>> entirely. (if you don't need them, why bother?)
> 
> Right, I don't need them personally, and things are not connected to
> the CPU, so I can't test that. I would expect anyone who needs this
> functions to add support to the code :)
> 
>>> +The ISL29003 does not have an ID register which could be used to identify
>>> +it, so the detection routine will just try to read from the configured I2C
>>> +addess and consider the device to be present as soon as it ACKs the
>>> +transfer.
>> This is a little nasty given the chances of something else sitting on that
>> address, but not much else you can do.
> 
> True.
> 
>> Some of the following are acting only as documentation. It's
>> a matter of personal preference whether you specify them.
> 
> I defined them to make future feature additions easy ...
> 
>>> +#define ISL29003_REG_COMMAND		0x00
>>> +#define ISL29003_ADC_ENABLED		(1 << 7)
>>> +#define ISL29003_ADC_PD			(1 << 6)
>>> +#define ISL29003_TIMING_INT		(1 << 5)
>>> +#define ISL29003_MODE_SHIFT		(2)
>>> +#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
>>> +#define ISL29003_RES_SHIFT		(0)
>>> +#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)
> 
> [...]
> 
>>> +static int __isl29003_write_reg(struct i2c_client *client,
>>> +				u32 reg, u8 mask, u8 shift, u8 val)
>>> +{
>>> +	struct isl29003_data *data = i2c_get_clientdata(client);
>>> +	int ret = 0;
>>> +	u8 tmp;
>>> +
>>> +	mutex_lock(&data->lock);
>>> +
>>> +	tmp = data->reg_cache[reg];
>>> +	tmp &= ~mask;
>>> +	tmp |= val << shift;
>>> +
>>> +	ret = i2c_smbus_write_byte_data(client, reg, tmp);
>> As i2c_smbus_write_byte_data is defined as returning zero on success
>> this could simply be, if (!ret).
> 
> Fixed.
> 
>>> +}
>>> +
>>> +/* power_state */
>>> +static int isl29003_set_power_state(struct i2c_client *client, int state)
>>> +{
>>> +	int ret;
>>> +
>>> +	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
>>> +		ISL29003_ADC_ENABLED, 0, state);
>> As this is either 0 or less than zero, again if (ret) will suffice.
> 
> Fixed.
> 
>>> +	if (ret < 0)
>>> +		return ret;
>>> +
>>> +	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
>>> +		ISL29003_ADC_PD, 0, ~state);
>> Just return ret hence losing these 2 lines.
> 
> Fixed.
> 
>>> +	if (ret < 0)
>>> +		return ret;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int isl29003_get_power_state(struct i2c_client *client)
>>> +{
>>> +	struct isl29003_data *data = i2c_get_clientdata(client);
>>> +	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
>>> +	return (cmdreg & ISL29003_ADC_ENABLED) && (~cmdreg & ISL29003_ADC_PD);
>>> +}
>> Unfortunately this chip has gain controlled by combination of the i2c
>> accessed registers and an external resitor.  I guess this falls
>> into the category of things to be fixed in userspace.  Perhaps
>> some documentation to indicate this issue would help?
>> (table 9 of data sheet)
> 
> The resistor does not affect the value read from the register - it's
> about integration time only. Or did I get it wrong?
My reading of table 9 on the data sheet is that the full scale
range is affected by the resistor. This is also described in the
bit about calculating lux.
> 
>>> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
>>> +		return -EINVAL;
>> See as there are rather a lot of calls like this, why don't
>> you consider moving this test into the register write command.
>> If the device is powered up then it will get copied to the
>> device. If not, just store it in the cache and it will be
>> written on resume anyway (assuming my understanding of your
>> suspend resume code is right!)
> 
> It's not even necessary to do that - the driver can access all registers
> while the PD bit is set. So the only check is to not read sensor data
> when this condition is matched.
> 
>>> +static ssize_t isl29003_store_power_state(struct device *dev,
>>> +					  struct device_attribute *attr,
>>> +					  const char *buf, size_t count)
>>> +{
>>> +	struct i2c_client *client = to_i2c_client(dev);
>>> +	unsigned long val;
>>> +	int ret;
>>> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
>>> +		return -EINVAL;
>>> +
>>> +	ret = isl29003_set_power_state(client, val);
>>> +	if (ret < 0)
>>> +		return ret;
>> I'd go with more concise "return ret ? ret : count;" but thats down
>> to personal preference.
> 
> Fixed.
> 
>>> +	/* read all the registers once to fill the cache.
>>> +	 * if one of the reads fails, we consider the init failed */
>> Why are you caching registers 4-7?  They are read only data registers
>> and those you use are read on demand anyway.  It's not a problem here,
>> but worth noting that even the first 2 are not simply read / write
>> control registers and hence any caching method has to be very careful
>>   (there is a interrupt flag in control according to the data sheet.)
> 
> You're right. I changed the cache to only store the first 4 registers
> for now. Interrupt handling will need some extra work anyway, so I'll
> leave that for now.
> 
>> If we are restoring registers from cache, why are we reading them?
>>
>>> +	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
>>> +		if (i2c_smbus_read_byte_data(client, i, data->reg_cache[i]) < 0)
>>> +			return -ENODEV;
> 
> Typo - couldn't test the suspend/resume code yet. Fixed now.
> 
> Please see the patch below.
> 
> Thanks again,
> Daniel
> 
> 
>>From 8ee5021834900f312ff26cd2586c18e99af31a5d Mon Sep 17 00:00:00 2001
> From: Daniel Mack <daniel@caiaq.de>
> Date: Tue, 10 Mar 2009 16:13:07 +0100
> Subject: [PATCH] Added driver for ISL29003 ambient light sensor
> 
> This patch adds 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 to drivers/misc for now until the industrial I/O framework
> gets merged.
> 
> Signed-off-by: Daniel Mack <daniel@caiaq.de>
> ---
>  New version with Jonathan Cameron's review notes addressed.
> 
>  Documentation/misc-devices/isl29003 |   62 +++++
>  drivers/misc/Kconfig                |   10 +
>  drivers/misc/Makefile               |    1 +
>  drivers/misc/isl29003.c             |  470 +++++++++++++++++++++++++++++++++++
>  4 files changed, 543 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/misc-devices/isl29003
>  create mode 100644 drivers/misc/isl29003.c
> 
> diff --git a/Documentation/misc-devices/isl29003 b/Documentation/misc-devices/isl29003
> new file mode 100644
> index 0000000..c4ff5f3
> --- /dev/null
> +++ b/Documentation/misc-devices/isl29003
> @@ -0,0 +1,62 @@
> +Kernel driver isl29003
> +=====================
> +
> +Supported chips:
> +* Intersil ISL29003
> +Prefix: 'isl29003'
> +Addresses scanned: none
> +Datasheet:
> +http://www.intersil.com/data/fn/fn7464.pdf
> +
> +Author: Daniel Mack <daniel@caiaq.de>
> +
> +
> +Description
> +-----------
> +The ISL29003 is an integrated light sensor with a 16-bit integrating type
> +ADC, I2C user programmable lux range select for optimized counts/lux, and
> +I2C multi-function control and monitoring capabilities. The internal ADC
> +provides 16-bit resolution while rejecting 50Hz and 60Hz flicker caused by
> +artificial light sources.
> +
> +The driver allows to set the lux range, the bit resolution, the operational
> +mode (see below) and the power state of device and can read the current lux
> +value, of course.
> +
> +
> +Detection
> +---------
> +
> +The ISL29003 does not have an ID register which could be used to identify
> +it, so the detection routine will just try to read from the configured I2C
> +addess and consider the device to be present as soon as it ACKs the
> +transfer.
> +
> +
> +Sysfs entries
> +-------------
> +
> +range:
> +	0: 0 lux to 1000 lux (default)
> +	1: 0 lux to 4000 lux
> +	2: 0 lux to 16,000 lux
> +	3: 0 lux to 64,000 lux
> +
> +resolution:
> +	0: 2^16 cycles (default)
> +	1: 2^12 cycles
> +	2: 2^8 cycles
> +	3: 2^4 cycles
> +
> +mode:
> +	0: diode1's current (unsigned 16bit) (default)
> +	1: diode1's current (unsigned 16bit)
> +	2: difference between diodes (l1 - l2, signed 15bit)
> +
> +power_state:
> +	0: device is disabled (default)
> +	1: device is enabled
> +
> +lux (read only):
> +	returns the value from the last sensor reading
> +
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index c64e679..b883b19 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -223,6 +223,16 @@ config DELL_LAPTOP
>  	This driver adds support for rfkill and backlight control to Dell
>  	laptops.
>  
> +config ISL29003
> +	tristate "Intersil ISL29003 ambient light sensor"
> +	depends on I2C
> +	help
> +	  If you say yes here you get support for the Intersil ISL29003
> +	  ambient light sensor.
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called isl29003.
> +
>  source "drivers/misc/c2port/Kconfig"
>  source "drivers/misc/eeprom/Kconfig"
>  
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index bc11998..7871f05 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -18,5 +18,6 @@ obj-$(CONFIG_KGDB_TESTS)	+= kgdbts.o
>  obj-$(CONFIG_SGI_XP)		+= sgi-xp/
>  obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
>  obj-$(CONFIG_HP_ILO)		+= hpilo.o
> +obj-$(CONFIG_ISL29003)		+= isl29003.o
>  obj-$(CONFIG_C2PORT)		+= c2port/
>  obj-y				+= eeprom/
> diff --git a/drivers/misc/isl29003.c b/drivers/misc/isl29003.c
> new file mode 100644
> index 0000000..2e2a592
> --- /dev/null
> +++ b/drivers/misc/isl29003.c
> @@ -0,0 +1,470 @@
> +/*
> + *  isl29003.c - Linux kernel module for
> + * 	Intersil ISL29003 ambient light sensor
> + *
> + *  See file:Documentation/misc-devices/isl29003
> + *
> + *  Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
> + *
> + *  Based on code written by
> + *  	Rodolfo Giometti <giometti@linux.it>
> + *  	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>
> +#include <linux/delay.h>
> +
> +#define ISL29003_DRV_NAME	"isl29003"
> +#define DRIVER_VERSION		"1.0"
> +
> +#define ISL29003_REG_COMMAND		0x00
> +#define ISL29003_ADC_ENABLED		(1 << 7)
> +#define ISL29003_ADC_PD			(1 << 6)
> +#define ISL29003_TIMING_INT		(1 << 5)
> +#define ISL29003_MODE_SHIFT		(2)
> +#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
> +#define ISL29003_RES_SHIFT		(0)
> +#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)
> +
> +#define ISL29003_REG_CONTROL		0x01
> +#define ISL29003_INT_FLG		(1 << 5)
> +#define ISL29003_RANGE_SHIFT		(2)
> +#define ISL29003_RANGE_MASK		(0x3 << ISL29003_RANGE_SHIFT)
> +#define ISL29003_INT_PERSISTS_SHIFT	(0)
> +#define ISL29003_INT_PERSISTS_MASK	(0xf << ISL29003_INT_PERSISTS_SHIFT)
> +
> +#define ISL29003_REG_IRQ_THRESH_HI	0x02
> +#define ISL29003_REG_IRQ_THRESH_LO	0x03
> +#define ISL29003_REG_LSB_SENSOR		0x04
> +#define ISL29003_REG_MSB_SENSOR		0x05
> +#define ISL29003_REG_LSB_TIMER		0x06
> +#define ISL29003_REG_MSB_TIMER		0x07
> +
> +#define ISL29003_NUM_CACHABLE_REGS	4
> +
> +struct isl29003_data {
> +	struct i2c_client *client;
> +	struct mutex lock;
> +	u8 reg_cache[ISL29003_NUM_CACHABLE_REGS];
> +};
> +
> +static int gain_range[] = {
> +	1000, 4000, 16000, 64000
> +};
> +
> +/*
> + * register access helpers
> + */
> +
> +static int __isl29003_read_reg(struct i2c_client *client,
> +			       u32 reg, u8 mask, u8 shift)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	return (data->reg_cache[reg] & mask) >> shift;
> +}
> +
> +static int __isl29003_write_reg(struct i2c_client *client,
> +				u32 reg, u8 mask, u8 shift, u8 val)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	int ret = 0;
> +	u8 tmp;
> +
> +	if (reg >= ISL29003_NUM_CACHABLE_REGS)
> +		return -EINVAL;
> +
> +	mutex_lock(&data->lock);
> +
> +	tmp = data->reg_cache[reg];
> +	tmp &= ~mask;
> +	tmp |= val << shift;
> +
> +	ret = i2c_smbus_write_byte_data(client, reg, tmp);
> +	if (!ret)
> +		data->reg_cache[reg] = tmp;
> +
> +	mutex_unlock(&data->lock);
> +	return ret;
> +}
> +
> +/*
> + * internally used functions
> + */
> +
> +/* range */
> +static int isl29003_get_range(struct i2c_client *client)
> +{
> +	return __isl29003_read_reg(client, ISL29003_REG_CONTROL,
> +		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT);
> +}
> +
> +static int isl29003_set_range(struct i2c_client *client, int range)
> +{
> +	return __isl29003_write_reg(client, ISL29003_REG_CONTROL,
> +		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT, range);
> +}
> +
> +/* resolution */
> +static int isl29003_get_resolution(struct i2c_client *client)
> +{
> +	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
> +}
> +
> +static int isl29003_set_resolution(struct i2c_client *client, int res)
> +{
> +	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_RES_MASK, ISL29003_RES_SHIFT, res);
> +}
> +
> +/* mode */
> +static int isl29003_get_mode(struct i2c_client *client)
> +{
> +	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
> +}
> +
> +static int isl29003_set_mode(struct i2c_client *client, int mode)
> +{
> +	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_RES_MASK, ISL29003_RES_SHIFT, mode);
> +}
> +
> +/* power_state */
> +static int isl29003_set_power_state(struct i2c_client *client, int state)
> +{
> +	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> +				ISL29003_ADC_ENABLED | ISL29003_ADC_PD, 0,
> +				state ? ISL29003_ADC_ENABLED : ISL29003_ADC_PD);
> +}
> +
> +static int isl29003_get_power_state(struct i2c_client *client)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
> +	return ~cmdreg & ISL29003_ADC_PD;
> +}
> +
> +static int isl29003_get_adc_value(struct i2c_client *client)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	int lsb, msb, range, bitdepth;
> +
> +	mutex_lock(&data->lock);
> +	lsb = i2c_smbus_read_byte_data(client, ISL29003_REG_LSB_SENSOR);
> +
> +	if (lsb < 0) {
> +		mutex_unlock(&data->lock);
> +		return lsb;
> +	}
> +
> +	msb = i2c_smbus_read_byte_data(client, ISL29003_REG_MSB_SENSOR);
> +	mutex_unlock(&data->lock);
> +
> +	if (msb < 0)
> +		return msb;
> +
> +	range = isl29003_get_range(client);
> +	bitdepth = (4 - isl29003_get_resolution(client)) * 4;
> +	return (((msb << 8) | lsb) * gain_range[range]) >> bitdepth;
> +}
> +
> +/*
> + * sysfs layer
> + */
> +
> +/* range */
> +static ssize_t isl29003_show_range(struct device *dev,
> +				   struct device_attribute *attr, char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	return sprintf(buf, "%i\n", isl29003_get_range(client));
> +}
> +
> +static ssize_t isl29003_store_range(struct device *dev,
> +				    struct device_attribute *attr,
> +				    const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	unsigned long val;
> +	int ret;
> +
> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
> +		return -EINVAL;
> +
> +	ret = isl29003_set_range(client, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
> +		   isl29003_show_range, isl29003_store_range);
> +
> +
> +/* resolution */
> +static ssize_t isl29003_show_resolution(struct device *dev,
> +					struct device_attribute *attr,
> +					char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	return sprintf(buf, "%d\n", isl29003_get_resolution(client));
> +}
> +
> +static ssize_t isl29003_store_resolution(struct device *dev,
> +					 struct device_attribute *attr,
> +					 const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	unsigned long val;
> +	int ret;
> +
> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
> +		return -EINVAL;
> +
> +	ret = isl29003_set_resolution(client, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR(resolution, S_IWUSR | S_IRUGO,
> +		   isl29003_show_resolution, isl29003_store_resolution);
> +
> +/* mode */
> +static ssize_t isl29003_show_mode(struct device *dev,
> +				  struct device_attribute *attr, char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	return sprintf(buf, "%d\n", isl29003_get_mode(client));
> +}
> +
> +static ssize_t isl29003_store_mode(struct device *dev,
> +		struct device_attribute *attr, const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	unsigned long val;
> +	int ret;
> +
> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 2))
> +		return -EINVAL;
> +
> +	ret = isl29003_set_mode(client, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
> +		   isl29003_show_mode, isl29003_store_mode);
> +
> +
> +/* power state */
> +static ssize_t isl29003_show_power_state(struct device *dev,
> +					 struct device_attribute *attr,
> +					 char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	return sprintf(buf, "%d\n", isl29003_get_power_state(client));
> +}
> +
> +static ssize_t isl29003_store_power_state(struct device *dev,
> +					  struct device_attribute *attr,
> +					  const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	unsigned long val;
> +	int ret;
> +
> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
> +		return -EINVAL;
> +
> +	ret = isl29003_set_power_state(client, val);
> +	return ret ? ret : count;
> +}
> +
> +static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO,
> +		   isl29003_show_power_state, isl29003_store_power_state);
> +
> +
> +/* lux */
> +static ssize_t isl29003_show_lux(struct device *dev,
> +				 struct device_attribute *attr, char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +
> +	/* No LUX data if not operational */
> +	if (!isl29003_get_power_state(client))
> +		return -EBUSY;
> +
> +	return sprintf(buf, "%d\n", isl29003_get_adc_value(client));
> +}
> +
> +static DEVICE_ATTR(lux, S_IRUGO, isl29003_show_lux, NULL);
> +
> +static struct attribute *isl29003_attributes[] = {
> +	&dev_attr_range.attr,
> +	&dev_attr_resolution.attr,
> +	&dev_attr_mode.attr,
> +	&dev_attr_power_state.attr,
> +	&dev_attr_lux.attr,
> +	NULL
> +};
> +
> +static const struct attribute_group isl29003_attr_group = {
> +	.attrs = isl29003_attributes,
> +};
> +
> +static int isl29003_init_client(struct i2c_client *client)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	int i;
> +
> +	/* read all the registers once to fill the cache.
> +	 * if one of the reads fails, we consider the init failed */
> +	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++) {
> +		int v = i2c_smbus_read_byte_data(client, i);
> +		if (v < 0)
> +			return -ENODEV;
> +
> +		data->reg_cache[i] = v;
> +	}
> +
> +	/* set defaults */
> +	isl29003_set_range(client, 0);
> +	isl29003_set_resolution(client, 0);
> +	isl29003_set_mode(client, 0);
> +	isl29003_set_power_state(client, 0);
> +
> +	return 0;
> +}
> +
> +/*
> + * I2C layer
> + */
> +
> +static int __devinit isl29003_probe(struct i2c_client *client,
> +				    const struct i2c_device_id *id)
> +{
> +	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
> +	struct isl29003_data *data;
> +	int err = 0;
> +
> +	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
> +		return -EIO;
> +
> +	data = kzalloc(sizeof(struct isl29003_data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	data->client = client;
> +	i2c_set_clientdata(client, data);
> +	mutex_init(&data->lock);
> +
> +	/* initialize the ISL29003 chip */
> +	err = isl29003_init_client(client);
> +	if (err)
> +		goto exit_kfree;
> +
> +	/* register sysfs hooks */
> +	err = sysfs_create_group(&client->dev.kobj, &isl29003_attr_group);
> +	if (err)
> +		goto exit_kfree;
> +
> +	dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);
> +	return 0;
> +
> +exit_kfree:
> +	kfree(data);
> +	return err;
> +}
> +
> +static int __devexit isl29003_remove(struct i2c_client *client)
> +{
> +	sysfs_remove_group(&client->dev.kobj, &isl29003_attr_group);
> +	isl29003_set_power_state(client, 0);
> +	kfree(i2c_get_clientdata(client));
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int isl29003_suspend(struct i2c_client *client, pm_message_t mesg)
> +{
> +	return isl29003_set_power_state(client, 0);
> +}
> +
> +static int isl29003_resume(struct i2c_client *client)
> +{
> +	int i;
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +
> +	/* restore registers from cache */
> +	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
> +		if (!i2c_smbus_write_byte_data(client, i, data->reg_cache[i]))
> +			return -EIO;
> +
> +	return 0;
> +}
> +
> +#else
> +#define isl29003_suspend	NULL
> +#define isl29003_resume		NULL
> +#endif /* CONFIG_PM */
> +
> +static const struct i2c_device_id isl29003_id[] = {
> +	{ "isl29003", 0 },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(i2c, isl29003_id);
> +
> +static struct i2c_driver isl29003_driver = {
> +	.driver = {
> +		.name	= ISL29003_DRV_NAME,
> +		.owner	= THIS_MODULE,
> +	},
> +	.suspend = isl29003_suspend,
> +	.resume	= isl29003_resume,
> +	.probe	= isl29003_probe,
> +	.remove	= __devexit_p(isl29003_remove),
> +	.id_table = isl29003_id,
> +};
> +
> +static int __init isl29003_init(void)
> +{
> +	return i2c_add_driver(&isl29003_driver);
> +}
> +
> +static void __exit isl29003_exit(void)
> +{
> +	i2c_del_driver(&isl29003_driver);
> +}
> +
> +MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
> +MODULE_DESCRIPTION("ISL29003 ambient light sensor driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_VERSION(DRIVER_VERSION);
> +
> +module_init(isl29003_init);
> +module_exit(isl29003_exit);
> +

All looks fine to me.

Acked-by: Jonathan Cameron <jic23@cam.ac.uk>

Cheers,

Jonathan

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-10 17:49                                                 ` Jonathan Cameron
  0 siblings, 0 replies; 49+ messages in thread
From: Jonathan Cameron @ 2009-03-10 17:49 UTC (permalink / raw)
  To: Daniel Mack
  Cc: Jean Delvare, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

Daniel Mack wrote:
> Hi Jonathan,
> 
> many thanks for your review!
You are welcome.
> 
> On Tue, Mar 10, 2009 at 01:11:21PM +0000, Jonathan Cameron wrote:
>> I notice that this device is extremely similar to the ISL29004 where only
>> differences being with i2c address selection (that one has some pins to
>> allow more than one option).  Worth rolling support for that device in
>> here?
> 
> Well, the I2C address isn't coded in the driver but defined in the
> i2c_board_info array of the board support file, so there is no reason
> why it shouldn't work. Or are you talking about the names of files and
> functions that you would like to see reflecting this?
function names and filenames are fine (typically you just pick a random device
that the driver supports and name everything after that).
I think the only addition would be documentation of the patch and and id in
the id table.
> 
>> This device has some interesting interrupt / timing options which will fit
>> nicely in the IIO framework.  For now the driver sensibly ignores them
>> entirely. (if you don't need them, why bother?)
> 
> Right, I don't need them personally, and things are not connected to
> the CPU, so I can't test that. I would expect anyone who needs this
> functions to add support to the code :)
> 
>>> +The ISL29003 does not have an ID register which could be used to identify
>>> +it, so the detection routine will just try to read from the configured I2C
>>> +addess and consider the device to be present as soon as it ACKs the
>>> +transfer.
>> This is a little nasty given the chances of something else sitting on that
>> address, but not much else you can do.
> 
> True.
> 
>> Some of the following are acting only as documentation. It's
>> a matter of personal preference whether you specify them.
> 
> I defined them to make future feature additions easy ...
> 
>>> +#define ISL29003_REG_COMMAND		0x00
>>> +#define ISL29003_ADC_ENABLED		(1 << 7)
>>> +#define ISL29003_ADC_PD			(1 << 6)
>>> +#define ISL29003_TIMING_INT		(1 << 5)
>>> +#define ISL29003_MODE_SHIFT		(2)
>>> +#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
>>> +#define ISL29003_RES_SHIFT		(0)
>>> +#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)
> 
> [...]
> 
>>> +static int __isl29003_write_reg(struct i2c_client *client,
>>> +				u32 reg, u8 mask, u8 shift, u8 val)
>>> +{
>>> +	struct isl29003_data *data = i2c_get_clientdata(client);
>>> +	int ret = 0;
>>> +	u8 tmp;
>>> +
>>> +	mutex_lock(&data->lock);
>>> +
>>> +	tmp = data->reg_cache[reg];
>>> +	tmp &= ~mask;
>>> +	tmp |= val << shift;
>>> +
>>> +	ret = i2c_smbus_write_byte_data(client, reg, tmp);
>> As i2c_smbus_write_byte_data is defined as returning zero on success
>> this could simply be, if (!ret).
> 
> Fixed.
> 
>>> +}
>>> +
>>> +/* power_state */
>>> +static int isl29003_set_power_state(struct i2c_client *client, int state)
>>> +{
>>> +	int ret;
>>> +
>>> +	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
>>> +		ISL29003_ADC_ENABLED, 0, state);
>> As this is either 0 or less than zero, again if (ret) will suffice.
> 
> Fixed.
> 
>>> +	if (ret < 0)
>>> +		return ret;
>>> +
>>> +	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
>>> +		ISL29003_ADC_PD, 0, ~state);
>> Just return ret hence losing these 2 lines.
> 
> Fixed.
> 
>>> +	if (ret < 0)
>>> +		return ret;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int isl29003_get_power_state(struct i2c_client *client)
>>> +{
>>> +	struct isl29003_data *data = i2c_get_clientdata(client);
>>> +	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
>>> +	return (cmdreg & ISL29003_ADC_ENABLED) && (~cmdreg & ISL29003_ADC_PD);
>>> +}
>> Unfortunately this chip has gain controlled by combination of the i2c
>> accessed registers and an external resitor.  I guess this falls
>> into the category of things to be fixed in userspace.  Perhaps
>> some documentation to indicate this issue would help?
>> (table 9 of data sheet)
> 
> The resistor does not affect the value read from the register - it's
> about integration time only. Or did I get it wrong?
My reading of table 9 on the data sheet is that the full scale
range is affected by the resistor. This is also described in the
bit about calculating lux.
> 
>>> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
>>> +		return -EINVAL;
>> See as there are rather a lot of calls like this, why don't
>> you consider moving this test into the register write command.
>> If the device is powered up then it will get copied to the
>> device. If not, just store it in the cache and it will be
>> written on resume anyway (assuming my understanding of your
>> suspend resume code is right!)
> 
> It's not even necessary to do that - the driver can access all registers
> while the PD bit is set. So the only check is to not read sensor data
> when this condition is matched.
> 
>>> +static ssize_t isl29003_store_power_state(struct device *dev,
>>> +					  struct device_attribute *attr,
>>> +					  const char *buf, size_t count)
>>> +{
>>> +	struct i2c_client *client = to_i2c_client(dev);
>>> +	unsigned long val;
>>> +	int ret;
>>> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
>>> +		return -EINVAL;
>>> +
>>> +	ret = isl29003_set_power_state(client, val);
>>> +	if (ret < 0)
>>> +		return ret;
>> I'd go with more concise "return ret ? ret : count;" but thats down
>> to personal preference.
> 
> Fixed.
> 
>>> +	/* read all the registers once to fill the cache.
>>> +	 * if one of the reads fails, we consider the init failed */
>> Why are you caching registers 4-7?  They are read only data registers
>> and those you use are read on demand anyway.  It's not a problem here,
>> but worth noting that even the first 2 are not simply read / write
>> control registers and hence any caching method has to be very careful
>>   (there is a interrupt flag in control according to the data sheet.)
> 
> You're right. I changed the cache to only store the first 4 registers
> for now. Interrupt handling will need some extra work anyway, so I'll
> leave that for now.
> 
>> If we are restoring registers from cache, why are we reading them?
>>
>>> +	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
>>> +		if (i2c_smbus_read_byte_data(client, i, data->reg_cache[i]) < 0)
>>> +			return -ENODEV;
> 
> Typo - couldn't test the suspend/resume code yet. Fixed now.
> 
> Please see the patch below.
> 
> Thanks again,
> Daniel
> 
> 
>>From 8ee5021834900f312ff26cd2586c18e99af31a5d Mon Sep 17 00:00:00 2001
> From: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> Date: Tue, 10 Mar 2009 16:13:07 +0100
> Subject: [PATCH] Added driver for ISL29003 ambient light sensor
> 
> This patch adds 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 to drivers/misc for now until the industrial I/O framework
> gets merged.
> 
> Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> ---
>  New version with Jonathan Cameron's review notes addressed.
> 
>  Documentation/misc-devices/isl29003 |   62 +++++
>  drivers/misc/Kconfig                |   10 +
>  drivers/misc/Makefile               |    1 +
>  drivers/misc/isl29003.c             |  470 +++++++++++++++++++++++++++++++++++
>  4 files changed, 543 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/misc-devices/isl29003
>  create mode 100644 drivers/misc/isl29003.c
> 
> diff --git a/Documentation/misc-devices/isl29003 b/Documentation/misc-devices/isl29003
> new file mode 100644
> index 0000000..c4ff5f3
> --- /dev/null
> +++ b/Documentation/misc-devices/isl29003
> @@ -0,0 +1,62 @@
> +Kernel driver isl29003
> +=====================
> +
> +Supported chips:
> +* Intersil ISL29003
> +Prefix: 'isl29003'
> +Addresses scanned: none
> +Datasheet:
> +http://www.intersil.com/data/fn/fn7464.pdf
> +
> +Author: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> +
> +
> +Description
> +-----------
> +The ISL29003 is an integrated light sensor with a 16-bit integrating type
> +ADC, I2C user programmable lux range select for optimized counts/lux, and
> +I2C multi-function control and monitoring capabilities. The internal ADC
> +provides 16-bit resolution while rejecting 50Hz and 60Hz flicker caused by
> +artificial light sources.
> +
> +The driver allows to set the lux range, the bit resolution, the operational
> +mode (see below) and the power state of device and can read the current lux
> +value, of course.
> +
> +
> +Detection
> +---------
> +
> +The ISL29003 does not have an ID register which could be used to identify
> +it, so the detection routine will just try to read from the configured I2C
> +addess and consider the device to be present as soon as it ACKs the
> +transfer.
> +
> +
> +Sysfs entries
> +-------------
> +
> +range:
> +	0: 0 lux to 1000 lux (default)
> +	1: 0 lux to 4000 lux
> +	2: 0 lux to 16,000 lux
> +	3: 0 lux to 64,000 lux
> +
> +resolution:
> +	0: 2^16 cycles (default)
> +	1: 2^12 cycles
> +	2: 2^8 cycles
> +	3: 2^4 cycles
> +
> +mode:
> +	0: diode1's current (unsigned 16bit) (default)
> +	1: diode1's current (unsigned 16bit)
> +	2: difference between diodes (l1 - l2, signed 15bit)
> +
> +power_state:
> +	0: device is disabled (default)
> +	1: device is enabled
> +
> +lux (read only):
> +	returns the value from the last sensor reading
> +
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index c64e679..b883b19 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -223,6 +223,16 @@ config DELL_LAPTOP
>  	This driver adds support for rfkill and backlight control to Dell
>  	laptops.
>  
> +config ISL29003
> +	tristate "Intersil ISL29003 ambient light sensor"
> +	depends on I2C
> +	help
> +	  If you say yes here you get support for the Intersil ISL29003
> +	  ambient light sensor.
> +
> +	  This driver can also be built as a module.  If so, the module
> +	  will be called isl29003.
> +
>  source "drivers/misc/c2port/Kconfig"
>  source "drivers/misc/eeprom/Kconfig"
>  
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index bc11998..7871f05 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -18,5 +18,6 @@ obj-$(CONFIG_KGDB_TESTS)	+= kgdbts.o
>  obj-$(CONFIG_SGI_XP)		+= sgi-xp/
>  obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
>  obj-$(CONFIG_HP_ILO)		+= hpilo.o
> +obj-$(CONFIG_ISL29003)		+= isl29003.o
>  obj-$(CONFIG_C2PORT)		+= c2port/
>  obj-y				+= eeprom/
> diff --git a/drivers/misc/isl29003.c b/drivers/misc/isl29003.c
> new file mode 100644
> index 0000000..2e2a592
> --- /dev/null
> +++ b/drivers/misc/isl29003.c
> @@ -0,0 +1,470 @@
> +/*
> + *  isl29003.c - Linux kernel module for
> + * 	Intersil ISL29003 ambient light sensor
> + *
> + *  See file:Documentation/misc-devices/isl29003
> + *
> + *  Copyright (c) 2009 Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> + *
> + *  Based on code written by
> + *  	Rodolfo Giometti <giometti-k2GhghHVRtY@public.gmane.org>
> + *  	Eurotech S.p.A. <info-ymFC7zkAqBI1GQ1Ptb7lUw@public.gmane.org>
> + *
> + *  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>
> +#include <linux/delay.h>
> +
> +#define ISL29003_DRV_NAME	"isl29003"
> +#define DRIVER_VERSION		"1.0"
> +
> +#define ISL29003_REG_COMMAND		0x00
> +#define ISL29003_ADC_ENABLED		(1 << 7)
> +#define ISL29003_ADC_PD			(1 << 6)
> +#define ISL29003_TIMING_INT		(1 << 5)
> +#define ISL29003_MODE_SHIFT		(2)
> +#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
> +#define ISL29003_RES_SHIFT		(0)
> +#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)
> +
> +#define ISL29003_REG_CONTROL		0x01
> +#define ISL29003_INT_FLG		(1 << 5)
> +#define ISL29003_RANGE_SHIFT		(2)
> +#define ISL29003_RANGE_MASK		(0x3 << ISL29003_RANGE_SHIFT)
> +#define ISL29003_INT_PERSISTS_SHIFT	(0)
> +#define ISL29003_INT_PERSISTS_MASK	(0xf << ISL29003_INT_PERSISTS_SHIFT)
> +
> +#define ISL29003_REG_IRQ_THRESH_HI	0x02
> +#define ISL29003_REG_IRQ_THRESH_LO	0x03
> +#define ISL29003_REG_LSB_SENSOR		0x04
> +#define ISL29003_REG_MSB_SENSOR		0x05
> +#define ISL29003_REG_LSB_TIMER		0x06
> +#define ISL29003_REG_MSB_TIMER		0x07
> +
> +#define ISL29003_NUM_CACHABLE_REGS	4
> +
> +struct isl29003_data {
> +	struct i2c_client *client;
> +	struct mutex lock;
> +	u8 reg_cache[ISL29003_NUM_CACHABLE_REGS];
> +};
> +
> +static int gain_range[] = {
> +	1000, 4000, 16000, 64000
> +};
> +
> +/*
> + * register access helpers
> + */
> +
> +static int __isl29003_read_reg(struct i2c_client *client,
> +			       u32 reg, u8 mask, u8 shift)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	return (data->reg_cache[reg] & mask) >> shift;
> +}
> +
> +static int __isl29003_write_reg(struct i2c_client *client,
> +				u32 reg, u8 mask, u8 shift, u8 val)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	int ret = 0;
> +	u8 tmp;
> +
> +	if (reg >= ISL29003_NUM_CACHABLE_REGS)
> +		return -EINVAL;
> +
> +	mutex_lock(&data->lock);
> +
> +	tmp = data->reg_cache[reg];
> +	tmp &= ~mask;
> +	tmp |= val << shift;
> +
> +	ret = i2c_smbus_write_byte_data(client, reg, tmp);
> +	if (!ret)
> +		data->reg_cache[reg] = tmp;
> +
> +	mutex_unlock(&data->lock);
> +	return ret;
> +}
> +
> +/*
> + * internally used functions
> + */
> +
> +/* range */
> +static int isl29003_get_range(struct i2c_client *client)
> +{
> +	return __isl29003_read_reg(client, ISL29003_REG_CONTROL,
> +		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT);
> +}
> +
> +static int isl29003_set_range(struct i2c_client *client, int range)
> +{
> +	return __isl29003_write_reg(client, ISL29003_REG_CONTROL,
> +		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT, range);
> +}
> +
> +/* resolution */
> +static int isl29003_get_resolution(struct i2c_client *client)
> +{
> +	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
> +}
> +
> +static int isl29003_set_resolution(struct i2c_client *client, int res)
> +{
> +	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_RES_MASK, ISL29003_RES_SHIFT, res);
> +}
> +
> +/* mode */
> +static int isl29003_get_mode(struct i2c_client *client)
> +{
> +	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
> +}
> +
> +static int isl29003_set_mode(struct i2c_client *client, int mode)
> +{
> +	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> +		ISL29003_RES_MASK, ISL29003_RES_SHIFT, mode);
> +}
> +
> +/* power_state */
> +static int isl29003_set_power_state(struct i2c_client *client, int state)
> +{
> +	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
> +				ISL29003_ADC_ENABLED | ISL29003_ADC_PD, 0,
> +				state ? ISL29003_ADC_ENABLED : ISL29003_ADC_PD);
> +}
> +
> +static int isl29003_get_power_state(struct i2c_client *client)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
> +	return ~cmdreg & ISL29003_ADC_PD;
> +}
> +
> +static int isl29003_get_adc_value(struct i2c_client *client)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	int lsb, msb, range, bitdepth;
> +
> +	mutex_lock(&data->lock);
> +	lsb = i2c_smbus_read_byte_data(client, ISL29003_REG_LSB_SENSOR);
> +
> +	if (lsb < 0) {
> +		mutex_unlock(&data->lock);
> +		return lsb;
> +	}
> +
> +	msb = i2c_smbus_read_byte_data(client, ISL29003_REG_MSB_SENSOR);
> +	mutex_unlock(&data->lock);
> +
> +	if (msb < 0)
> +		return msb;
> +
> +	range = isl29003_get_range(client);
> +	bitdepth = (4 - isl29003_get_resolution(client)) * 4;
> +	return (((msb << 8) | lsb) * gain_range[range]) >> bitdepth;
> +}
> +
> +/*
> + * sysfs layer
> + */
> +
> +/* range */
> +static ssize_t isl29003_show_range(struct device *dev,
> +				   struct device_attribute *attr, char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	return sprintf(buf, "%i\n", isl29003_get_range(client));
> +}
> +
> +static ssize_t isl29003_store_range(struct device *dev,
> +				    struct device_attribute *attr,
> +				    const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	unsigned long val;
> +	int ret;
> +
> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
> +		return -EINVAL;
> +
> +	ret = isl29003_set_range(client, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
> +		   isl29003_show_range, isl29003_store_range);
> +
> +
> +/* resolution */
> +static ssize_t isl29003_show_resolution(struct device *dev,
> +					struct device_attribute *attr,
> +					char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	return sprintf(buf, "%d\n", isl29003_get_resolution(client));
> +}
> +
> +static ssize_t isl29003_store_resolution(struct device *dev,
> +					 struct device_attribute *attr,
> +					 const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	unsigned long val;
> +	int ret;
> +
> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
> +		return -EINVAL;
> +
> +	ret = isl29003_set_resolution(client, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR(resolution, S_IWUSR | S_IRUGO,
> +		   isl29003_show_resolution, isl29003_store_resolution);
> +
> +/* mode */
> +static ssize_t isl29003_show_mode(struct device *dev,
> +				  struct device_attribute *attr, char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	return sprintf(buf, "%d\n", isl29003_get_mode(client));
> +}
> +
> +static ssize_t isl29003_store_mode(struct device *dev,
> +		struct device_attribute *attr, const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	unsigned long val;
> +	int ret;
> +
> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 2))
> +		return -EINVAL;
> +
> +	ret = isl29003_set_mode(client, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	return count;
> +}
> +
> +static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
> +		   isl29003_show_mode, isl29003_store_mode);
> +
> +
> +/* power state */
> +static ssize_t isl29003_show_power_state(struct device *dev,
> +					 struct device_attribute *attr,
> +					 char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	return sprintf(buf, "%d\n", isl29003_get_power_state(client));
> +}
> +
> +static ssize_t isl29003_store_power_state(struct device *dev,
> +					  struct device_attribute *attr,
> +					  const char *buf, size_t count)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +	unsigned long val;
> +	int ret;
> +
> +	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
> +		return -EINVAL;
> +
> +	ret = isl29003_set_power_state(client, val);
> +	return ret ? ret : count;
> +}
> +
> +static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO,
> +		   isl29003_show_power_state, isl29003_store_power_state);
> +
> +
> +/* lux */
> +static ssize_t isl29003_show_lux(struct device *dev,
> +				 struct device_attribute *attr, char *buf)
> +{
> +	struct i2c_client *client = to_i2c_client(dev);
> +
> +	/* No LUX data if not operational */
> +	if (!isl29003_get_power_state(client))
> +		return -EBUSY;
> +
> +	return sprintf(buf, "%d\n", isl29003_get_adc_value(client));
> +}
> +
> +static DEVICE_ATTR(lux, S_IRUGO, isl29003_show_lux, NULL);
> +
> +static struct attribute *isl29003_attributes[] = {
> +	&dev_attr_range.attr,
> +	&dev_attr_resolution.attr,
> +	&dev_attr_mode.attr,
> +	&dev_attr_power_state.attr,
> +	&dev_attr_lux.attr,
> +	NULL
> +};
> +
> +static const struct attribute_group isl29003_attr_group = {
> +	.attrs = isl29003_attributes,
> +};
> +
> +static int isl29003_init_client(struct i2c_client *client)
> +{
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +	int i;
> +
> +	/* read all the registers once to fill the cache.
> +	 * if one of the reads fails, we consider the init failed */
> +	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++) {
> +		int v = i2c_smbus_read_byte_data(client, i);
> +		if (v < 0)
> +			return -ENODEV;
> +
> +		data->reg_cache[i] = v;
> +	}
> +
> +	/* set defaults */
> +	isl29003_set_range(client, 0);
> +	isl29003_set_resolution(client, 0);
> +	isl29003_set_mode(client, 0);
> +	isl29003_set_power_state(client, 0);
> +
> +	return 0;
> +}
> +
> +/*
> + * I2C layer
> + */
> +
> +static int __devinit isl29003_probe(struct i2c_client *client,
> +				    const struct i2c_device_id *id)
> +{
> +	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
> +	struct isl29003_data *data;
> +	int err = 0;
> +
> +	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
> +		return -EIO;
> +
> +	data = kzalloc(sizeof(struct isl29003_data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	data->client = client;
> +	i2c_set_clientdata(client, data);
> +	mutex_init(&data->lock);
> +
> +	/* initialize the ISL29003 chip */
> +	err = isl29003_init_client(client);
> +	if (err)
> +		goto exit_kfree;
> +
> +	/* register sysfs hooks */
> +	err = sysfs_create_group(&client->dev.kobj, &isl29003_attr_group);
> +	if (err)
> +		goto exit_kfree;
> +
> +	dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);
> +	return 0;
> +
> +exit_kfree:
> +	kfree(data);
> +	return err;
> +}
> +
> +static int __devexit isl29003_remove(struct i2c_client *client)
> +{
> +	sysfs_remove_group(&client->dev.kobj, &isl29003_attr_group);
> +	isl29003_set_power_state(client, 0);
> +	kfree(i2c_get_clientdata(client));
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int isl29003_suspend(struct i2c_client *client, pm_message_t mesg)
> +{
> +	return isl29003_set_power_state(client, 0);
> +}
> +
> +static int isl29003_resume(struct i2c_client *client)
> +{
> +	int i;
> +	struct isl29003_data *data = i2c_get_clientdata(client);
> +
> +	/* restore registers from cache */
> +	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
> +		if (!i2c_smbus_write_byte_data(client, i, data->reg_cache[i]))
> +			return -EIO;
> +
> +	return 0;
> +}
> +
> +#else
> +#define isl29003_suspend	NULL
> +#define isl29003_resume		NULL
> +#endif /* CONFIG_PM */
> +
> +static const struct i2c_device_id isl29003_id[] = {
> +	{ "isl29003", 0 },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(i2c, isl29003_id);
> +
> +static struct i2c_driver isl29003_driver = {
> +	.driver = {
> +		.name	= ISL29003_DRV_NAME,
> +		.owner	= THIS_MODULE,
> +	},
> +	.suspend = isl29003_suspend,
> +	.resume	= isl29003_resume,
> +	.probe	= isl29003_probe,
> +	.remove	= __devexit_p(isl29003_remove),
> +	.id_table = isl29003_id,
> +};
> +
> +static int __init isl29003_init(void)
> +{
> +	return i2c_add_driver(&isl29003_driver);
> +}
> +
> +static void __exit isl29003_exit(void)
> +{
> +	i2c_del_driver(&isl29003_driver);
> +}
> +
> +MODULE_AUTHOR("Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>");
> +MODULE_DESCRIPTION("ISL29003 ambient light sensor driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_VERSION(DRIVER_VERSION);
> +
> +module_init(isl29003_init);
> +module_exit(isl29003_exit);
> +

All looks fine to me.

Acked-by: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>

Cheers,

Jonathan

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-11  0:31                                                   ` Daniel Mack
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel Mack @ 2009-03-11  0:31 UTC (permalink / raw)
  To: Jonathan Cameron; +Cc: Jean Delvare, linux-i2c, linux-kernel

On Tue, Mar 10, 2009 at 05:49:55PM +0000, Jonathan Cameron wrote:
> >>From 8ee5021834900f312ff26cd2586c18e99af31a5d Mon Sep 17 00:00:00 2001
> > From: Daniel Mack <daniel@caiaq.de>
> > Date: Tue, 10 Mar 2009 16:13:07 +0100
> > Subject: [PATCH] Added driver for ISL29003 ambient light sensor
> > 
> > This patch adds 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 to drivers/misc for now until the industrial I/O framework
> > gets merged.
> > 
> > Signed-off-by: Daniel Mack <daniel@caiaq.de>

[...]

> All looks fine to me.
> 
> Acked-by: Jonathan Cameron <jic23@cam.ac.uk>

Ok, wonderful :)

What's the merge path for that, who'll queue it?

Thanks,
Daniel


^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-11  0:31                                                   ` Daniel Mack
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel Mack @ 2009-03-11  0:31 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Jean Delvare, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

On Tue, Mar 10, 2009 at 05:49:55PM +0000, Jonathan Cameron wrote:
> >>From 8ee5021834900f312ff26cd2586c18e99af31a5d Mon Sep 17 00:00:00 2001
> > From: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> > Date: Tue, 10 Mar 2009 16:13:07 +0100
> > Subject: [PATCH] Added driver for ISL29003 ambient light sensor
> > 
> > This patch adds 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 to drivers/misc for now until the industrial I/O framework
> > gets merged.
> > 
> > Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>

[...]

> All looks fine to me.
> 
> Acked-by: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>

Ok, wonderful :)

What's the merge path for that, who'll queue it?

Thanks,
Daniel

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-11  4:28                                                     ` Andrew Morton
  0 siblings, 0 replies; 49+ messages in thread
From: Andrew Morton @ 2009-03-11  4:28 UTC (permalink / raw)
  To: Daniel Mack; +Cc: Jonathan Cameron, Jean Delvare, linux-i2c, linux-kernel

On Wed, 11 Mar 2009 01:31:55 +0100 Daniel Mack <daniel@caiaq.de> wrote:

> On Tue, Mar 10, 2009 at 05:49:55PM +0000, Jonathan Cameron wrote:
> > >>From 8ee5021834900f312ff26cd2586c18e99af31a5d Mon Sep 17 00:00:00 2001
> > > From: Daniel Mack <daniel@caiaq.de>
> > > Date: Tue, 10 Mar 2009 16:13:07 +0100
> > > Subject: [PATCH] Added driver for ISL29003 ambient light sensor
> > > 
> > > This patch adds 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 to drivers/misc for now until the industrial I/O framework
> > > gets merged.
> > > 
> > > Signed-off-by: Daniel Mack <daniel@caiaq.de>
> 
> [...]
> 
> > All looks fine to me.
> > 
> > Acked-by: Jonathan Cameron <jic23@cam.ac.uk>

I cannot find this review which Jonathan performed.  Fumbled
reply-to-all, or did vger break?

> Ok, wonderful :)
> 
> What's the merge path for that, who'll queue it?

I can take care of it.

Was any thought given to the CONFIG_SYSFS=n situation?

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-11  4:28                                                     ` Andrew Morton
  0 siblings, 0 replies; 49+ messages in thread
From: Andrew Morton @ 2009-03-11  4:28 UTC (permalink / raw)
  To: Daniel Mack
  Cc: Jonathan Cameron, Jean Delvare, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

On Wed, 11 Mar 2009 01:31:55 +0100 Daniel Mack <daniel-rDUAYElUppE@public.gmane.org> wrote:

> On Tue, Mar 10, 2009 at 05:49:55PM +0000, Jonathan Cameron wrote:
> > >>From 8ee5021834900f312ff26cd2586c18e99af31a5d Mon Sep 17 00:00:00 2001
> > > From: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> > > Date: Tue, 10 Mar 2009 16:13:07 +0100
> > > Subject: [PATCH] Added driver for ISL29003 ambient light sensor
> > > 
> > > This patch adds 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 to drivers/misc for now until the industrial I/O framework
> > > gets merged.
> > > 
> > > Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
> 
> [...]
> 
> > All looks fine to me.
> > 
> > Acked-by: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>

I cannot find this review which Jonathan performed.  Fumbled
reply-to-all, or did vger break?

> Ok, wonderful :)
> 
> What's the merge path for that, who'll queue it?

I can take care of it.

Was any thought given to the CONFIG_SYSFS=n situation?

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-11  8:32                                                       ` Daniel Mack
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel Mack @ 2009-03-11  8:32 UTC (permalink / raw)
  To: Andrew Morton; +Cc: Jonathan Cameron, Jean Delvare, linux-i2c, linux-kernel

On Tue, Mar 10, 2009 at 09:28:26PM -0700, Andrew Morton wrote:
> On Wed, 11 Mar 2009 01:31:55 +0100 Daniel Mack <daniel@caiaq.de> wrote:
> > > All looks fine to me.
> > > 
> > > Acked-by: Jonathan Cameron <jic23@cam.ac.uk>
> 
> I cannot find this review which Jonathan performed.  Fumbled
> reply-to-all, or did vger break?

Jonathan replied to the posting on linux-i2c and linux-kernel wasn't in
the loop, sorry. Find his posting here:

	http://marc.info/?l=linux-i2c&m=123669064909549&w=2

> I can take care of it.
> 
> Was any thought given to the CONFIG_SYSFS=n situation?

You're right, it should depend on that. You want me so send a patch on
top of the other or one that replaces it?

Thanks,
Daniel


^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-11  8:32                                                       ` Daniel Mack
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel Mack @ 2009-03-11  8:32 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Jonathan Cameron, Jean Delvare, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

On Tue, Mar 10, 2009 at 09:28:26PM -0700, Andrew Morton wrote:
> On Wed, 11 Mar 2009 01:31:55 +0100 Daniel Mack <daniel-rDUAYElUppE@public.gmane.org> wrote:
> > > All looks fine to me.
> > > 
> > > Acked-by: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
> 
> I cannot find this review which Jonathan performed.  Fumbled
> reply-to-all, or did vger break?

Jonathan replied to the posting on linux-i2c and linux-kernel wasn't in
the loop, sorry. Find his posting here:

	http://marc.info/?l=linux-i2c&m=123669064909549&w=2

> I can take care of it.
> 
> Was any thought given to the CONFIG_SYSFS=n situation?

You're right, it should depend on that. You want me so send a patch on
top of the other or one that replaces it?

Thanks,
Daniel

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-11 16:32                                                         ` Andrew Morton
  0 siblings, 0 replies; 49+ messages in thread
From: Andrew Morton @ 2009-03-11 16:32 UTC (permalink / raw)
  To: Daniel Mack; +Cc: Jonathan Cameron, Jean Delvare, linux-i2c, linux-kernel

On Wed, 11 Mar 2009 09:32:52 +0100 Daniel Mack <daniel@caiaq.de> wrote:

> On Tue, Mar 10, 2009 at 09:28:26PM -0700, Andrew Morton wrote:
> > On Wed, 11 Mar 2009 01:31:55 +0100 Daniel Mack <daniel@caiaq.de> wrote:
> > > > All looks fine to me.
> > > > 
> > > > Acked-by: Jonathan Cameron <jic23@cam.ac.uk>
> > 
> > I cannot find this review which Jonathan performed.  Fumbled
> > reply-to-all, or did vger break?
> 
> Jonathan replied to the posting on linux-i2c and linux-kernel wasn't in
> the loop, sorry. Find his posting here:
> 
> 	http://marc.info/?l=linux-i2c&m=123669064909549&w=2

OK.  Please always use reply-to-all, guys.

> > I can take care of it.
> > 
> > Was any thought given to the CONFIG_SYSFS=n situation?
> 
> You're right, it should depend on that. You want me so send a patch on
> top of the other or one that replaces it?

Either way is OK by me at this time.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-11 16:32                                                         ` Andrew Morton
  0 siblings, 0 replies; 49+ messages in thread
From: Andrew Morton @ 2009-03-11 16:32 UTC (permalink / raw)
  To: Daniel Mack
  Cc: Jonathan Cameron, Jean Delvare, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

On Wed, 11 Mar 2009 09:32:52 +0100 Daniel Mack <daniel-rDUAYElUppE@public.gmane.org> wrote:

> On Tue, Mar 10, 2009 at 09:28:26PM -0700, Andrew Morton wrote:
> > On Wed, 11 Mar 2009 01:31:55 +0100 Daniel Mack <daniel-rDUAYElUppE@public.gmane.org> wrote:
> > > > All looks fine to me.
> > > > 
> > > > Acked-by: Jonathan Cameron <jic23-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
> > 
> > I cannot find this review which Jonathan performed.  Fumbled
> > reply-to-all, or did vger break?
> 
> Jonathan replied to the posting on linux-i2c and linux-kernel wasn't in
> the loop, sorry. Find his posting here:
> 
> 	http://marc.info/?l=linux-i2c&m=123669064909549&w=2

OK.  Please always use reply-to-all, guys.

> > I can take care of it.
> > 
> > Was any thought given to the CONFIG_SYSFS=n situation?
> 
> You're right, it should depend on that. You want me so send a patch on
> top of the other or one that replaces it?

Either way is OK by me at this time.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-11 16:43                                                           ` Daniel Mack
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel Mack @ 2009-03-11 16:43 UTC (permalink / raw)
  To: Andrew Morton; +Cc: Jonathan Cameron, Jean Delvare, linux-i2c, linux-kernel

On Wed, Mar 11, 2009 at 09:32:34AM -0700, Andrew Morton wrote:
> > Jonathan replied to the posting on linux-i2c and linux-kernel wasn't in
> > the loop, sorry. Find his posting here:
> > 
> > 	http://marc.info/?l=linux-i2c&m=123669064909549&w=2
> 
> OK.  Please always use reply-to-all, guys.

The thread moved from linux-i2c to linux-kernel, hence the trouble.

> > You're right, it should depend on that. You want me so send a patch on
> > top of the other or one that replaces it?
> 
> Either way is OK by me at this time.

Ok, I chose the shorter variant :) See the patch below.

Thanks,
Daniel


>From 9143c849682280ca3b0544e580bc90241c385927 Mon Sep 17 00:00:00 2001
From: Daniel Mack <daniel@caiaq.de>
Date: Wed, 11 Mar 2009 17:39:13 +0100
Subject: [PATCH] make isl29003 depend on sysfs


Signed-off-by: Daniel Mack <daniel@caiaq.de>
---
 drivers/misc/Kconfig |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b883b19..16e1815 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -225,7 +225,7 @@ config DELL_LAPTOP
 
 config ISL29003
 	tristate "Intersil ISL29003 ambient light sensor"
-	depends on I2C
+	depends on I2C && SYSFS
 	help
 	  If you say yes here you get support for the Intersil ISL29003
 	  ambient light sensor.
-- 
1.6.2


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-11 16:43                                                           ` Daniel Mack
  0 siblings, 0 replies; 49+ messages in thread
From: Daniel Mack @ 2009-03-11 16:43 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Jonathan Cameron, Jean Delvare, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

On Wed, Mar 11, 2009 at 09:32:34AM -0700, Andrew Morton wrote:
> > Jonathan replied to the posting on linux-i2c and linux-kernel wasn't in
> > the loop, sorry. Find his posting here:
> > 
> > 	http://marc.info/?l=linux-i2c&m=123669064909549&w=2
> 
> OK.  Please always use reply-to-all, guys.

The thread moved from linux-i2c to linux-kernel, hence the trouble.

> > You're right, it should depend on that. You want me so send a patch on
> > top of the other or one that replaces it?
> 
> Either way is OK by me at this time.

Ok, I chose the shorter variant :) See the patch below.

Thanks,
Daniel


>From 9143c849682280ca3b0544e580bc90241c385927 Mon Sep 17 00:00:00 2001
From: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
Date: Wed, 11 Mar 2009 17:39:13 +0100
Subject: [PATCH] make isl29003 depend on sysfs


Signed-off-by: Daniel Mack <daniel-rDUAYElUppE@public.gmane.org>
---
 drivers/misc/Kconfig |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b883b19..16e1815 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -225,7 +225,7 @@ config DELL_LAPTOP
 
 config ISL29003
 	tristate "Intersil ISL29003 ambient light sensor"
-	depends on I2C
+	depends on I2C && SYSFS
 	help
 	  If you say yes here you get support for the Intersil ISL29003
 	  ambient light sensor.
-- 
1.6.2

^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [PATCH] Added driver for ISL29003 ambient light sensor
  2009-03-10 11:19 Daniel Mack
@ 2009-03-15  5:41 ` James Cloos
  0 siblings, 0 replies; 49+ messages in thread
From: James Cloos @ 2009-03-15  5:41 UTC (permalink / raw)
  To: Daniel Mack; +Cc: linux-kernel

>>>>> "Daniel" == Daniel Mack <daniel@caiaq.de> writes:

Trivial comment on the docs:

Daniel> +mode:
Daniel> +	0: diode1's current (unsigned 16bit) (default)
Daniel> +	1: diode1's current (unsigned 16bit)
Daniel> +	2: difference between diodes (l1 - l2, signed 15bit)

That has diode1 twice.  Should it, instead, be:

+mode:
+	0: diode1's current (unsigned 16bit) (default)
+	1: diode2's current (unsigned 16bit)
+	2: difference between diodes (l1 - l2, signed 15bit)

?

-JimC
-- 
James Cloos <cloos@jhcloos.com>         OpenPGP: 1024D/ED7DAEA6

^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH] Added driver for ISL29003 ambient light sensor
@ 2009-03-10 11:19 Daniel Mack
  2009-03-15  5:41 ` James Cloos
  0 siblings, 1 reply; 49+ messages in thread
From: Daniel Mack @ 2009-03-10 11:19 UTC (permalink / raw)
  To: linux-kernel; +Cc: Daniel Mack

This patch adds 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 to drivers/misc for now until the industrial I/O framework
gets merged.

Signed-off-by: Daniel Mack <daniel@caiaq.de>
---
 (The discussion about where to put the files for this has some
  history on linux-i2c list and the verdict there was to re-post
  the driver to the LKML for further review as both drivers/hwmon and
  drivers/i2c/chips are unsuitable places for it)

 Documentation/misc-devices/isl29003 |   62 +++++
 drivers/misc/Kconfig                |   10 +
 drivers/misc/Makefile               |    1 +
 drivers/misc/isl29003.c             |  486 +++++++++++++++++++++++++++++++++++
 4 files changed, 559 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/misc-devices/isl29003
 create mode 100644 drivers/misc/isl29003.c

diff --git a/Documentation/misc-devices/isl29003 b/Documentation/misc-devices/isl29003
new file mode 100644
index 0000000..c4ff5f3
--- /dev/null
+++ b/Documentation/misc-devices/isl29003
@@ -0,0 +1,62 @@
+Kernel driver isl29003
+=====================
+
+Supported chips:
+* Intersil ISL29003
+Prefix: 'isl29003'
+Addresses scanned: none
+Datasheet:
+http://www.intersil.com/data/fn/fn7464.pdf
+
+Author: Daniel Mack <daniel@caiaq.de>
+
+
+Description
+-----------
+The ISL29003 is an integrated light sensor with a 16-bit integrating type
+ADC, I2C user programmable lux range select for optimized counts/lux, and
+I2C multi-function control and monitoring capabilities. The internal ADC
+provides 16-bit resolution while rejecting 50Hz and 60Hz flicker caused by
+artificial light sources.
+
+The driver allows to set the lux range, the bit resolution, the operational
+mode (see below) and the power state of device and can read the current lux
+value, of course.
+
+
+Detection
+---------
+
+The ISL29003 does not have an ID register which could be used to identify
+it, so the detection routine will just try to read from the configured I2C
+addess and consider the device to be present as soon as it ACKs the
+transfer.
+
+
+Sysfs entries
+-------------
+
+range:
+	0: 0 lux to 1000 lux (default)
+	1: 0 lux to 4000 lux
+	2: 0 lux to 16,000 lux
+	3: 0 lux to 64,000 lux
+
+resolution:
+	0: 2^16 cycles (default)
+	1: 2^12 cycles
+	2: 2^8 cycles
+	3: 2^4 cycles
+
+mode:
+	0: diode1's current (unsigned 16bit) (default)
+	1: diode1's current (unsigned 16bit)
+	2: difference between diodes (l1 - l2, signed 15bit)
+
+power_state:
+	0: device is disabled (default)
+	1: device is enabled
+
+lux (read only):
+	returns the value from the last sensor reading
+
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c64e679..b883b19 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -223,6 +223,16 @@ config DELL_LAPTOP
 	This driver adds support for rfkill and backlight control to Dell
 	laptops.
 
+config ISL29003
+	tristate "Intersil ISL29003 ambient light sensor"
+	depends on I2C
+	help
+	  If you say yes here you get support for the Intersil ISL29003
+	  ambient light sensor.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called isl29003.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index bc11998..7871f05 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -18,5 +18,6 @@ obj-$(CONFIG_KGDB_TESTS)	+= kgdbts.o
 obj-$(CONFIG_SGI_XP)		+= sgi-xp/
 obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
 obj-$(CONFIG_HP_ILO)		+= hpilo.o
+obj-$(CONFIG_ISL29003)		+= isl29003.o
 obj-$(CONFIG_C2PORT)		+= c2port/
 obj-y				+= eeprom/
diff --git a/drivers/misc/isl29003.c b/drivers/misc/isl29003.c
new file mode 100644
index 0000000..9c6fb0e
--- /dev/null
+++ b/drivers/misc/isl29003.c
@@ -0,0 +1,486 @@
+/*
+ *  isl29003.c - Linux kernel module for
+ * 	Intersil ISL29003 ambient light sensor
+ *
+ *  See file:Documentation/misc-devices/isl29003
+ *
+ *  Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
+ *
+ *  Based on code written by
+ *  	Rodolfo Giometti <giometti@linux.it>
+ *  	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>
+#include <linux/delay.h>
+
+#define ISL29003_DRV_NAME	"isl29003"
+#define DRIVER_VERSION		"1.0"
+
+#define ISL29003_REG_COMMAND		0x00
+#define ISL29003_ADC_ENABLED		(1 << 7)
+#define ISL29003_ADC_PD			(1 << 6)
+#define ISL29003_TIMING_INT		(1 << 5)
+#define ISL29003_MODE_SHIFT		(2)
+#define ISL29003_MODE_MASK		(0x3 << ISL29003_MODE_SHIFT)
+#define ISL29003_RES_SHIFT		(0)
+#define ISL29003_RES_MASK		(0x3 << ISL29003_RES_SHIFT)
+
+#define ISL29003_REG_CONTROL		0x01
+#define ISL29003_INT_FLG		(1 << 5)
+#define ISL29003_RANGE_SHIFT		(2)
+#define ISL29003_RANGE_MASK		(0x3 << ISL29003_RANGE_SHIFT)
+#define ISL29003_INT_PERSISTS_SHIFT	(0)
+#define ISL29003_INT_PERSISTS_MASK	(0xf << ISL29003_INT_PERSISTS_SHIFT)
+
+#define ISL29003_REG_IRQ_THRESH_HI	0x02
+#define ISL29003_REG_IRQ_THRESH_LO	0x03
+#define ISL29003_REG_LSB_SENSOR		0x04
+#define ISL29003_REG_MSB_SENSOR		0x05
+#define ISL29003_REG_LSB_TIMER		0x06
+#define ISL29003_REG_MSB_TIMER		0x07
+
+struct isl29003_data {
+	struct i2c_client *client;
+	struct mutex lock;
+	u8 reg_cache[8];
+};
+
+static int gain_range[] = {
+	1000, 4000, 16000, 64000
+};
+
+/*
+ * register access helpers
+ */
+
+static int __isl29003_read_reg(struct i2c_client *client,
+			       u32 reg, u8 mask, u8 shift)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	return (data->reg_cache[reg] & mask) >> shift;
+}
+
+static int __isl29003_write_reg(struct i2c_client *client,
+				u32 reg, u8 mask, u8 shift, u8 val)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int ret = 0;
+	u8 tmp;
+
+	mutex_lock(&data->lock);
+
+	tmp = data->reg_cache[reg];
+	tmp &= ~mask;
+	tmp |= val << shift;
+
+	ret = i2c_smbus_write_byte_data(client, reg, tmp);
+	if (ret >= 0)
+		data->reg_cache[reg] = tmp;
+
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+/*
+ * internally used functions
+ */
+
+/* range */
+static int isl29003_get_range(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT);
+}
+
+static int isl29003_set_range(struct i2c_client *client, int range)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_CONTROL,
+		ISL29003_RANGE_MASK, ISL29003_RANGE_SHIFT, range);
+}
+
+/* resolution */
+static int isl29003_get_resolution(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_resolution(struct i2c_client *client, int res)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, res);
+}
+
+/* mode */
+static int isl29003_get_mode(struct i2c_client *client)
+{
+	return __isl29003_read_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT);
+}
+
+static int isl29003_set_mode(struct i2c_client *client, int mode)
+{
+	return __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_RES_MASK, ISL29003_RES_SHIFT, mode);
+}
+
+/* power_state */
+static int isl29003_set_power_state(struct i2c_client *client, int state)
+{
+	int ret;
+
+	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_ADC_ENABLED, 0, state);
+	if (ret < 0)
+		return ret;
+
+	ret = __isl29003_write_reg(client, ISL29003_REG_COMMAND,
+		ISL29003_ADC_PD, 0, ~state);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int isl29003_get_power_state(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	u8 cmdreg = data->reg_cache[ISL29003_REG_COMMAND];
+	return (cmdreg & ISL29003_ADC_ENABLED) && (~cmdreg & ISL29003_ADC_PD);
+}
+
+static int isl29003_get_adc_value(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int lsb, msb, range, bitdepth;
+
+	mutex_lock(&data->lock);
+	lsb = i2c_smbus_read_byte_data(client, ISL29003_REG_LSB_SENSOR);
+
+	if (lsb < 0) {
+		mutex_unlock(&data->lock);
+		return lsb;
+	}
+
+	msb = i2c_smbus_read_byte_data(client, ISL29003_REG_MSB_SENSOR);
+	mutex_unlock(&data->lock);
+
+	if (msb < 0)
+		return msb;
+
+	range = isl29003_get_range(client);
+	bitdepth = (4 - isl29003_get_resolution(client)) * 4;
+	return (((msb << 8) | lsb) * gain_range[range]) >> bitdepth;
+}
+
+/*
+ * sysfs layer
+ */
+
+/* range */
+static ssize_t isl29003_show_range(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%i\n", isl29003_get_range(client));
+}
+
+static ssize_t isl29003_store_range(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	ret = isl29003_set_range(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
+		   isl29003_show_range, isl29003_store_range);
+
+
+/* resolution */
+static ssize_t isl29003_show_resolution(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_resolution(client));
+}
+
+static ssize_t isl29003_store_resolution(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	ret = isl29003_set_resolution(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(resolution, S_IWUSR | S_IRUGO,
+		   isl29003_show_resolution, isl29003_store_resolution);
+
+/* mode */
+static ssize_t isl29003_show_mode(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_mode(client));
+}
+
+static ssize_t isl29003_store_mode(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 2))
+		return -EINVAL;
+
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	ret = isl29003_set_mode(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
+		   isl29003_show_mode, isl29003_store_mode);
+
+
+/* power state */
+static ssize_t isl29003_show_power_state(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return sprintf(buf, "%d\n", isl29003_get_power_state(client));
+}
+
+static ssize_t isl29003_store_power_state(struct device *dev,
+					  struct device_attribute *attr,
+					  const char *buf, size_t count)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	unsigned long val;
+	int ret;
+
+	if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
+		return -EINVAL;
+
+	ret = isl29003_set_power_state(client, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO,
+		   isl29003_show_power_state, isl29003_store_power_state);
+
+
+/* lux */
+static ssize_t isl29003_show_lux(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	/* No LUX data if not operational */
+	if (!isl29003_get_power_state(client))
+		return -EBUSY;
+
+	return sprintf(buf, "%d\n", isl29003_get_adc_value(client));
+}
+
+static DEVICE_ATTR(lux, S_IRUGO, isl29003_show_lux, NULL);
+
+static struct attribute *isl29003_attributes[] = {
+	&dev_attr_range.attr,
+	&dev_attr_resolution.attr,
+	&dev_attr_mode.attr,
+	&dev_attr_power_state.attr,
+	&dev_attr_lux.attr,
+	NULL
+};
+
+static const struct attribute_group isl29003_attr_group = {
+	.attrs = isl29003_attributes,
+};
+
+static int isl29003_init_client(struct i2c_client *client)
+{
+	struct isl29003_data *data = i2c_get_clientdata(client);
+	int i;
+
+	/* read all the registers once to fill the cache.
+	 * if one of the reads fails, we consider the init failed */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++) {
+		int v = i2c_smbus_read_byte_data(client, i);
+		if (v < 0)
+			return -ENODEV;
+
+		data->reg_cache[i] = v;
+	}
+
+	/* set defaults */
+	isl29003_set_range(client, 0);
+	isl29003_set_resolution(client, 0);
+	isl29003_set_mode(client, 0);
+	isl29003_set_power_state(client, 0);
+
+	return 0;
+}
+
+/*
+ * I2C layer
+ */
+
+static int __devinit isl29003_probe(struct i2c_client *client,
+				    const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct isl29003_data *data;
+	int err = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+		return -EIO;
+
+	data = kzalloc(sizeof(struct isl29003_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->client = client;
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->lock);
+
+	/* initialize the ISL29003 chip */
+	err = isl29003_init_client(client);
+	if (err)
+		goto exit_kfree;
+
+	/* register sysfs hooks */
+	err = sysfs_create_group(&client->dev.kobj, &isl29003_attr_group);
+	if (err)
+		goto exit_kfree;
+
+	dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);
+	return 0;
+
+exit_kfree:
+	kfree(data);
+	return err;
+}
+
+static int __devexit isl29003_remove(struct i2c_client *client)
+{
+	sysfs_remove_group(&client->dev.kobj, &isl29003_attr_group);
+	isl29003_set_power_state(client, 0);
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int isl29003_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	return isl29003_set_power_state(client, 0);
+}
+
+static int isl29003_resume(struct i2c_client *client)
+{
+	int i;
+
+	/* restore registers from cache */
+	for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
+		if (i2c_smbus_read_byte_data(client, i, data->reg_cache[i]) < 0)
+			return -ENODEV;
+
+	return 0;
+}
+
+#else
+#define isl29003_suspend	NULL
+#define isl29003_resume		NULL
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id isl29003_id[] = {
+	{ "isl29003", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, isl29003_id);
+
+static struct i2c_driver isl29003_driver = {
+	.driver = {
+		.name	= ISL29003_DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+	.suspend = isl29003_suspend,
+	.resume	= isl29003_resume,
+	.probe	= isl29003_probe,
+	.remove	= __devexit_p(isl29003_remove),
+	.id_table = isl29003_id,
+};
+
+static int __init isl29003_init(void)
+{
+	return i2c_add_driver(&isl29003_driver);
+}
+
+static void __exit isl29003_exit(void)
+{
+	i2c_del_driver(&isl29003_driver);
+}
+
+MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
+MODULE_DESCRIPTION("ISL29003 ambient light sensor driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(isl29003_init);
+module_exit(isl29003_exit);
+
-- 
1.6.2


^ permalink raw reply related	[flat|nested] 49+ messages in thread

end of thread, other threads:[~2009-03-15  5:53 UTC | newest]

Thread overview: 49+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-02-25 20:10 [PATCH] Added driver for ISL29003 ambient light sensor Daniel Mack
     [not found] ` <1235592618-10880-1-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
2009-02-25 20:19   ` Daniel Mack
2009-02-25 20:21   ` Jean Delvare
     [not found]     ` <20090225212119.67402391-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2009-02-25 23:40       ` Daniel Mack
     [not found]         ` <20090225234004.GA19039-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
2009-02-26  8:50           ` Jean Delvare
2009-02-25 23:57       ` Daniel Mack
     [not found]         ` <1235606248-13004-1-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
2009-02-26  8:48           ` Jean Delvare
     [not found]             ` <20090226094857.50d0710d-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2009-02-26  9:06               ` Daniel Mack
     [not found]                 ` <20090226090651.GA20531-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
2009-02-26  9:42                   ` Jean Delvare
     [not found]                     ` <20090226104204.21dd9616-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2009-02-26  9:57                       ` Daniel Mack
     [not found]                         ` <20090226095718.GA24575-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
2009-02-26 13:24                           ` [PATCH 1/3] Added empty directories for hardware sensors Daniel Mack
     [not found]                             ` <1235654648-3450-1-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
2009-02-26 13:24                               ` [PATCH 2/3] Added driver for ISL29003 ambient light sensor Daniel Mack
     [not found]                                 ` <1235654648-3450-2-git-send-email-daniel-rDUAYElUppE@public.gmane.org>
2009-02-26 13:24                                   ` [PATCH 3/3] move tsl2550.c to drivers/sensors Daniel Mack
2009-02-28 12:40                               ` [PATCH 1/3] Added empty directories for hardware sensors Daniel Mack
     [not found]                                 ` <20090228124048.GB18336-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
2009-03-02  5:22                                   ` Trilok Soni
     [not found]                                     ` <5d5443650903012122p795d51cdr18c1a79c606cd496-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2009-03-02 10:12                                       ` Daniel Mack
2009-03-06 16:00               ` [PATCH] Added driver for ISL29003 ambient light sensor Daniel Mack
     [not found]                 ` <20090306160042.GA17800-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
2009-03-06 17:24                   ` Jean Delvare
     [not found]                     ` <20090306182432.338b0244-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2009-03-06 18:09                       ` Jonathan Cameron
     [not found]                         ` <49B166E2.5060909-KWPb1pKIrIJaa/9Udqfwiw@public.gmane.org>
2009-03-06 18:54                           ` Daniel Mack
2009-03-06 18:14                       ` Daniel Mack
     [not found]                         ` <20090306181409.GA20135-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
2009-03-07 10:54                           ` Jean Delvare
     [not found]                             ` <20090307115449.32f0ab26-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2009-03-07 12:06                               ` Daniel Mack
     [not found]                                 ` <20090307120631.GA25276-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
2009-03-07 12:08                                   ` Jean Delvare
     [not found]                                     ` <20090307130818.367bcaad-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2009-03-09 21:09                                       ` Daniel Mack
     [not found]                                         ` <20090309210901.GC31367-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
2009-03-10  8:56                                           ` Jean Delvare
     [not found]                                             ` <20090310095655.3850f0d5-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2009-03-10  9:00                                               ` Daniel Mack
     [not found]                                                 ` <20090310090007.GB3263-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
2009-03-10 10:10                                                   ` Jean Delvare
     [not found]                                                     ` <20090310111014.523df33e-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2009-03-10 10:25                                                       ` Daniel Mack
     [not found]                                                         ` <20090310102534.GC9564-ahpEBR4enfnCULTFXS99ULNAH6kLmebB@public.gmane.org>
2009-03-10 10:40                                                           ` Jean Delvare
     [not found]                                                             ` <20090310114011.012d3fcc-ig7AzVSIIG7kN2dkZ6Wm7A@public.gmane.org>
2009-03-10 11:01                                                               ` Trilok Soni
     [not found]                                                                 ` <5d5443650903100401j35f13459kcc03adb7dc7e9297-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2009-03-10 12:11                                                                   ` Jonathan Cameron
2009-03-10 13:11                                           ` Jonathan Cameron
2009-03-10 15:15                                             ` Daniel Mack
2009-03-10 15:15                                               ` Daniel Mack
2009-03-10 17:49                                               ` Jonathan Cameron
2009-03-10 17:49                                                 ` Jonathan Cameron
2009-03-11  0:31                                                 ` Daniel Mack
2009-03-11  0:31                                                   ` Daniel Mack
2009-03-11  4:28                                                   ` Andrew Morton
2009-03-11  4:28                                                     ` Andrew Morton
2009-03-11  8:32                                                     ` Daniel Mack
2009-03-11  8:32                                                       ` Daniel Mack
2009-03-11 16:32                                                       ` Andrew Morton
2009-03-11 16:32                                                         ` Andrew Morton
2009-03-11 16:43                                                         ` Daniel Mack
2009-03-11 16:43                                                           ` Daniel Mack
2009-03-10 11:19 Daniel Mack
2009-03-15  5:41 ` James Cloos

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.