All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] mfd: add LM3533 lighting-power chip driver
@ 2012-04-20 15:30 ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold

These patches (against v3.4-rc3) add support for the National Semiconductor /
Texas Instruments LM3533 lighting-power chip.

This multi-function device has four LEDs, two backlights and an ambient-light
sensor.

The LEDs and backlights can be controlled directly, through PWM input,
or by the on-chip ambient light sensor. Hardware-accelerated blinking is
provided for the LEDs.

ALS control is done through defining five light zones and three sets of
corresponding brightness target levels. The ALS driver presents a character
device (/dev/lm3533-als) which can be used to retrieve the current light zone
or to poll for zone changes.

Further details and specifications will soon be available from

	http://www.ti.com/product/lm3533

This work has been done on behalf of National Semiconductor / Texas
Instruments.

Thanks,
Johan


Johan Hovold (4):
  mfd: add LM3533 lighting-power core driver
  misc: add LM3533 ambient light sensor driver
  leds: add LM3533 LED driver
  backlight: add LM3533 backlight driver

 drivers/leds/Kconfig                |   13 +
 drivers/leds/Makefile               |    1 +
 drivers/leds/leds-lm3533.c          |  713 +++++++++++++++++++++++++++++++++
 drivers/mfd/Kconfig                 |   12 +
 drivers/mfd/Makefile                |    3 +
 drivers/mfd/lm3533-core.c           |  738 +++++++++++++++++++++++++++++++++++
 drivers/mfd/lm3533-ctrlbank.c       |  134 +++++++
 drivers/mfd/lm3533-i2c.c            |  115 ++++++
 drivers/misc/Kconfig                |   13 +
 drivers/misc/Makefile               |    1 +
 drivers/misc/lm3533-als.c           |  662 +++++++++++++++++++++++++++++++
 drivers/video/backlight/Kconfig     |   12 +
 drivers/video/backlight/Makefile    |    1 +
 drivers/video/backlight/lm3533_bl.c |  432 ++++++++++++++++++++
 include/linux/mfd/lm3533.h          |  106 +++++
 15 files changed, 2956 insertions(+), 0 deletions(-)
 create mode 100644 drivers/leds/leds-lm3533.c
 create mode 100644 drivers/mfd/lm3533-core.c
 create mode 100644 drivers/mfd/lm3533-ctrlbank.c
 create mode 100644 drivers/mfd/lm3533-i2c.c
 create mode 100644 drivers/misc/lm3533-als.c
 create mode 100644 drivers/video/backlight/lm3533_bl.c
 create mode 100644 include/linux/mfd/lm3533.h

-- 
1.7.8.5


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

* [PATCH 0/4] mfd: add LM3533 lighting-power chip driver
@ 2012-04-20 15:30 ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold

These patches (against v3.4-rc3) add support for the National Semiconductor /
Texas Instruments LM3533 lighting-power chip.

This multi-function device has four LEDs, two backlights and an ambient-light
sensor.

The LEDs and backlights can be controlled directly, through PWM input,
or by the on-chip ambient light sensor. Hardware-accelerated blinking is
provided for the LEDs.

ALS control is done through defining five light zones and three sets of
corresponding brightness target levels. The ALS driver presents a character
device (/dev/lm3533-als) which can be used to retrieve the current light zone
or to poll for zone changes.

Further details and specifications will soon be available from

	http://www.ti.com/product/lm3533

This work has been done on behalf of National Semiconductor / Texas
Instruments.

Thanks,
Johan


Johan Hovold (4):
  mfd: add LM3533 lighting-power core driver
  misc: add LM3533 ambient light sensor driver
  leds: add LM3533 LED driver
  backlight: add LM3533 backlight driver

 drivers/leds/Kconfig                |   13 +
 drivers/leds/Makefile               |    1 +
 drivers/leds/leds-lm3533.c          |  713 +++++++++++++++++++++++++++++++++
 drivers/mfd/Kconfig                 |   12 +
 drivers/mfd/Makefile                |    3 +
 drivers/mfd/lm3533-core.c           |  738 +++++++++++++++++++++++++++++++++++
 drivers/mfd/lm3533-ctrlbank.c       |  134 +++++++
 drivers/mfd/lm3533-i2c.c            |  115 ++++++
 drivers/misc/Kconfig                |   13 +
 drivers/misc/Makefile               |    1 +
 drivers/misc/lm3533-als.c           |  662 +++++++++++++++++++++++++++++++
 drivers/video/backlight/Kconfig     |   12 +
 drivers/video/backlight/Makefile    |    1 +
 drivers/video/backlight/lm3533_bl.c |  432 ++++++++++++++++++++
 include/linux/mfd/lm3533.h          |  106 +++++
 15 files changed, 2956 insertions(+), 0 deletions(-)
 create mode 100644 drivers/leds/leds-lm3533.c
 create mode 100644 drivers/mfd/lm3533-core.c
 create mode 100644 drivers/mfd/lm3533-ctrlbank.c
 create mode 100644 drivers/mfd/lm3533-i2c.c
 create mode 100644 drivers/misc/lm3533-als.c
 create mode 100644 drivers/video/backlight/lm3533_bl.c
 create mode 100644 include/linux/mfd/lm3533.h

-- 
1.7.8.5


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

* [PATCH 1/4] mfd: add LM3533 lighting-power core driver
  2012-04-20 15:30 ` Johan Hovold
@ 2012-04-20 15:30   ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold

Add support for National Semiconductor / TI LM3533 lighting power chips.

This is the core driver which provides register access over I2C and
registers the ambient light sensor, LED and backlight sub-drivers.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/mfd/Kconfig           |   12 +
 drivers/mfd/Makefile          |    3 +
 drivers/mfd/lm3533-core.c     |  738 +++++++++++++++++++++++++++++++++++++++++
 drivers/mfd/lm3533-ctrlbank.c |  134 ++++++++
 drivers/mfd/lm3533-i2c.c      |  115 +++++++
 include/linux/mfd/lm3533.h    |  106 ++++++
 6 files changed, 1108 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/lm3533-core.c
 create mode 100644 drivers/mfd/lm3533-ctrlbank.c
 create mode 100644 drivers/mfd/lm3533-i2c.c
 create mode 100644 include/linux/mfd/lm3533.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 29f463c..e2760d9 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -106,6 +106,18 @@ config UCB1400_CORE
 	  To compile this driver as a module, choose M here: the
 	  module will be called ucb1400_core.
 
+config MFD_LM3533
+	tristate "LM3533 Lighting Power chip"
+	depends on I2C
+	select MFD_CORE
+	help
+	  Say yes here to enable support for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  This driver provides common support for accessing the device;
+	  additional drivers must be enabled in order to use the LED,
+	  backlight or ambient-light-sensor functionality of the device.
+
 config TPS6105X
 	tristate "TPS61050/61052 Boost Converters"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 05fa538..563fffa 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -15,6 +15,9 @@ obj-$(CONFIG_MFD_DAVINCI_VOICECODEC)	+= davinci_voicecodec.o
 obj-$(CONFIG_MFD_DM355EVM_MSP)	+= dm355evm_msp.o
 obj-$(CONFIG_MFD_TI_SSP)	+= ti-ssp.o
 
+lm3533-objs			:= lm3533-core.o lm3533-i2c.o
+obj-$(CONFIG_MFD_LM3533)	+= lm3533.o lm3533-ctrlbank.o
+
 obj-$(CONFIG_MFD_STMPE)		+= stmpe.o
 obj-$(CONFIG_STMPE_I2C)		+= stmpe-i2c.o
 obj-$(CONFIG_STMPE_SPI)		+= stmpe-spi.o
diff --git a/drivers/mfd/lm3533-core.c b/drivers/mfd/lm3533-core.c
new file mode 100644
index 0000000..7a2d174
--- /dev/null
+++ b/drivers/mfd/lm3533-core.c
@@ -0,0 +1,738 @@
+/*
+ * lm3533-core.c -- LM3533 Core
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/debugfs.h>
+#include <linux/gpio.h>
+#include <linux/mfd/core.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_BOOST_OVP_MAX		0x03
+#define LM3533_BOOST_OVP_MASK		0x06
+#define LM3533_BOOST_OVP_SHIFT		1
+
+#define LM3533_BOOST_FREQ_MAX		0x01
+#define LM3533_BOOST_FREQ_MASK		0x01
+#define LM3533_BOOST_FREQ_SHIFT		0
+
+#define LM3533_BL_ID_MASK		1
+#define LM3533_LED_ID_MASK		3
+#define LM3533_BL_ID_MAX		1
+#define LM3533_LED_ID_MAX		3
+
+#define LM3533_HVLED_ID_MAX		2
+#define LM3533_LVLED_ID_MAX		5
+
+#define LM3533_REG_OUTPUT_CONF1		0x10
+#define LM3533_REG_OUTPUT_CONF2		0x11
+#define LM3533_REG_BOOST_PWM		0x2c
+
+
+static struct mfd_cell lm3533_als_devs[] = {
+	{
+		.name	= "lm3533-als",
+		.id	= -1,
+	},
+};
+
+static struct mfd_cell lm3533_bl_devs[] = {
+	{
+		.name	= "lm3533-backlight",
+		.id	= 0,
+	},
+	{
+		.name	= "lm3533-backlight",
+		.id	= 1,
+	},
+};
+
+static struct mfd_cell lm3533_led_devs[] = {
+	{
+		.name	= "lm3533-leds",
+		.id	= 0,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 1,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 2,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 3,
+	},
+};
+
+static int __lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
+{
+	int ret;
+
+	ret = lm3533->read(lm3533, reg, val);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to read register %02x: %d\n",
+								reg, ret);
+		return ret;
+	}
+
+	dev_dbg(lm3533->dev, "read [%02x]: %02x\n", reg, *val);
+
+	return 0;
+}
+
+static int __lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val)
+{
+	int ret;
+
+	dev_dbg(lm3533->dev, "write [%02x]: %02x\n", reg, val);
+
+	ret = lm3533->write(lm3533, reg, val);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to write register %02x: %d\n",
+								reg, ret);
+	}
+
+	return ret;
+}
+
+int lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
+{
+	int ret;
+
+	mutex_lock(&lm3533->io_mutex);
+	ret = __lm3533_read(lm3533, reg, val);
+	mutex_unlock(&lm3533->io_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_read);
+
+int lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val)
+{
+	int ret;
+
+	mutex_lock(&lm3533->io_mutex);
+	ret = __lm3533_write(lm3533, reg, val);
+	mutex_unlock(&lm3533->io_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_write);
+
+int lm3533_update(struct lm3533 *lm3533, u8 reg, u8 val, u8 mask)
+{
+	u8 old_val;
+	u8 new_val;
+	int ret;
+
+	mutex_lock(&lm3533->io_mutex);
+	ret = __lm3533_read(lm3533, reg, &old_val);
+	if (ret)
+		goto out;
+	new_val = (old_val & ~mask) | (val & mask);
+	if (new_val != old_val)
+		ret = __lm3533_write(lm3533, reg, new_val);
+out:
+	mutex_unlock(&lm3533->io_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_update);
+
+/*
+ * HVLED output config -- output hvled controlled by backlight bl
+ */
+static int lm3533_set_hvled_config(struct lm3533 *lm3533, u8 hvled, u8 bl)
+{
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (hvled == 0 || hvled > LM3533_HVLED_ID_MAX)
+		return -EINVAL;
+
+	if (bl > LM3533_BL_ID_MAX)
+		return -EINVAL;
+
+	shift = hvled - 1;
+	mask = LM3533_BL_ID_MASK << shift;
+	val = bl << shift;
+
+	ret = lm3533_update(lm3533, LM3533_REG_OUTPUT_CONF1, val, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to set hvled config\n");
+
+	return ret;
+}
+
+/*
+ * LVLED output config -- output lvled controlled by LED led
+ */
+static int lm3533_set_lvled_config(struct lm3533 *lm3533, u8 lvled, u8 led)
+{
+	u8 reg;
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (lvled == 0 || lvled > LM3533_LVLED_ID_MAX)
+		return -EINVAL;
+
+	if (led > LM3533_LED_ID_MAX)
+		return -EINVAL;
+
+	if (lvled < 4) {
+		reg = LM3533_REG_OUTPUT_CONF1;
+		shift = 2 * lvled;
+	} else {
+		reg = LM3533_REG_OUTPUT_CONF2;
+		shift = 2 * (lvled - 4);
+	}
+
+	mask = LM3533_LED_ID_MASK << shift;
+	val = led << shift;
+
+	ret = lm3533_update(lm3533, reg, val, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to set lvled config\n");
+
+	return ret;
+}
+
+static void lm3533_enable(struct lm3533 *lm3533)
+{
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_set_value(lm3533->gpio_hwen, 1);
+}
+
+static void lm3533_disable(struct lm3533 *lm3533)
+{
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_set_value(lm3533->gpio_hwen, 0);
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+const struct {
+	u8 min;
+	u8 max;
+} lm3533_regs[] = {
+	{ 0x10, 0x2c },
+	{ 0x30, 0x38 },
+	{ 0x40, 0x45 },
+	{ 0x50, 0x57 },
+	{ 0x60, 0x6e },
+	{ 0x70, 0x75 },
+	{ 0x80, 0x85 },
+	{ 0x90, 0x95 },
+	{ 0xa0, 0xa5 },
+	{ 0xb0, 0xb2 },
+};
+
+static int lm3533_regs_show(struct seq_file *s, void *__unused)
+{
+	struct lm3533 *lm3533 = s->private;
+	u8 reg;
+	u8 val;
+	int ret;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(lm3533_regs); ++i) {
+		for (reg = lm3533_regs[i].min;
+					reg <= lm3533_regs[i].max; ++reg) {
+			ret = lm3533_read(lm3533, reg, &val);
+			if (ret)
+				return ret;
+
+			seq_printf(s, "[%02x]: %02x\n", reg, val);
+		}
+	}
+
+	return 0;
+}
+
+static int lm3533_regs_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, lm3533_regs_show, inode->i_private);
+}
+
+static int lm3533_set_reg_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+
+	return 0;
+}
+
+static ssize_t lm3533_set_reg_write(struct file *file,
+					const char __user *user_buf,
+					size_t count, loff_t *ppos)
+{
+	struct lm3533 *lm3533 = file->private_data;
+	char buf[32];
+	int len;
+	unsigned reg;
+	unsigned val;
+	int ret;
+
+	len = min(count, sizeof(buf) - 1);
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+	buf[len] = 0;
+
+	if (sscanf(buf, "%x %x", &reg, &val) != 2)
+		return -EINVAL;
+
+	if (reg > 0xff || val > 0xff)
+		return -EINVAL;
+
+	ret = lm3533_write(lm3533, (u8)reg, (u8)val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static const struct file_operations lm3533_regs_fops = {
+	.open		= lm3533_regs_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static const struct file_operations lm3533_set_reg_fops = {
+	.open		= lm3533_set_reg_open,
+	.write		= lm3533_set_reg_write,
+};
+
+static void __devinit lm3533_debugfs_init(struct lm3533 *lm3533)
+{
+	struct dentry *d;
+
+	lm3533->debugfs_root = debugfs_create_dir("lm3533", NULL);
+	if (!lm3533->debugfs_root) {
+		dev_err(lm3533->dev, "failed to create debugfs root\n");
+		return;
+	}
+
+	d = debugfs_create_file("regs", 0444, lm3533->debugfs_root, lm3533,
+							&lm3533_regs_fops);
+	if (!d)
+		dev_err(lm3533->dev, "failed to create debugfs regs file\n");
+
+	d = debugfs_create_file("set_reg", 0644, lm3533->debugfs_root, lm3533,
+							&lm3533_set_reg_fops);
+	if (!d) {
+		dev_err(lm3533->dev,
+				"failed to create debugfs set_reg file\n");
+	}
+}
+
+static void __devexit lm3533_debugfs_cleanup(struct lm3533 *lm3533)
+{
+	debugfs_remove_recursive(lm3533->debugfs_root);
+}
+
+#else	/* CONFIG_DEBUG_FS */
+static void lm3533_debugfs_init(struct lm3533 *lm3533) { }
+static void lm3533_debugfs_cleanup(struct lm3533 *lm3533) { }
+#endif	/* CONFIG_DEBUG_FS */
+
+enum lm3533_attribute_type {
+	LM3533_ATTR_TYPE_BACKLIGHT,
+	LM3533_ATTR_TYPE_LED,
+};
+
+struct lm3533_device_attribute {
+	struct device_attribute dev_attr;
+	enum lm3533_attribute_type type;
+	union {
+		struct {
+			u8 id;
+		} output;
+		struct {
+			u8 reg;
+			u8 shift;
+			u8 mask;
+			u8 max;
+		} generic;
+	} u;
+};
+
+#define to_lm3533_dev_attr(_attr) \
+	container_of(_attr, struct lm3533_device_attribute, dev_attr)
+
+static ssize_t show_lm3533_reg(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(lm3533, lattr->u.generic.reg, &val);
+	if (ret)
+		return ret;
+
+	val = (val & lattr->u.generic.mask) >> lattr->u.generic.shift;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_lm3533_reg(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t len)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > lattr->u.generic.max)
+		return -EINVAL;
+
+	val = val << lattr->u.generic.shift;
+	ret = lm3533_update(lm3533, lattr->u.generic.reg, val,
+							lattr->u.generic.mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define GENERIC_ATTR(_reg, _max, _mask, _shift) \
+	{ .reg		= _reg, \
+	  .max		= _max, \
+	  .mask		= _mask, \
+	  .shift	= _shift }
+
+#define LM3533_GENERIC_ATTR(_name, _mode, _show, _store, _type,	\
+						_reg, _max, _mask, _shift) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name = { \
+		.dev_attr	= __ATTR(_name, _mode, _show, _store), \
+		.type		= _type, \
+		.u.generic	= GENERIC_ATTR(_reg, _max, _mask, _shift) }
+
+#define LM3533_GENERIC_ATTR_RW(_name, _type, _reg, _max, _mask, _shift) \
+	LM3533_GENERIC_ATTR(_name, S_IRUGO | S_IWUSR, \
+					show_lm3533_reg, store_lm3533_reg, \
+					_type, _reg, _max, _mask, _shift)
+
+#define LM3533_BOOST_ATTR_RW(_name, _NAME) \
+	LM3533_GENERIC_ATTR_RW(_name, LM3533_ATTR_TYPE_BACKLIGHT, \
+				LM3533_REG_BOOST_PWM, LM3533_##_NAME##_MAX, \
+				LM3533_##_NAME##_MASK, LM3533_##_NAME##_SHIFT)
+/*
+ * Boost Over Voltage Protection Select
+ *
+ *   0 -- 16 V (default)
+ *   1 -- 24 V
+ *   2 -- 32 V
+ *   3 -- 40 V
+ */
+static LM3533_BOOST_ATTR_RW(boost_ovp, BOOST_OVP);
+
+/*
+ * Boost Frequency Select
+ *
+ *   0 -- 500 kHz (default)
+ *   1 -- 1 MHz
+ */
+static LM3533_BOOST_ATTR_RW(boost_freq, BOOST_FREQ);
+
+static ssize_t show_output(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	int id = lattr->u.output.id;
+	u8 reg;
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (lattr->type == LM3533_ATTR_TYPE_BACKLIGHT) {
+		reg = LM3533_REG_OUTPUT_CONF1;
+		shift = id - 1;
+		mask = LM3533_BL_ID_MASK << shift;
+	} else {
+		if (id < 4) {
+			reg = LM3533_REG_OUTPUT_CONF1;
+			shift = 2 * id;
+		} else {
+			reg = LM3533_REG_OUTPUT_CONF2;
+			shift = 2 * (id - 4);
+		}
+		mask = LM3533_LED_ID_MASK << shift;
+	}
+
+	ret = lm3533_read(lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	val = (val & mask) >> shift;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_output(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	int id = lattr->u.output.id;
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	if (lattr->type == LM3533_ATTR_TYPE_BACKLIGHT)
+		ret = lm3533_set_hvled_config(lm3533, id, val);
+	else
+		ret = lm3533_set_lvled_config(lm3533, id, val);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define LM3533_OUTPUT_ATTR(_name, _mode, _show, _store, _type, _id) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name = \
+		{ .dev_attr	= __ATTR(_name, _mode, _show, _store), \
+		  .type		= _type, \
+		  .u.output	= { .id = _id }, }
+
+#define LM3533_OUTPUT_ATTR_RW(_name, _type, _id) \
+	LM3533_OUTPUT_ATTR(output_##_name, S_IRUGO | S_IWUSR, \
+					show_output, store_output, _type, _id)
+
+#define LM3533_OUTPUT_HVLED_ATTR_RW(_nr) \
+	LM3533_OUTPUT_ATTR_RW(hvled##_nr, LM3533_ATTR_TYPE_BACKLIGHT, _nr)
+#define LM3533_OUTPUT_LVLED_ATTR_RW(_nr) \
+	LM3533_OUTPUT_ATTR_RW(lvled##_nr, LM3533_ATTR_TYPE_LED, _nr)
+/*
+ * Output config:
+ *
+ * output_hvled<nr> -- 0-1
+ * output_lvled<nr> -- 0-3
+ */
+static LM3533_OUTPUT_HVLED_ATTR_RW(1);
+static LM3533_OUTPUT_HVLED_ATTR_RW(2);
+static LM3533_OUTPUT_LVLED_ATTR_RW(1);
+static LM3533_OUTPUT_LVLED_ATTR_RW(2);
+static LM3533_OUTPUT_LVLED_ATTR_RW(3);
+static LM3533_OUTPUT_LVLED_ATTR_RW(4);
+static LM3533_OUTPUT_LVLED_ATTR_RW(5);
+
+static struct attribute *lm3533_attributes[] = {
+	&lm3533_dev_attr_boost_freq.dev_attr.attr,
+	&lm3533_dev_attr_boost_ovp.dev_attr.attr,
+	&lm3533_dev_attr_output_hvled1.dev_attr.attr,
+	&lm3533_dev_attr_output_hvled2.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled1.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled2.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled3.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled4.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled5.dev_attr.attr,
+	NULL,
+};
+
+#define to_dev_attr(_attr) \
+	container_of(_attr, struct device_attribute, attr)
+
+static mode_t lm3533_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct device_attribute *dattr = to_dev_attr(attr);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(dattr);
+	enum lm3533_attribute_type type = lattr->type;
+	mode_t mode = attr->mode;
+
+	if (!lm3533->have_backlights && type == LM3533_ATTR_TYPE_BACKLIGHT)
+		mode = 0;
+	else if (!lm3533->have_leds && type == LM3533_ATTR_TYPE_LED)
+		mode = 0;
+
+	return mode;
+};
+
+static struct attribute_group lm3533_attribute_group = {
+	.is_visible	= lm3533_attr_is_visible,
+	.attrs		= lm3533_attributes
+};
+
+static int __devinit lm3533_device_als_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int ret;
+
+	if (!pdata->als)
+		return 0;
+
+	lm3533_als_devs[0].platform_data = pdata->als;
+	lm3533_als_devs[0].pdata_size = sizeof(*pdata->als);
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_als_devs, 1, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add ALS device\n");
+		return ret;
+	}
+
+	lm3533->have_als = 1;
+
+	return 0;
+}
+
+static int __devinit lm3533_device_bl_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int i;
+	int ret;
+
+	if (!pdata->backlights || pdata->num_backlights == 0)
+		return 0;
+
+	if (pdata->num_backlights > ARRAY_SIZE(lm3533_bl_devs))
+		pdata->num_backlights = ARRAY_SIZE(lm3533_bl_devs);
+
+	for (i = 0; i < pdata->num_backlights; ++i) {
+		lm3533_bl_devs[i].platform_data = &pdata->backlights[i];
+		lm3533_bl_devs[i].pdata_size = sizeof(pdata->backlights[i]);
+	}
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_bl_devs,
+					pdata->num_backlights, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add backlight devices\n");
+		return ret;
+	}
+
+	lm3533->have_backlights = 1;
+
+	return 0;
+}
+
+static int __devinit lm3533_device_led_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int i;
+	int ret;
+
+	if (!pdata->leds || pdata->num_leds == 0)
+		return 0;
+
+	if (pdata->num_leds > ARRAY_SIZE(lm3533_led_devs))
+		pdata->num_leds = ARRAY_SIZE(lm3533_led_devs);
+
+	for (i = 0; i < pdata->num_leds; ++i) {
+		lm3533_led_devs[i].platform_data = &pdata->leds[i];
+		lm3533_led_devs[i].pdata_size = sizeof(pdata->leds[i]);
+	}
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_led_devs,
+						pdata->num_leds, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add LED devices\n");
+		return ret;
+	}
+
+	lm3533->have_leds = 1;
+
+	return 0;
+}
+
+int __devinit lm3533_device_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int ret;
+
+	dev_dbg(lm3533->dev, "%s\n", __func__);
+
+	if (!pdata) {
+		dev_err(lm3533->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	mutex_init(&lm3533->io_mutex);
+	lm3533->gpio_hwen = pdata->gpio_hwen;
+
+	dev_set_drvdata(lm3533->dev, lm3533);
+
+	if (gpio_is_valid(lm3533->gpio_hwen)) {
+		ret = gpio_request_one(lm3533->gpio_hwen, GPIOF_OUT_INIT_LOW,
+								"lm3533-hwen");
+		if (ret < 0) {
+			dev_err(lm3533->dev,
+				"failed to request HWEN GPIO %d\n",
+				lm3533->gpio_hwen);
+			return ret;
+		}
+	}
+
+	lm3533_enable(lm3533);
+
+	lm3533_device_als_init(lm3533);
+	lm3533_device_bl_init(lm3533);
+	lm3533_device_led_init(lm3533);
+
+	ret = sysfs_create_group(&lm3533->dev->kobj, &lm3533_attribute_group);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	lm3533_debugfs_init(lm3533);
+
+	return 0;
+
+err_unregister:
+	mfd_remove_devices(lm3533->dev);
+	lm3533_disable(lm3533);
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_free(lm3533->gpio_hwen);
+
+	return ret;
+}
+
+void __devexit lm3533_device_exit(struct lm3533 *lm3533)
+{
+	dev_dbg(lm3533->dev, "%s\n", __func__);
+
+	lm3533_debugfs_cleanup(lm3533);
+
+	sysfs_remove_group(&lm3533->dev->kobj, &lm3533_attribute_group);
+
+	mfd_remove_devices(lm3533->dev);
+	lm3533_disable(lm3533);
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_free(lm3533->gpio_hwen);
+}
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/lm3533-ctrlbank.c b/drivers/mfd/lm3533-ctrlbank.c
new file mode 100644
index 0000000..2feb4e8
--- /dev/null
+++ b/drivers/mfd/lm3533-ctrlbank.c
@@ -0,0 +1,134 @@
+/*
+ * lm3533-ctrlbank.c -- LM3533 Generic Control Bank interface
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_BRIGHTNESS_MAX		255
+#define LM3533_MAX_CURRENT_MAX		31
+#define LM3533_PWM_MAX			0x3f
+
+#define LM3533_REG_PWM_BASE		0x14
+#define LM3533_REG_MAX_CURRENT_BASE	0x1f
+#define LM3533_REG_CTRLBANK_ENABLE	0x27
+#define LM3533_REG_BRIGHTNESS_BASE	0x40
+
+
+static inline u8 lm3533_ctrlbank_get_reg(struct lm3533_ctrlbank *cb, u8 base)
+{
+	return base + cb->id;
+}
+
+int lm3533_ctrlbank_enable(struct lm3533_ctrlbank *cb)
+{
+	u8 mask;
+	int ret;
+
+	dev_dbg(cb->dev, "%s - %d\n", __func__, cb->id);
+
+	mask = 1 << cb->id;
+	ret = lm3533_update(cb->lm3533, LM3533_REG_CTRLBANK_ENABLE,
+								mask, mask);
+	if (ret)
+		dev_err(cb->dev, "failed to enable ctrlbank %d\n", cb->id);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_enable);
+
+int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb)
+{
+	u8 mask;
+	int ret;
+
+	dev_dbg(cb->dev, "%s - %d\n", __func__, cb->id);
+
+	mask = 1 << cb->id;
+	ret = lm3533_update(cb->lm3533, LM3533_REG_CTRLBANK_ENABLE, 0, mask);
+	if (ret)
+		dev_err(cb->dev, "failed to disable ctrlbank %d\n", cb->id);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_disable);
+
+#define lm3533_ctrlbank_set(_name, _NAME)				\
+int lm3533_ctrlbank_set_##_name(struct lm3533_ctrlbank *cb, u8 val)	\
+{									\
+	u8 reg;								\
+	int ret;							\
+									\
+	if (val > LM3533_##_NAME##_MAX)					\
+		return -EINVAL;						\
+									\
+	reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_##_NAME##_BASE);	\
+	ret = lm3533_write(cb->lm3533, reg, val);			\
+	if (ret)							\
+		dev_err(cb->dev, "failed to set " #_name "\n");		\
+									\
+	return ret;							\
+}									\
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_set_##_name);
+
+#define lm3533_ctrlbank_get(_name, _NAME)				\
+int lm3533_ctrlbank_get_##_name(struct lm3533_ctrlbank *cb, u8 *val)	\
+{									\
+	u8 reg;								\
+	int ret;							\
+									\
+	reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_##_NAME##_BASE);	\
+	ret = lm3533_read(cb->lm3533, reg, val);			\
+	if (ret)							\
+		dev_err(cb->dev, "failed to get " #_name "\n");		\
+									\
+	return ret;							\
+}									\
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_get_##_name);
+
+lm3533_ctrlbank_set(brightness, BRIGHTNESS);
+lm3533_ctrlbank_get(brightness, BRIGHTNESS);
+
+/*
+ * Full scale current.
+ *
+ * Imax = 5 + val * 0.8 mA, e.g.:
+ *
+ *    0 - 5 mA
+ *     ...
+ *   19 - 20.2 mA (default)
+ *     ...
+ *   31 - 29.8 mA
+ */
+lm3533_ctrlbank_set(max_current, MAX_CURRENT);
+lm3533_ctrlbank_get(max_current, MAX_CURRENT);
+
+/*
+ * PWM control:
+ *
+ *   bit 5 - PWM enabled in Zone 4
+ *   bit 4 - PWM enabled in Zone 3
+ *   bit 3 - PWM enabled in Zone 2
+ *   bit 2 - PWM enabled in Zone 1
+ *   bit 1 - PWM enabled in Zone 0
+ *   bit 0 - PWM enabled
+ */
+lm3533_ctrlbank_set(pwm, PWM);
+lm3533_ctrlbank_get(pwm, PWM);
+
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Control Bank interface");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/lm3533-i2c.c b/drivers/mfd/lm3533-i2c.c
new file mode 100644
index 0000000..d479b62
--- /dev/null
+++ b/drivers/mfd/lm3533-i2c.c
@@ -0,0 +1,115 @@
+/*
+ * lm3533-i2c.c -- LM3533 I2C interface
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+static int lm3533_i2c_read(struct lm3533 *lm3533, u8 reg, u8 *val)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(lm3533->i2c, reg);
+	if (ret < 0)
+		return ret;
+
+	*val = (u8)ret;
+
+	return 0;
+}
+
+static int lm3533_i2c_write(struct lm3533 *lm3533, u8 reg, u8 val)
+{
+	return i2c_smbus_write_byte_data(lm3533->i2c, reg, val);
+}
+
+static int __devinit lm3533_i2c_probe(struct i2c_client *i2c,
+					const struct i2c_device_id *id)
+{
+	struct lm3533 *lm3533;
+	int ret;
+
+	dev_dbg(&i2c->dev, "%s\n", __func__);
+
+	if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -EINVAL;
+
+	lm3533 = kzalloc(sizeof(*lm3533), GFP_KERNEL);
+	if (!lm3533)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, lm3533);
+
+	lm3533->dev = &i2c->dev;
+	lm3533->i2c = i2c;
+	lm3533->irq = i2c->irq;
+	lm3533->read = lm3533_i2c_read;
+	lm3533->write = lm3533_i2c_write;
+
+	ret = lm3533_device_init(lm3533);
+	if (ret) {
+		kfree(lm3533);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int __devexit lm3533_i2c_remove(struct i2c_client *i2c)
+{
+	struct lm3533 *lm3533 = i2c_get_clientdata(i2c);
+
+	dev_dbg(&i2c->dev, "%s\n", __func__);
+
+	lm3533_device_exit(lm3533);
+
+	kfree(lm3533);
+
+	return 0;
+}
+
+static const struct i2c_device_id lm3533_i2c_ids[] = {
+	{ "lm3533", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, lm3533_i2c_ids);
+
+static struct i2c_driver lm3533_i2c_driver = {
+	.driver = {
+		   .name = "lm3533",
+		   .owner = THIS_MODULE,
+	},
+	.id_table	= lm3533_i2c_ids,
+	.probe		= lm3533_i2c_probe,
+	.remove		= __devexit_p(lm3533_i2c_remove),
+};
+
+static int __init lm3533_i2c_init(void)
+{
+	return i2c_add_driver(&lm3533_i2c_driver);
+}
+subsys_initcall(lm3533_i2c_init);
+
+static void __exit lm3533_i2c_exit(void)
+{
+	i2c_del_driver(&lm3533_i2c_driver);
+}
+module_exit(lm3533_i2c_exit);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 I2C interface");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
new file mode 100644
index 0000000..cb8bf33
--- /dev/null
+++ b/include/linux/mfd/lm3533.h
@@ -0,0 +1,106 @@
+/*
+ * lm3533.h -- LM3533 interface
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_MFD_LM3533_H
+#define __LINUX_MFD_LM3533_H
+
+#include <linux/mutex.h>
+
+
+#define LM3533_ATTR_RO(_name) \
+	DEVICE_ATTR(_name, S_IRUGO, show_##_name, NULL)
+#define LM3533_ATTR_RW(_name) \
+	DEVICE_ATTR(_name, S_IRUGO | S_IWUSR , show_##_name, store_##_name)
+
+struct device;
+struct dentry;
+struct i2c_client;
+
+struct lm3533 {
+	struct device *dev;
+	struct i2c_client *i2c;
+
+	struct mutex io_mutex;
+
+	int (*read)(struct lm3533 *lm3533, u8 reg, u8 *val);
+	int (*write)(struct lm3533 *lm3533, u8 reg, u8 val);
+
+	int gpio_hwen;
+	int irq;
+
+	unsigned have_als:1;
+	unsigned have_backlights:1;
+	unsigned have_leds:1;
+
+#ifdef CONFIG_DEBUG_FS
+	struct dentry *debugfs_root;
+#endif
+};
+
+struct lm3533_ctrlbank {
+	struct lm3533 *lm3533;
+	struct device *dev;
+	int id;
+};
+
+struct lm3533_als_platform_data {
+	unsigned pwm_mode:1;		/* PWM input mode (default analog) */
+	unsigned int_mode:1;		/* interrupt mode (default polled) */
+	unsigned poll_interval;		/* in polled mode (in ms) */
+};
+
+struct lm3533_bl_platform_data {
+	char *name;
+	u8 default_brightness;		/* 0 - 255 */
+	u8 max_current;			/* 0 - 31 */
+	u8 pwm;				/* 0 - 0x3f */
+};
+
+struct lm3533_led_platform_data {
+	char *name;
+	const char *default_trigger;
+	u8 max_current;			/* 0 - 31 */
+	u8 pwm;				/* 0 - 0x3f */
+};
+
+struct lm3533_platform_data {
+	int gpio_hwen;
+
+	struct lm3533_als_platform_data *als;
+
+	struct lm3533_bl_platform_data *backlights;
+	int num_backlights;
+
+	struct lm3533_led_platform_data *leds;
+	int num_leds;
+};
+
+extern int lm3533_ctrlbank_enable(struct lm3533_ctrlbank *cb);
+extern int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb);
+
+extern int lm3533_ctrlbank_set_brightness(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_brightness(struct lm3533_ctrlbank *cb, u8 *val);
+extern int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_max_current(struct lm3533_ctrlbank *cb,
+								u8 *val);
+extern int lm3533_ctrlbank_set_pwm(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_pwm(struct lm3533_ctrlbank *cb, u8 *val);
+
+extern int lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val);
+extern int lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val);
+extern int lm3533_update(struct lm3533 *lm3533, u8 reg, u8 val, u8 mask);
+
+extern int __devinit lm3533_device_init(struct lm3533 *lm3533);
+extern void __devinit lm3533_device_exit(struct lm3533 *lm3533);
+
+#endif	/* __LINUX_MFD_LM3533_H */
-- 
1.7.8.5


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

* [PATCH 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-04-20 15:30   ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold

Add support for National Semiconductor / TI LM3533 lighting power chips.

This is the core driver which provides register access over I2C and
registers the ambient light sensor, LED and backlight sub-drivers.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/mfd/Kconfig           |   12 +
 drivers/mfd/Makefile          |    3 +
 drivers/mfd/lm3533-core.c     |  738 +++++++++++++++++++++++++++++++++++++++++
 drivers/mfd/lm3533-ctrlbank.c |  134 ++++++++
 drivers/mfd/lm3533-i2c.c      |  115 +++++++
 include/linux/mfd/lm3533.h    |  106 ++++++
 6 files changed, 1108 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/lm3533-core.c
 create mode 100644 drivers/mfd/lm3533-ctrlbank.c
 create mode 100644 drivers/mfd/lm3533-i2c.c
 create mode 100644 include/linux/mfd/lm3533.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 29f463c..e2760d9 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -106,6 +106,18 @@ config UCB1400_CORE
 	  To compile this driver as a module, choose M here: the
 	  module will be called ucb1400_core.
 
+config MFD_LM3533
+	tristate "LM3533 Lighting Power chip"
+	depends on I2C
+	select MFD_CORE
+	help
+	  Say yes here to enable support for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  This driver provides common support for accessing the device;
+	  additional drivers must be enabled in order to use the LED,
+	  backlight or ambient-light-sensor functionality of the device.
+
 config TPS6105X
 	tristate "TPS61050/61052 Boost Converters"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 05fa538..563fffa 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -15,6 +15,9 @@ obj-$(CONFIG_MFD_DAVINCI_VOICECODEC)	+= davinci_voicecodec.o
 obj-$(CONFIG_MFD_DM355EVM_MSP)	+= dm355evm_msp.o
 obj-$(CONFIG_MFD_TI_SSP)	+= ti-ssp.o
 
+lm3533-objs			:= lm3533-core.o lm3533-i2c.o
+obj-$(CONFIG_MFD_LM3533)	+= lm3533.o lm3533-ctrlbank.o
+
 obj-$(CONFIG_MFD_STMPE)		+= stmpe.o
 obj-$(CONFIG_STMPE_I2C)		+= stmpe-i2c.o
 obj-$(CONFIG_STMPE_SPI)		+= stmpe-spi.o
diff --git a/drivers/mfd/lm3533-core.c b/drivers/mfd/lm3533-core.c
new file mode 100644
index 0000000..7a2d174
--- /dev/null
+++ b/drivers/mfd/lm3533-core.c
@@ -0,0 +1,738 @@
+/*
+ * lm3533-core.c -- LM3533 Core
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/debugfs.h>
+#include <linux/gpio.h>
+#include <linux/mfd/core.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_BOOST_OVP_MAX		0x03
+#define LM3533_BOOST_OVP_MASK		0x06
+#define LM3533_BOOST_OVP_SHIFT		1
+
+#define LM3533_BOOST_FREQ_MAX		0x01
+#define LM3533_BOOST_FREQ_MASK		0x01
+#define LM3533_BOOST_FREQ_SHIFT		0
+
+#define LM3533_BL_ID_MASK		1
+#define LM3533_LED_ID_MASK		3
+#define LM3533_BL_ID_MAX		1
+#define LM3533_LED_ID_MAX		3
+
+#define LM3533_HVLED_ID_MAX		2
+#define LM3533_LVLED_ID_MAX		5
+
+#define LM3533_REG_OUTPUT_CONF1		0x10
+#define LM3533_REG_OUTPUT_CONF2		0x11
+#define LM3533_REG_BOOST_PWM		0x2c
+
+
+static struct mfd_cell lm3533_als_devs[] = {
+	{
+		.name	= "lm3533-als",
+		.id	= -1,
+	},
+};
+
+static struct mfd_cell lm3533_bl_devs[] = {
+	{
+		.name	= "lm3533-backlight",
+		.id	= 0,
+	},
+	{
+		.name	= "lm3533-backlight",
+		.id	= 1,
+	},
+};
+
+static struct mfd_cell lm3533_led_devs[] = {
+	{
+		.name	= "lm3533-leds",
+		.id	= 0,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 1,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 2,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 3,
+	},
+};
+
+static int __lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
+{
+	int ret;
+
+	ret = lm3533->read(lm3533, reg, val);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to read register %02x: %d\n",
+								reg, ret);
+		return ret;
+	}
+
+	dev_dbg(lm3533->dev, "read [%02x]: %02x\n", reg, *val);
+
+	return 0;
+}
+
+static int __lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val)
+{
+	int ret;
+
+	dev_dbg(lm3533->dev, "write [%02x]: %02x\n", reg, val);
+
+	ret = lm3533->write(lm3533, reg, val);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to write register %02x: %d\n",
+								reg, ret);
+	}
+
+	return ret;
+}
+
+int lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
+{
+	int ret;
+
+	mutex_lock(&lm3533->io_mutex);
+	ret = __lm3533_read(lm3533, reg, val);
+	mutex_unlock(&lm3533->io_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_read);
+
+int lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val)
+{
+	int ret;
+
+	mutex_lock(&lm3533->io_mutex);
+	ret = __lm3533_write(lm3533, reg, val);
+	mutex_unlock(&lm3533->io_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_write);
+
+int lm3533_update(struct lm3533 *lm3533, u8 reg, u8 val, u8 mask)
+{
+	u8 old_val;
+	u8 new_val;
+	int ret;
+
+	mutex_lock(&lm3533->io_mutex);
+	ret = __lm3533_read(lm3533, reg, &old_val);
+	if (ret)
+		goto out;
+	new_val = (old_val & ~mask) | (val & mask);
+	if (new_val != old_val)
+		ret = __lm3533_write(lm3533, reg, new_val);
+out:
+	mutex_unlock(&lm3533->io_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_update);
+
+/*
+ * HVLED output config -- output hvled controlled by backlight bl
+ */
+static int lm3533_set_hvled_config(struct lm3533 *lm3533, u8 hvled, u8 bl)
+{
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (hvled = 0 || hvled > LM3533_HVLED_ID_MAX)
+		return -EINVAL;
+
+	if (bl > LM3533_BL_ID_MAX)
+		return -EINVAL;
+
+	shift = hvled - 1;
+	mask = LM3533_BL_ID_MASK << shift;
+	val = bl << shift;
+
+	ret = lm3533_update(lm3533, LM3533_REG_OUTPUT_CONF1, val, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to set hvled config\n");
+
+	return ret;
+}
+
+/*
+ * LVLED output config -- output lvled controlled by LED led
+ */
+static int lm3533_set_lvled_config(struct lm3533 *lm3533, u8 lvled, u8 led)
+{
+	u8 reg;
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (lvled = 0 || lvled > LM3533_LVLED_ID_MAX)
+		return -EINVAL;
+
+	if (led > LM3533_LED_ID_MAX)
+		return -EINVAL;
+
+	if (lvled < 4) {
+		reg = LM3533_REG_OUTPUT_CONF1;
+		shift = 2 * lvled;
+	} else {
+		reg = LM3533_REG_OUTPUT_CONF2;
+		shift = 2 * (lvled - 4);
+	}
+
+	mask = LM3533_LED_ID_MASK << shift;
+	val = led << shift;
+
+	ret = lm3533_update(lm3533, reg, val, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to set lvled config\n");
+
+	return ret;
+}
+
+static void lm3533_enable(struct lm3533 *lm3533)
+{
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_set_value(lm3533->gpio_hwen, 1);
+}
+
+static void lm3533_disable(struct lm3533 *lm3533)
+{
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_set_value(lm3533->gpio_hwen, 0);
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+const struct {
+	u8 min;
+	u8 max;
+} lm3533_regs[] = {
+	{ 0x10, 0x2c },
+	{ 0x30, 0x38 },
+	{ 0x40, 0x45 },
+	{ 0x50, 0x57 },
+	{ 0x60, 0x6e },
+	{ 0x70, 0x75 },
+	{ 0x80, 0x85 },
+	{ 0x90, 0x95 },
+	{ 0xa0, 0xa5 },
+	{ 0xb0, 0xb2 },
+};
+
+static int lm3533_regs_show(struct seq_file *s, void *__unused)
+{
+	struct lm3533 *lm3533 = s->private;
+	u8 reg;
+	u8 val;
+	int ret;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(lm3533_regs); ++i) {
+		for (reg = lm3533_regs[i].min;
+					reg <= lm3533_regs[i].max; ++reg) {
+			ret = lm3533_read(lm3533, reg, &val);
+			if (ret)
+				return ret;
+
+			seq_printf(s, "[%02x]: %02x\n", reg, val);
+		}
+	}
+
+	return 0;
+}
+
+static int lm3533_regs_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, lm3533_regs_show, inode->i_private);
+}
+
+static int lm3533_set_reg_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+
+	return 0;
+}
+
+static ssize_t lm3533_set_reg_write(struct file *file,
+					const char __user *user_buf,
+					size_t count, loff_t *ppos)
+{
+	struct lm3533 *lm3533 = file->private_data;
+	char buf[32];
+	int len;
+	unsigned reg;
+	unsigned val;
+	int ret;
+
+	len = min(count, sizeof(buf) - 1);
+	if (copy_from_user(buf, user_buf, len))
+		return -EFAULT;
+	buf[len] = 0;
+
+	if (sscanf(buf, "%x %x", &reg, &val) != 2)
+		return -EINVAL;
+
+	if (reg > 0xff || val > 0xff)
+		return -EINVAL;
+
+	ret = lm3533_write(lm3533, (u8)reg, (u8)val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static const struct file_operations lm3533_regs_fops = {
+	.open		= lm3533_regs_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static const struct file_operations lm3533_set_reg_fops = {
+	.open		= lm3533_set_reg_open,
+	.write		= lm3533_set_reg_write,
+};
+
+static void __devinit lm3533_debugfs_init(struct lm3533 *lm3533)
+{
+	struct dentry *d;
+
+	lm3533->debugfs_root = debugfs_create_dir("lm3533", NULL);
+	if (!lm3533->debugfs_root) {
+		dev_err(lm3533->dev, "failed to create debugfs root\n");
+		return;
+	}
+
+	d = debugfs_create_file("regs", 0444, lm3533->debugfs_root, lm3533,
+							&lm3533_regs_fops);
+	if (!d)
+		dev_err(lm3533->dev, "failed to create debugfs regs file\n");
+
+	d = debugfs_create_file("set_reg", 0644, lm3533->debugfs_root, lm3533,
+							&lm3533_set_reg_fops);
+	if (!d) {
+		dev_err(lm3533->dev,
+				"failed to create debugfs set_reg file\n");
+	}
+}
+
+static void __devexit lm3533_debugfs_cleanup(struct lm3533 *lm3533)
+{
+	debugfs_remove_recursive(lm3533->debugfs_root);
+}
+
+#else	/* CONFIG_DEBUG_FS */
+static void lm3533_debugfs_init(struct lm3533 *lm3533) { }
+static void lm3533_debugfs_cleanup(struct lm3533 *lm3533) { }
+#endif	/* CONFIG_DEBUG_FS */
+
+enum lm3533_attribute_type {
+	LM3533_ATTR_TYPE_BACKLIGHT,
+	LM3533_ATTR_TYPE_LED,
+};
+
+struct lm3533_device_attribute {
+	struct device_attribute dev_attr;
+	enum lm3533_attribute_type type;
+	union {
+		struct {
+			u8 id;
+		} output;
+		struct {
+			u8 reg;
+			u8 shift;
+			u8 mask;
+			u8 max;
+		} generic;
+	} u;
+};
+
+#define to_lm3533_dev_attr(_attr) \
+	container_of(_attr, struct lm3533_device_attribute, dev_attr)
+
+static ssize_t show_lm3533_reg(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(lm3533, lattr->u.generic.reg, &val);
+	if (ret)
+		return ret;
+
+	val = (val & lattr->u.generic.mask) >> lattr->u.generic.shift;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_lm3533_reg(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t len)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > lattr->u.generic.max)
+		return -EINVAL;
+
+	val = val << lattr->u.generic.shift;
+	ret = lm3533_update(lm3533, lattr->u.generic.reg, val,
+							lattr->u.generic.mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define GENERIC_ATTR(_reg, _max, _mask, _shift) \
+	{ .reg		= _reg, \
+	  .max		= _max, \
+	  .mask		= _mask, \
+	  .shift	= _shift }
+
+#define LM3533_GENERIC_ATTR(_name, _mode, _show, _store, _type,	\
+						_reg, _max, _mask, _shift) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name = { \
+		.dev_attr	= __ATTR(_name, _mode, _show, _store), \
+		.type		= _type, \
+		.u.generic	= GENERIC_ATTR(_reg, _max, _mask, _shift) }
+
+#define LM3533_GENERIC_ATTR_RW(_name, _type, _reg, _max, _mask, _shift) \
+	LM3533_GENERIC_ATTR(_name, S_IRUGO | S_IWUSR, \
+					show_lm3533_reg, store_lm3533_reg, \
+					_type, _reg, _max, _mask, _shift)
+
+#define LM3533_BOOST_ATTR_RW(_name, _NAME) \
+	LM3533_GENERIC_ATTR_RW(_name, LM3533_ATTR_TYPE_BACKLIGHT, \
+				LM3533_REG_BOOST_PWM, LM3533_##_NAME##_MAX, \
+				LM3533_##_NAME##_MASK, LM3533_##_NAME##_SHIFT)
+/*
+ * Boost Over Voltage Protection Select
+ *
+ *   0 -- 16 V (default)
+ *   1 -- 24 V
+ *   2 -- 32 V
+ *   3 -- 40 V
+ */
+static LM3533_BOOST_ATTR_RW(boost_ovp, BOOST_OVP);
+
+/*
+ * Boost Frequency Select
+ *
+ *   0 -- 500 kHz (default)
+ *   1 -- 1 MHz
+ */
+static LM3533_BOOST_ATTR_RW(boost_freq, BOOST_FREQ);
+
+static ssize_t show_output(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	int id = lattr->u.output.id;
+	u8 reg;
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (lattr->type = LM3533_ATTR_TYPE_BACKLIGHT) {
+		reg = LM3533_REG_OUTPUT_CONF1;
+		shift = id - 1;
+		mask = LM3533_BL_ID_MASK << shift;
+	} else {
+		if (id < 4) {
+			reg = LM3533_REG_OUTPUT_CONF1;
+			shift = 2 * id;
+		} else {
+			reg = LM3533_REG_OUTPUT_CONF2;
+			shift = 2 * (id - 4);
+		}
+		mask = LM3533_LED_ID_MASK << shift;
+	}
+
+	ret = lm3533_read(lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	val = (val & mask) >> shift;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_output(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	int id = lattr->u.output.id;
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	if (lattr->type = LM3533_ATTR_TYPE_BACKLIGHT)
+		ret = lm3533_set_hvled_config(lm3533, id, val);
+	else
+		ret = lm3533_set_lvled_config(lm3533, id, val);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define LM3533_OUTPUT_ATTR(_name, _mode, _show, _store, _type, _id) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name = \
+		{ .dev_attr	= __ATTR(_name, _mode, _show, _store), \
+		  .type		= _type, \
+		  .u.output	= { .id = _id }, }
+
+#define LM3533_OUTPUT_ATTR_RW(_name, _type, _id) \
+	LM3533_OUTPUT_ATTR(output_##_name, S_IRUGO | S_IWUSR, \
+					show_output, store_output, _type, _id)
+
+#define LM3533_OUTPUT_HVLED_ATTR_RW(_nr) \
+	LM3533_OUTPUT_ATTR_RW(hvled##_nr, LM3533_ATTR_TYPE_BACKLIGHT, _nr)
+#define LM3533_OUTPUT_LVLED_ATTR_RW(_nr) \
+	LM3533_OUTPUT_ATTR_RW(lvled##_nr, LM3533_ATTR_TYPE_LED, _nr)
+/*
+ * Output config:
+ *
+ * output_hvled<nr> -- 0-1
+ * output_lvled<nr> -- 0-3
+ */
+static LM3533_OUTPUT_HVLED_ATTR_RW(1);
+static LM3533_OUTPUT_HVLED_ATTR_RW(2);
+static LM3533_OUTPUT_LVLED_ATTR_RW(1);
+static LM3533_OUTPUT_LVLED_ATTR_RW(2);
+static LM3533_OUTPUT_LVLED_ATTR_RW(3);
+static LM3533_OUTPUT_LVLED_ATTR_RW(4);
+static LM3533_OUTPUT_LVLED_ATTR_RW(5);
+
+static struct attribute *lm3533_attributes[] = {
+	&lm3533_dev_attr_boost_freq.dev_attr.attr,
+	&lm3533_dev_attr_boost_ovp.dev_attr.attr,
+	&lm3533_dev_attr_output_hvled1.dev_attr.attr,
+	&lm3533_dev_attr_output_hvled2.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled1.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled2.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled3.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled4.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled5.dev_attr.attr,
+	NULL,
+};
+
+#define to_dev_attr(_attr) \
+	container_of(_attr, struct device_attribute, attr)
+
+static mode_t lm3533_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct device_attribute *dattr = to_dev_attr(attr);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(dattr);
+	enum lm3533_attribute_type type = lattr->type;
+	mode_t mode = attr->mode;
+
+	if (!lm3533->have_backlights && type = LM3533_ATTR_TYPE_BACKLIGHT)
+		mode = 0;
+	else if (!lm3533->have_leds && type = LM3533_ATTR_TYPE_LED)
+		mode = 0;
+
+	return mode;
+};
+
+static struct attribute_group lm3533_attribute_group = {
+	.is_visible	= lm3533_attr_is_visible,
+	.attrs		= lm3533_attributes
+};
+
+static int __devinit lm3533_device_als_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int ret;
+
+	if (!pdata->als)
+		return 0;
+
+	lm3533_als_devs[0].platform_data = pdata->als;
+	lm3533_als_devs[0].pdata_size = sizeof(*pdata->als);
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_als_devs, 1, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add ALS device\n");
+		return ret;
+	}
+
+	lm3533->have_als = 1;
+
+	return 0;
+}
+
+static int __devinit lm3533_device_bl_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int i;
+	int ret;
+
+	if (!pdata->backlights || pdata->num_backlights = 0)
+		return 0;
+
+	if (pdata->num_backlights > ARRAY_SIZE(lm3533_bl_devs))
+		pdata->num_backlights = ARRAY_SIZE(lm3533_bl_devs);
+
+	for (i = 0; i < pdata->num_backlights; ++i) {
+		lm3533_bl_devs[i].platform_data = &pdata->backlights[i];
+		lm3533_bl_devs[i].pdata_size = sizeof(pdata->backlights[i]);
+	}
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_bl_devs,
+					pdata->num_backlights, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add backlight devices\n");
+		return ret;
+	}
+
+	lm3533->have_backlights = 1;
+
+	return 0;
+}
+
+static int __devinit lm3533_device_led_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int i;
+	int ret;
+
+	if (!pdata->leds || pdata->num_leds = 0)
+		return 0;
+
+	if (pdata->num_leds > ARRAY_SIZE(lm3533_led_devs))
+		pdata->num_leds = ARRAY_SIZE(lm3533_led_devs);
+
+	for (i = 0; i < pdata->num_leds; ++i) {
+		lm3533_led_devs[i].platform_data = &pdata->leds[i];
+		lm3533_led_devs[i].pdata_size = sizeof(pdata->leds[i]);
+	}
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_led_devs,
+						pdata->num_leds, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add LED devices\n");
+		return ret;
+	}
+
+	lm3533->have_leds = 1;
+
+	return 0;
+}
+
+int __devinit lm3533_device_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int ret;
+
+	dev_dbg(lm3533->dev, "%s\n", __func__);
+
+	if (!pdata) {
+		dev_err(lm3533->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	mutex_init(&lm3533->io_mutex);
+	lm3533->gpio_hwen = pdata->gpio_hwen;
+
+	dev_set_drvdata(lm3533->dev, lm3533);
+
+	if (gpio_is_valid(lm3533->gpio_hwen)) {
+		ret = gpio_request_one(lm3533->gpio_hwen, GPIOF_OUT_INIT_LOW,
+								"lm3533-hwen");
+		if (ret < 0) {
+			dev_err(lm3533->dev,
+				"failed to request HWEN GPIO %d\n",
+				lm3533->gpio_hwen);
+			return ret;
+		}
+	}
+
+	lm3533_enable(lm3533);
+
+	lm3533_device_als_init(lm3533);
+	lm3533_device_bl_init(lm3533);
+	lm3533_device_led_init(lm3533);
+
+	ret = sysfs_create_group(&lm3533->dev->kobj, &lm3533_attribute_group);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	lm3533_debugfs_init(lm3533);
+
+	return 0;
+
+err_unregister:
+	mfd_remove_devices(lm3533->dev);
+	lm3533_disable(lm3533);
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_free(lm3533->gpio_hwen);
+
+	return ret;
+}
+
+void __devexit lm3533_device_exit(struct lm3533 *lm3533)
+{
+	dev_dbg(lm3533->dev, "%s\n", __func__);
+
+	lm3533_debugfs_cleanup(lm3533);
+
+	sysfs_remove_group(&lm3533->dev->kobj, &lm3533_attribute_group);
+
+	mfd_remove_devices(lm3533->dev);
+	lm3533_disable(lm3533);
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_free(lm3533->gpio_hwen);
+}
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/lm3533-ctrlbank.c b/drivers/mfd/lm3533-ctrlbank.c
new file mode 100644
index 0000000..2feb4e8
--- /dev/null
+++ b/drivers/mfd/lm3533-ctrlbank.c
@@ -0,0 +1,134 @@
+/*
+ * lm3533-ctrlbank.c -- LM3533 Generic Control Bank interface
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_BRIGHTNESS_MAX		255
+#define LM3533_MAX_CURRENT_MAX		31
+#define LM3533_PWM_MAX			0x3f
+
+#define LM3533_REG_PWM_BASE		0x14
+#define LM3533_REG_MAX_CURRENT_BASE	0x1f
+#define LM3533_REG_CTRLBANK_ENABLE	0x27
+#define LM3533_REG_BRIGHTNESS_BASE	0x40
+
+
+static inline u8 lm3533_ctrlbank_get_reg(struct lm3533_ctrlbank *cb, u8 base)
+{
+	return base + cb->id;
+}
+
+int lm3533_ctrlbank_enable(struct lm3533_ctrlbank *cb)
+{
+	u8 mask;
+	int ret;
+
+	dev_dbg(cb->dev, "%s - %d\n", __func__, cb->id);
+
+	mask = 1 << cb->id;
+	ret = lm3533_update(cb->lm3533, LM3533_REG_CTRLBANK_ENABLE,
+								mask, mask);
+	if (ret)
+		dev_err(cb->dev, "failed to enable ctrlbank %d\n", cb->id);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_enable);
+
+int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb)
+{
+	u8 mask;
+	int ret;
+
+	dev_dbg(cb->dev, "%s - %d\n", __func__, cb->id);
+
+	mask = 1 << cb->id;
+	ret = lm3533_update(cb->lm3533, LM3533_REG_CTRLBANK_ENABLE, 0, mask);
+	if (ret)
+		dev_err(cb->dev, "failed to disable ctrlbank %d\n", cb->id);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_disable);
+
+#define lm3533_ctrlbank_set(_name, _NAME)				\
+int lm3533_ctrlbank_set_##_name(struct lm3533_ctrlbank *cb, u8 val)	\
+{									\
+	u8 reg;								\
+	int ret;							\
+									\
+	if (val > LM3533_##_NAME##_MAX)					\
+		return -EINVAL;						\
+									\
+	reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_##_NAME##_BASE);	\
+	ret = lm3533_write(cb->lm3533, reg, val);			\
+	if (ret)							\
+		dev_err(cb->dev, "failed to set " #_name "\n");		\
+									\
+	return ret;							\
+}									\
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_set_##_name);
+
+#define lm3533_ctrlbank_get(_name, _NAME)				\
+int lm3533_ctrlbank_get_##_name(struct lm3533_ctrlbank *cb, u8 *val)	\
+{									\
+	u8 reg;								\
+	int ret;							\
+									\
+	reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_##_NAME##_BASE);	\
+	ret = lm3533_read(cb->lm3533, reg, val);			\
+	if (ret)							\
+		dev_err(cb->dev, "failed to get " #_name "\n");		\
+									\
+	return ret;							\
+}									\
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_get_##_name);
+
+lm3533_ctrlbank_set(brightness, BRIGHTNESS);
+lm3533_ctrlbank_get(brightness, BRIGHTNESS);
+
+/*
+ * Full scale current.
+ *
+ * Imax = 5 + val * 0.8 mA, e.g.:
+ *
+ *    0 - 5 mA
+ *     ...
+ *   19 - 20.2 mA (default)
+ *     ...
+ *   31 - 29.8 mA
+ */
+lm3533_ctrlbank_set(max_current, MAX_CURRENT);
+lm3533_ctrlbank_get(max_current, MAX_CURRENT);
+
+/*
+ * PWM control:
+ *
+ *   bit 5 - PWM enabled in Zone 4
+ *   bit 4 - PWM enabled in Zone 3
+ *   bit 3 - PWM enabled in Zone 2
+ *   bit 2 - PWM enabled in Zone 1
+ *   bit 1 - PWM enabled in Zone 0
+ *   bit 0 - PWM enabled
+ */
+lm3533_ctrlbank_set(pwm, PWM);
+lm3533_ctrlbank_get(pwm, PWM);
+
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Control Bank interface");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/lm3533-i2c.c b/drivers/mfd/lm3533-i2c.c
new file mode 100644
index 0000000..d479b62
--- /dev/null
+++ b/drivers/mfd/lm3533-i2c.c
@@ -0,0 +1,115 @@
+/*
+ * lm3533-i2c.c -- LM3533 I2C interface
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+static int lm3533_i2c_read(struct lm3533 *lm3533, u8 reg, u8 *val)
+{
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(lm3533->i2c, reg);
+	if (ret < 0)
+		return ret;
+
+	*val = (u8)ret;
+
+	return 0;
+}
+
+static int lm3533_i2c_write(struct lm3533 *lm3533, u8 reg, u8 val)
+{
+	return i2c_smbus_write_byte_data(lm3533->i2c, reg, val);
+}
+
+static int __devinit lm3533_i2c_probe(struct i2c_client *i2c,
+					const struct i2c_device_id *id)
+{
+	struct lm3533 *lm3533;
+	int ret;
+
+	dev_dbg(&i2c->dev, "%s\n", __func__);
+
+	if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -EINVAL;
+
+	lm3533 = kzalloc(sizeof(*lm3533), GFP_KERNEL);
+	if (!lm3533)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, lm3533);
+
+	lm3533->dev = &i2c->dev;
+	lm3533->i2c = i2c;
+	lm3533->irq = i2c->irq;
+	lm3533->read = lm3533_i2c_read;
+	lm3533->write = lm3533_i2c_write;
+
+	ret = lm3533_device_init(lm3533);
+	if (ret) {
+		kfree(lm3533);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int __devexit lm3533_i2c_remove(struct i2c_client *i2c)
+{
+	struct lm3533 *lm3533 = i2c_get_clientdata(i2c);
+
+	dev_dbg(&i2c->dev, "%s\n", __func__);
+
+	lm3533_device_exit(lm3533);
+
+	kfree(lm3533);
+
+	return 0;
+}
+
+static const struct i2c_device_id lm3533_i2c_ids[] = {
+	{ "lm3533", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, lm3533_i2c_ids);
+
+static struct i2c_driver lm3533_i2c_driver = {
+	.driver = {
+		   .name = "lm3533",
+		   .owner = THIS_MODULE,
+	},
+	.id_table	= lm3533_i2c_ids,
+	.probe		= lm3533_i2c_probe,
+	.remove		= __devexit_p(lm3533_i2c_remove),
+};
+
+static int __init lm3533_i2c_init(void)
+{
+	return i2c_add_driver(&lm3533_i2c_driver);
+}
+subsys_initcall(lm3533_i2c_init);
+
+static void __exit lm3533_i2c_exit(void)
+{
+	i2c_del_driver(&lm3533_i2c_driver);
+}
+module_exit(lm3533_i2c_exit);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 I2C interface");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
new file mode 100644
index 0000000..cb8bf33
--- /dev/null
+++ b/include/linux/mfd/lm3533.h
@@ -0,0 +1,106 @@
+/*
+ * lm3533.h -- LM3533 interface
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_MFD_LM3533_H
+#define __LINUX_MFD_LM3533_H
+
+#include <linux/mutex.h>
+
+
+#define LM3533_ATTR_RO(_name) \
+	DEVICE_ATTR(_name, S_IRUGO, show_##_name, NULL)
+#define LM3533_ATTR_RW(_name) \
+	DEVICE_ATTR(_name, S_IRUGO | S_IWUSR , show_##_name, store_##_name)
+
+struct device;
+struct dentry;
+struct i2c_client;
+
+struct lm3533 {
+	struct device *dev;
+	struct i2c_client *i2c;
+
+	struct mutex io_mutex;
+
+	int (*read)(struct lm3533 *lm3533, u8 reg, u8 *val);
+	int (*write)(struct lm3533 *lm3533, u8 reg, u8 val);
+
+	int gpio_hwen;
+	int irq;
+
+	unsigned have_als:1;
+	unsigned have_backlights:1;
+	unsigned have_leds:1;
+
+#ifdef CONFIG_DEBUG_FS
+	struct dentry *debugfs_root;
+#endif
+};
+
+struct lm3533_ctrlbank {
+	struct lm3533 *lm3533;
+	struct device *dev;
+	int id;
+};
+
+struct lm3533_als_platform_data {
+	unsigned pwm_mode:1;		/* PWM input mode (default analog) */
+	unsigned int_mode:1;		/* interrupt mode (default polled) */
+	unsigned poll_interval;		/* in polled mode (in ms) */
+};
+
+struct lm3533_bl_platform_data {
+	char *name;
+	u8 default_brightness;		/* 0 - 255 */
+	u8 max_current;			/* 0 - 31 */
+	u8 pwm;				/* 0 - 0x3f */
+};
+
+struct lm3533_led_platform_data {
+	char *name;
+	const char *default_trigger;
+	u8 max_current;			/* 0 - 31 */
+	u8 pwm;				/* 0 - 0x3f */
+};
+
+struct lm3533_platform_data {
+	int gpio_hwen;
+
+	struct lm3533_als_platform_data *als;
+
+	struct lm3533_bl_platform_data *backlights;
+	int num_backlights;
+
+	struct lm3533_led_platform_data *leds;
+	int num_leds;
+};
+
+extern int lm3533_ctrlbank_enable(struct lm3533_ctrlbank *cb);
+extern int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb);
+
+extern int lm3533_ctrlbank_set_brightness(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_brightness(struct lm3533_ctrlbank *cb, u8 *val);
+extern int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_max_current(struct lm3533_ctrlbank *cb,
+								u8 *val);
+extern int lm3533_ctrlbank_set_pwm(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_pwm(struct lm3533_ctrlbank *cb, u8 *val);
+
+extern int lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val);
+extern int lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val);
+extern int lm3533_update(struct lm3533 *lm3533, u8 reg, u8 val, u8 mask);
+
+extern int __devinit lm3533_device_init(struct lm3533 *lm3533);
+extern void __devinit lm3533_device_exit(struct lm3533 *lm3533);
+
+#endif	/* __LINUX_MFD_LM3533_H */
-- 
1.7.8.5


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

* [PATCH 2/4] misc: add LM3533 ambient light sensor driver
  2012-04-20 15:30 ` Johan Hovold
@ 2012-04-20 15:30   ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold

Add sub-driver for the ambient light sensor in National Semiconductor /
TI LM3533 lighting power chips.

Raw ADC values as well as current ALS zone can be retrieved through
sysfs. The ALS zone can also be read using a character device
(/dev/lm3533-als) which is updated on zone changes (interrupt driven or
polled).

The driver provides a configuration interface through sysfs.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/misc/Kconfig      |   13 +
 drivers/misc/Makefile     |    1 +
 drivers/misc/lm3533-als.c |  662 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 676 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/lm3533-als.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c779509..cc8cbf0 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -314,6 +314,19 @@ config APDS9802ALS
 	  This driver can also be built as a module.  If so, the module
 	  will be called apds9802als.
 
+config ALS_LM3533
+	tristate "LM3533 Ambient Light Sensor"
+	depends on MFD_LM3533
+	help
+	  If you say yes here you get support for the ambient light sensor on
+	  National Semiconductor / TI LM3533 Lighting Power chips.
+
+	  The sensor can be used to control the LEDs and backlights of the chip
+	  through defining five light zones and three sets of corresponding
+	  brightness target levels. The driver presents a character device
+	  (/dev/lm3533-als) which can be used to retrieve the current light
+	  zone or to poll for zone changes.
+
 config ISL29003
 	tristate "Intersil ISL29003 ambient light sensor"
 	depends on I2C && SYSFS
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 3e1d8010..122a168 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
 obj-$(CONFIG_CS5535_MFGPT)	+= cs5535-mfgpt.o
 obj-$(CONFIG_HP_ILO)		+= hpilo.o
 obj-$(CONFIG_APDS9802ALS)	+= apds9802als.o
+obj-$(CONFIG_ALS_LM3533)	+= lm3533-als.o
 obj-$(CONFIG_ISL29003)		+= isl29003.o
 obj-$(CONFIG_ISL29020)		+= isl29020.o
 obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
diff --git a/drivers/misc/lm3533-als.c b/drivers/misc/lm3533-als.c
new file mode 100644
index 0000000..0348c6d
--- /dev/null
+++ b/drivers/misc/lm3533-als.c
@@ -0,0 +1,662 @@
+/*
+ * lm3533-als.c -- LM3533 Ambient Light Sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/atomic.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+/* Default poll intervall for zone changes in polled mode in ms. */
+#define LM3533_ALS_POLL_INTERVAL		1000
+
+#define LM3533_ALS_RESISTOR_MAX			0x7f
+#define LM3533_ALS_BOUNDARY_MAX			0xff
+#define LM3533_ALS_TARGET_MAX			0xff
+#define LM3533_ALS_ZONE_MAX			4
+
+#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
+#define LM3533_REG_ALS_CONF			0x31
+#define LM3533_REG_ALS_ZONE_INFO		0x34
+#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x37
+#define LM3533_REG_ALS_READ_ADC_RAW		0x38
+#define LM3533_REG_ALS_BOUNDARY0_HIGH		0x50
+#define LM3533_REG_ALS_BOUNDARY0_LOW		0x51
+#define LM3533_REG_ALS_BOUNDARY1_HIGH		0x52
+#define LM3533_REG_ALS_BOUNDARY1_LOW		0x53
+#define LM3533_REG_ALS_BOUNDARY2_HIGH		0x54
+#define LM3533_REG_ALS_BOUNDARY2_LOW		0x55
+#define LM3533_REG_ALS_BOUNDARY3_HIGH		0x56
+#define LM3533_REG_ALS_BOUNDARY3_LOW		0x57
+#define LM3533_REG_ALS_M1_TARGET_0		0x60
+#define LM3533_REG_ALS_M1_TARGET_1		0x61
+#define LM3533_REG_ALS_M1_TARGET_2		0x62
+#define LM3533_REG_ALS_M1_TARGET_3		0x63
+#define LM3533_REG_ALS_M1_TARGET_4		0x64
+#define LM3533_REG_ALS_M2_TARGET_0		0x65
+#define LM3533_REG_ALS_M2_TARGET_1		0x66
+#define LM3533_REG_ALS_M2_TARGET_2		0x67
+#define LM3533_REG_ALS_M2_TARGET_3		0x68
+#define LM3533_REG_ALS_M2_TARGET_4		0x69
+#define LM3533_REG_ALS_M3_TARGET_0		0x6a
+#define LM3533_REG_ALS_M3_TARGET_1		0x6b
+#define LM3533_REG_ALS_M3_TARGET_2		0x6c
+#define LM3533_REG_ALS_M3_TARGET_3		0x6d
+#define LM3533_REG_ALS_M3_TARGET_4		0x6e
+
+#define LM3533_ALS_ENABLE_MASK			0x01
+#define LM3533_ALS_INPUT_MODE_MASK		0x02
+#define LM3533_ALS_INT_ENABLE_MASK		0x01
+
+#define LM3533_ALS_ZONE_SHIFT			2
+#define LM3533_ALS_ZONE_MASK			0x1c
+
+#define LM3533_ALS_FLAG_ZONE_CHANGED		1
+
+
+struct lm3533_als {
+	struct lm3533 *lm3533;
+	struct miscdevice cdev;
+
+	int irq;
+
+	atomic_t open_ref;
+
+	unsigned long poll_interval;
+	struct delayed_work dwork;
+
+	wait_queue_head_t read_wait;
+
+	struct mutex mutex;
+	unsigned long flags;
+	u8 zone;
+};
+
+#define to_lm3533_als(_cdev) \
+	container_of(_cdev, struct lm3533_als, cdev)
+
+
+static int lm3533_als_get_zone(struct lm3533_als *als, u8 *zone)
+{
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(als->cdev.this_device, "failed to read zone\n");
+		return ret;
+	}
+
+	*zone = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
+	*zone = min_t(u8, *zone, LM3533_ALS_ZONE_MAX);
+
+	return 0;
+}
+
+static void lm3533_als_work(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct lm3533_als *als = container_of(dwork, struct lm3533_als, dwork);
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(als, &zone);
+	if (ret)
+		goto out;
+
+	mutex_lock(&als->mutex);
+	if (als->zone != zone) {
+		als->zone = zone;
+		set_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+	}
+	mutex_unlock(&als->mutex);
+
+	if (test_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags))
+		wake_up_interruptible(&als->read_wait);
+out:
+	if (als->irq < 0)
+		schedule_delayed_work(dwork, als->poll_interval);
+}
+
+static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
+{
+	struct lm3533_als *als = dev_id;
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(als, &zone);
+	if (ret)
+		goto out;
+
+	mutex_lock(&als->mutex);
+	als->zone = zone;
+	set_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+	mutex_unlock(&als->mutex);
+
+	wake_up_interruptible(&als->read_wait);
+out:
+	return IRQ_HANDLED;
+}
+
+static int lm3533_als_set_int_mode(struct lm3533_als *als, int enable)
+{
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	return lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
+}
+
+static int lm3533_als_int_enable(struct lm3533_als *als)
+{
+	int ret;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	ret = lm3533_als_set_int_mode(als, 1);
+	if (ret) {
+		dev_warn(als->cdev.this_device, "could not enable int mode\n");
+		goto err;
+	}
+
+	ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					als->cdev.name, als);
+	if (ret) {
+		dev_warn(als->cdev.this_device, "could not get irq %d\n",
+								als->irq);
+		goto err;
+	}
+
+	return ret;
+err:
+	als->irq = -EINVAL;
+
+	return ret;
+}
+
+static int lm3533_als_int_disable(struct lm3533_als *als)
+{
+	int ret;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	free_irq(als->irq, als);
+
+	ret = lm3533_als_set_int_mode(als, 0);
+	if (ret) {
+		dev_warn(als->cdev.this_device,
+					"could not disable int mode\n");
+	}
+
+	return ret;
+}
+
+static int lm3533_als_open(struct inode *inode, struct file *file)
+{
+	struct lm3533_als *als = to_lm3533_als(file->private_data);
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	file->private_data = als;
+
+	if (atomic_inc_return(&als->open_ref) != 1)
+		goto out;
+
+	/* Enable interrupt mode if requested, but fall back to polled mode on
+	 * errors.
+	 */
+	if (als->irq >= 0)
+		lm3533_als_int_enable(als);
+
+	/* Make sure first read returns current zone. */
+	mutex_lock(&als->mutex);
+	clear_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+	als->zone = (u8)-1;
+	mutex_unlock(&als->mutex);
+
+	schedule_delayed_work(&als->dwork, 0);
+out:
+	return nonseekable_open(inode, file);
+}
+
+static int lm3533_als_release(struct inode *inode, struct file *file)
+{
+	struct lm3533_als *als = file->private_data;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	if (atomic_dec_return(&als->open_ref))
+		return 0;
+
+	if (als->irq >= 0)
+		lm3533_als_int_disable(als);
+
+	cancel_delayed_work_sync(&als->dwork);
+
+	return 0;
+}
+
+static unsigned int lm3533_als_poll(struct file *file,
+						struct poll_table_struct *pt)
+{
+	struct lm3533_als *als = file->private_data;
+	unsigned mask = 0;
+
+	poll_wait(file, &als->read_wait, pt);
+
+	if (test_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags))
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+
+static ssize_t lm3533_als_read(struct file *file, char __user *buf,
+						size_t count, loff_t *f_pos)
+{
+	struct lm3533_als *als = file->private_data;
+	int ret;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	if (!count)
+		return 0;
+
+	mutex_lock(&als->mutex);
+	while (!test_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags)) {
+		mutex_unlock(&als->mutex);
+
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		ret = wait_event_interruptible(als->read_wait,
+					test_bit(LM3533_ALS_FLAG_ZONE_CHANGED,
+								&als->flags));
+		if (ret)
+			return -ERESTARTSYS;
+
+		mutex_lock(&als->mutex);
+	}
+
+	count = min(count, sizeof(als->zone));
+	if (copy_to_user(buf, &als->zone, count)) {
+		ret = -EFAULT;
+		goto out;
+	}
+	*f_pos += count;
+	ret = count;
+
+	clear_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+out:
+	mutex_unlock(&als->mutex);
+
+	return ret;
+}
+
+static const struct file_operations lm3533_als_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.open		= lm3533_als_open,
+	.release	= lm3533_als_release,
+	.poll		= lm3533_als_poll,
+	.read		= lm3533_als_read,
+};
+
+struct lm3533_device_attribute {
+	struct device_attribute dev_attr;
+	u8 reg;
+	u8 max;
+};
+
+#define to_lm3533_dev_attr(_dev_attr) \
+	container_of(_dev_attr, struct lm3533_device_attribute, dev_attr)
+
+static ssize_t show_zone(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct miscdevice *cdev = dev_get_drvdata(dev);
+	struct lm3533_als *als = to_lm3533_als(cdev);
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(als, &zone);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
+}
+
+static ssize_t show_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct miscdevice *cdev = dev_get_drvdata(dev);
+	struct lm3533_als *als = to_lm3533_als(cdev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, lm3533_attr->reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct miscdevice *cdev = dev_get_drvdata(dev);
+	struct lm3533_als *als = to_lm3533_als(cdev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > lm3533_attr->max)
+		return -EINVAL;
+
+	ret = lm3533_write(als->lm3533, lm3533_attr->reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	{ .dev_attr = __ATTR(_name, _mode, _show, _store), \
+	  .reg = _reg, \
+	  .max = _max }
+
+#define LM3533_REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name \
+		= REG_ATTR(_name, _mode, _show, _store, _reg, _max)
+
+#define LM3533_REG_ATTR_RO(_name, _reg) \
+	LM3533_REG_ATTR(_name, S_IRUGO, show_lm3533_als_reg, NULL, _reg, 0)
+#define LM3533_REG_ATTR_RW(_name, _reg, _max) \
+	LM3533_REG_ATTR(_name, S_IRUGO | S_IWUSR, show_lm3533_als_reg, \
+					store_lm3533_als_reg, _reg, _max)
+
+#define ALS_BOUNDARY_LOW_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(boundary##_nr##_low, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_LOW, LM3533_ALS_BOUNDARY_MAX)
+
+#define ALS_BOUNDARY_HIGH_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(boundary##_nr##_high, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_HIGH, LM3533_ALS_BOUNDARY_MAX)
+
+/* ALS Zone boundaries
+ *
+ * boundary[0-3]_low	0-255
+ * boundary[0-3]_high	0-255
+ */
+static ALS_BOUNDARY_LOW_ATTR_RW(0);
+static ALS_BOUNDARY_LOW_ATTR_RW(1);
+static ALS_BOUNDARY_LOW_ATTR_RW(2);
+static ALS_BOUNDARY_LOW_ATTR_RW(3);
+
+static ALS_BOUNDARY_HIGH_ATTR_RW(0);
+static ALS_BOUNDARY_HIGH_ATTR_RW(1);
+static ALS_BOUNDARY_HIGH_ATTR_RW(2);
+static ALS_BOUNDARY_HIGH_ATTR_RW(3);
+
+#define ALS_TARGET_ATTR_RW(_mapper, _nr) \
+	LM3533_REG_ATTR_RW(target##_mapper##_##_nr, \
+		LM3533_REG_ALS_M##_mapper##_TARGET_##_nr, LM3533_ALS_TARGET_MAX)
+
+/* ALS Mapper targets
+ *
+ * target[1-3]_[0-4]		0-255
+ */
+static ALS_TARGET_ATTR_RW(1, 0);
+static ALS_TARGET_ATTR_RW(1, 1);
+static ALS_TARGET_ATTR_RW(1, 2);
+static ALS_TARGET_ATTR_RW(1, 3);
+static ALS_TARGET_ATTR_RW(1, 4);
+
+static ALS_TARGET_ATTR_RW(2, 0);
+static ALS_TARGET_ATTR_RW(2, 1);
+static ALS_TARGET_ATTR_RW(2, 2);
+static ALS_TARGET_ATTR_RW(2, 3);
+static ALS_TARGET_ATTR_RW(2, 4);
+
+static ALS_TARGET_ATTR_RW(3, 0);
+static ALS_TARGET_ATTR_RW(3, 1);
+static ALS_TARGET_ATTR_RW(3, 2);
+static ALS_TARGET_ATTR_RW(3, 3);
+static ALS_TARGET_ATTR_RW(3, 4);
+
+/* ALS ADC
+ *
+ * adc_average	0-255
+ * adc_raw	0-255
+ */
+static LM3533_REG_ATTR_RO(adc_average, LM3533_REG_ALS_READ_ADC_AVERAGE);
+static LM3533_REG_ATTR_RO(adc_raw, LM3533_REG_ALS_READ_ADC_RAW);
+
+/* ALS Gain resistor setting
+ *
+ * gain		0-31
+ */
+static LM3533_REG_ATTR_RW(gain, LM3533_REG_ALS_RESISTOR_SELECT,
+						LM3533_ALS_RESISTOR_MAX);
+/* ALS Current Zone
+ *
+ * zone		0-4
+ */
+static LM3533_ATTR_RO(zone);
+
+static struct attribute *lm3533_als_attributes[] = {
+	&lm3533_dev_attr_adc_average.dev_attr.attr,
+	&lm3533_dev_attr_adc_raw.dev_attr.attr,
+	&lm3533_dev_attr_boundary0_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary0_low.dev_attr.attr,
+	&lm3533_dev_attr_boundary1_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary1_low.dev_attr.attr,
+	&lm3533_dev_attr_boundary2_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary2_low.dev_attr.attr,
+	&lm3533_dev_attr_boundary3_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary3_low.dev_attr.attr,
+	&lm3533_dev_attr_target1_0.dev_attr.attr,
+	&lm3533_dev_attr_target1_1.dev_attr.attr,
+	&lm3533_dev_attr_target1_2.dev_attr.attr,
+	&lm3533_dev_attr_target1_3.dev_attr.attr,
+	&lm3533_dev_attr_target1_4.dev_attr.attr,
+	&lm3533_dev_attr_target2_0.dev_attr.attr,
+	&lm3533_dev_attr_target2_1.dev_attr.attr,
+	&lm3533_dev_attr_target2_2.dev_attr.attr,
+	&lm3533_dev_attr_target2_3.dev_attr.attr,
+	&lm3533_dev_attr_target2_4.dev_attr.attr,
+	&lm3533_dev_attr_target3_0.dev_attr.attr,
+	&lm3533_dev_attr_target3_1.dev_attr.attr,
+	&lm3533_dev_attr_target3_2.dev_attr.attr,
+	&lm3533_dev_attr_target3_3.dev_attr.attr,
+	&lm3533_dev_attr_target3_4.dev_attr.attr,
+	&lm3533_dev_attr_gain.dev_attr.attr,
+	&dev_attr_zone.attr,
+	NULL,
+};
+
+static struct attribute_group lm3533_als_attribute_group = {
+	.attrs = lm3533_als_attributes
+};
+
+static int __devinit lm3533_als_set_input_mode(struct lm3533 *lm3533,
+								int pwm_mode)
+{
+	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
+	u8 val;
+	int ret;
+
+	if (pwm_mode)
+		val = mask;	/* pwm input */
+	else
+		val = 0;	/* analog input */
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret) {
+		dev_err(lm3533->dev,
+				"failed to set input mode %d\n", pwm_mode);
+	}
+
+	return ret;
+}
+
+static int __devinit lm3533_als_enable(struct lm3533 *lm3533)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to enable ALS\n");
+
+	return ret;
+}
+
+static int lm3533_als_disable(struct lm3533 *lm3533)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, 0, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to disable ALS\n");
+
+	return ret;
+}
+
+static int __devinit lm3533_als_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_als_platform_data *pdata;
+	struct lm3533_als *als;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	als = kzalloc(sizeof(*als), GFP_KERNEL);
+	if (!als)
+		return -ENOMEM;
+
+	als->lm3533 = lm3533;
+	if (pdata->int_mode)
+		als->irq = lm3533->irq;
+	else
+		als->irq = -EINVAL;
+
+	if (!pdata->poll_interval)
+		pdata->poll_interval = LM3533_ALS_POLL_INTERVAL;
+	als->poll_interval = msecs_to_jiffies(pdata->poll_interval);
+
+	als->cdev.name = "lm3533-als";
+	als->cdev.parent = pdev->dev.parent;
+	als->cdev.minor = MISC_DYNAMIC_MINOR;
+	als->cdev.fops = &lm3533_als_fops;
+
+	mutex_init(&als->mutex);
+	INIT_DELAYED_WORK(&als->dwork, lm3533_als_work);
+	init_waitqueue_head(&als->read_wait);
+
+	platform_set_drvdata(pdev, als);
+
+	ret = lm3533_als_set_input_mode(lm3533, pdata->pwm_mode);
+	if (ret)
+		goto err;
+
+	ret = lm3533_als_enable(lm3533);
+	if (ret)
+		goto err;
+
+	ret = misc_register(&als->cdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register ALS\n");
+		goto err_disable;
+	}
+
+	ret = sysfs_create_group(&als->cdev.this_device->kobj,
+						&lm3533_als_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	return 0;
+
+err_unregister:
+	misc_deregister(&als->cdev);
+err_disable:
+	lm3533_als_disable(lm3533);
+err:
+	kfree(als);
+
+	return ret;
+}
+
+static int __devexit lm3533_als_remove(struct platform_device *pdev)
+{
+	struct lm3533_als *als = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	sysfs_remove_group(&als->cdev.this_device->kobj,
+						&lm3533_als_attribute_group);
+	misc_deregister(&als->cdev);
+	lm3533_als_disable(als->lm3533);
+	kfree(als);
+
+	return 0;
+}
+
+static struct platform_driver lm3533_als_driver = {
+	.driver = {
+		.name = "lm3533-als",
+		.owner = THIS_MODULE,
+	},
+	.probe		= lm3533_als_probe,
+	.remove		= __devexit_p(lm3533_als_remove),
+};
+module_platform_driver(lm3533_als_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
-- 
1.7.8.5


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

* [PATCH 2/4] misc: add LM3533 ambient light sensor driver
@ 2012-04-20 15:30   ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold

Add sub-driver for the ambient light sensor in National Semiconductor /
TI LM3533 lighting power chips.

Raw ADC values as well as current ALS zone can be retrieved through
sysfs. The ALS zone can also be read using a character device
(/dev/lm3533-als) which is updated on zone changes (interrupt driven or
polled).

The driver provides a configuration interface through sysfs.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/misc/Kconfig      |   13 +
 drivers/misc/Makefile     |    1 +
 drivers/misc/lm3533-als.c |  662 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 676 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/lm3533-als.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c779509..cc8cbf0 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -314,6 +314,19 @@ config APDS9802ALS
 	  This driver can also be built as a module.  If so, the module
 	  will be called apds9802als.
 
+config ALS_LM3533
+	tristate "LM3533 Ambient Light Sensor"
+	depends on MFD_LM3533
+	help
+	  If you say yes here you get support for the ambient light sensor on
+	  National Semiconductor / TI LM3533 Lighting Power chips.
+
+	  The sensor can be used to control the LEDs and backlights of the chip
+	  through defining five light zones and three sets of corresponding
+	  brightness target levels. The driver presents a character device
+	  (/dev/lm3533-als) which can be used to retrieve the current light
+	  zone or to poll for zone changes.
+
 config ISL29003
 	tristate "Intersil ISL29003 ambient light sensor"
 	depends on I2C && SYSFS
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 3e1d8010..122a168 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_SGI_GRU)		+= sgi-gru/
 obj-$(CONFIG_CS5535_MFGPT)	+= cs5535-mfgpt.o
 obj-$(CONFIG_HP_ILO)		+= hpilo.o
 obj-$(CONFIG_APDS9802ALS)	+= apds9802als.o
+obj-$(CONFIG_ALS_LM3533)	+= lm3533-als.o
 obj-$(CONFIG_ISL29003)		+= isl29003.o
 obj-$(CONFIG_ISL29020)		+= isl29020.o
 obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
diff --git a/drivers/misc/lm3533-als.c b/drivers/misc/lm3533-als.c
new file mode 100644
index 0000000..0348c6d
--- /dev/null
+++ b/drivers/misc/lm3533-als.c
@@ -0,0 +1,662 @@
+/*
+ * lm3533-als.c -- LM3533 Ambient Light Sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/atomic.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+/* Default poll intervall for zone changes in polled mode in ms. */
+#define LM3533_ALS_POLL_INTERVAL		1000
+
+#define LM3533_ALS_RESISTOR_MAX			0x7f
+#define LM3533_ALS_BOUNDARY_MAX			0xff
+#define LM3533_ALS_TARGET_MAX			0xff
+#define LM3533_ALS_ZONE_MAX			4
+
+#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
+#define LM3533_REG_ALS_CONF			0x31
+#define LM3533_REG_ALS_ZONE_INFO		0x34
+#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x37
+#define LM3533_REG_ALS_READ_ADC_RAW		0x38
+#define LM3533_REG_ALS_BOUNDARY0_HIGH		0x50
+#define LM3533_REG_ALS_BOUNDARY0_LOW		0x51
+#define LM3533_REG_ALS_BOUNDARY1_HIGH		0x52
+#define LM3533_REG_ALS_BOUNDARY1_LOW		0x53
+#define LM3533_REG_ALS_BOUNDARY2_HIGH		0x54
+#define LM3533_REG_ALS_BOUNDARY2_LOW		0x55
+#define LM3533_REG_ALS_BOUNDARY3_HIGH		0x56
+#define LM3533_REG_ALS_BOUNDARY3_LOW		0x57
+#define LM3533_REG_ALS_M1_TARGET_0		0x60
+#define LM3533_REG_ALS_M1_TARGET_1		0x61
+#define LM3533_REG_ALS_M1_TARGET_2		0x62
+#define LM3533_REG_ALS_M1_TARGET_3		0x63
+#define LM3533_REG_ALS_M1_TARGET_4		0x64
+#define LM3533_REG_ALS_M2_TARGET_0		0x65
+#define LM3533_REG_ALS_M2_TARGET_1		0x66
+#define LM3533_REG_ALS_M2_TARGET_2		0x67
+#define LM3533_REG_ALS_M2_TARGET_3		0x68
+#define LM3533_REG_ALS_M2_TARGET_4		0x69
+#define LM3533_REG_ALS_M3_TARGET_0		0x6a
+#define LM3533_REG_ALS_M3_TARGET_1		0x6b
+#define LM3533_REG_ALS_M3_TARGET_2		0x6c
+#define LM3533_REG_ALS_M3_TARGET_3		0x6d
+#define LM3533_REG_ALS_M3_TARGET_4		0x6e
+
+#define LM3533_ALS_ENABLE_MASK			0x01
+#define LM3533_ALS_INPUT_MODE_MASK		0x02
+#define LM3533_ALS_INT_ENABLE_MASK		0x01
+
+#define LM3533_ALS_ZONE_SHIFT			2
+#define LM3533_ALS_ZONE_MASK			0x1c
+
+#define LM3533_ALS_FLAG_ZONE_CHANGED		1
+
+
+struct lm3533_als {
+	struct lm3533 *lm3533;
+	struct miscdevice cdev;
+
+	int irq;
+
+	atomic_t open_ref;
+
+	unsigned long poll_interval;
+	struct delayed_work dwork;
+
+	wait_queue_head_t read_wait;
+
+	struct mutex mutex;
+	unsigned long flags;
+	u8 zone;
+};
+
+#define to_lm3533_als(_cdev) \
+	container_of(_cdev, struct lm3533_als, cdev)
+
+
+static int lm3533_als_get_zone(struct lm3533_als *als, u8 *zone)
+{
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(als->cdev.this_device, "failed to read zone\n");
+		return ret;
+	}
+
+	*zone = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
+	*zone = min_t(u8, *zone, LM3533_ALS_ZONE_MAX);
+
+	return 0;
+}
+
+static void lm3533_als_work(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct lm3533_als *als = container_of(dwork, struct lm3533_als, dwork);
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(als, &zone);
+	if (ret)
+		goto out;
+
+	mutex_lock(&als->mutex);
+	if (als->zone != zone) {
+		als->zone = zone;
+		set_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+	}
+	mutex_unlock(&als->mutex);
+
+	if (test_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags))
+		wake_up_interruptible(&als->read_wait);
+out:
+	if (als->irq < 0)
+		schedule_delayed_work(dwork, als->poll_interval);
+}
+
+static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
+{
+	struct lm3533_als *als = dev_id;
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(als, &zone);
+	if (ret)
+		goto out;
+
+	mutex_lock(&als->mutex);
+	als->zone = zone;
+	set_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+	mutex_unlock(&als->mutex);
+
+	wake_up_interruptible(&als->read_wait);
+out:
+	return IRQ_HANDLED;
+}
+
+static int lm3533_als_set_int_mode(struct lm3533_als *als, int enable)
+{
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	return lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
+}
+
+static int lm3533_als_int_enable(struct lm3533_als *als)
+{
+	int ret;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	ret = lm3533_als_set_int_mode(als, 1);
+	if (ret) {
+		dev_warn(als->cdev.this_device, "could not enable int mode\n");
+		goto err;
+	}
+
+	ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					als->cdev.name, als);
+	if (ret) {
+		dev_warn(als->cdev.this_device, "could not get irq %d\n",
+								als->irq);
+		goto err;
+	}
+
+	return ret;
+err:
+	als->irq = -EINVAL;
+
+	return ret;
+}
+
+static int lm3533_als_int_disable(struct lm3533_als *als)
+{
+	int ret;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	free_irq(als->irq, als);
+
+	ret = lm3533_als_set_int_mode(als, 0);
+	if (ret) {
+		dev_warn(als->cdev.this_device,
+					"could not disable int mode\n");
+	}
+
+	return ret;
+}
+
+static int lm3533_als_open(struct inode *inode, struct file *file)
+{
+	struct lm3533_als *als = to_lm3533_als(file->private_data);
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	file->private_data = als;
+
+	if (atomic_inc_return(&als->open_ref) != 1)
+		goto out;
+
+	/* Enable interrupt mode if requested, but fall back to polled mode on
+	 * errors.
+	 */
+	if (als->irq >= 0)
+		lm3533_als_int_enable(als);
+
+	/* Make sure first read returns current zone. */
+	mutex_lock(&als->mutex);
+	clear_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+	als->zone = (u8)-1;
+	mutex_unlock(&als->mutex);
+
+	schedule_delayed_work(&als->dwork, 0);
+out:
+	return nonseekable_open(inode, file);
+}
+
+static int lm3533_als_release(struct inode *inode, struct file *file)
+{
+	struct lm3533_als *als = file->private_data;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	if (atomic_dec_return(&als->open_ref))
+		return 0;
+
+	if (als->irq >= 0)
+		lm3533_als_int_disable(als);
+
+	cancel_delayed_work_sync(&als->dwork);
+
+	return 0;
+}
+
+static unsigned int lm3533_als_poll(struct file *file,
+						struct poll_table_struct *pt)
+{
+	struct lm3533_als *als = file->private_data;
+	unsigned mask = 0;
+
+	poll_wait(file, &als->read_wait, pt);
+
+	if (test_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags))
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+
+static ssize_t lm3533_als_read(struct file *file, char __user *buf,
+						size_t count, loff_t *f_pos)
+{
+	struct lm3533_als *als = file->private_data;
+	int ret;
+
+	dev_dbg(als->cdev.this_device, "%s\n", __func__);
+
+	if (!count)
+		return 0;
+
+	mutex_lock(&als->mutex);
+	while (!test_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags)) {
+		mutex_unlock(&als->mutex);
+
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		ret = wait_event_interruptible(als->read_wait,
+					test_bit(LM3533_ALS_FLAG_ZONE_CHANGED,
+								&als->flags));
+		if (ret)
+			return -ERESTARTSYS;
+
+		mutex_lock(&als->mutex);
+	}
+
+	count = min(count, sizeof(als->zone));
+	if (copy_to_user(buf, &als->zone, count)) {
+		ret = -EFAULT;
+		goto out;
+	}
+	*f_pos += count;
+	ret = count;
+
+	clear_bit(LM3533_ALS_FLAG_ZONE_CHANGED, &als->flags);
+out:
+	mutex_unlock(&als->mutex);
+
+	return ret;
+}
+
+static const struct file_operations lm3533_als_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.open		= lm3533_als_open,
+	.release	= lm3533_als_release,
+	.poll		= lm3533_als_poll,
+	.read		= lm3533_als_read,
+};
+
+struct lm3533_device_attribute {
+	struct device_attribute dev_attr;
+	u8 reg;
+	u8 max;
+};
+
+#define to_lm3533_dev_attr(_dev_attr) \
+	container_of(_dev_attr, struct lm3533_device_attribute, dev_attr)
+
+static ssize_t show_zone(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct miscdevice *cdev = dev_get_drvdata(dev);
+	struct lm3533_als *als = to_lm3533_als(cdev);
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(als, &zone);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
+}
+
+static ssize_t show_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct miscdevice *cdev = dev_get_drvdata(dev);
+	struct lm3533_als *als = to_lm3533_als(cdev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, lm3533_attr->reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct miscdevice *cdev = dev_get_drvdata(dev);
+	struct lm3533_als *als = to_lm3533_als(cdev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > lm3533_attr->max)
+		return -EINVAL;
+
+	ret = lm3533_write(als->lm3533, lm3533_attr->reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	{ .dev_attr = __ATTR(_name, _mode, _show, _store), \
+	  .reg = _reg, \
+	  .max = _max }
+
+#define LM3533_REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name \
+		= REG_ATTR(_name, _mode, _show, _store, _reg, _max)
+
+#define LM3533_REG_ATTR_RO(_name, _reg) \
+	LM3533_REG_ATTR(_name, S_IRUGO, show_lm3533_als_reg, NULL, _reg, 0)
+#define LM3533_REG_ATTR_RW(_name, _reg, _max) \
+	LM3533_REG_ATTR(_name, S_IRUGO | S_IWUSR, show_lm3533_als_reg, \
+					store_lm3533_als_reg, _reg, _max)
+
+#define ALS_BOUNDARY_LOW_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(boundary##_nr##_low, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_LOW, LM3533_ALS_BOUNDARY_MAX)
+
+#define ALS_BOUNDARY_HIGH_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(boundary##_nr##_high, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_HIGH, LM3533_ALS_BOUNDARY_MAX)
+
+/* ALS Zone boundaries
+ *
+ * boundary[0-3]_low	0-255
+ * boundary[0-3]_high	0-255
+ */
+static ALS_BOUNDARY_LOW_ATTR_RW(0);
+static ALS_BOUNDARY_LOW_ATTR_RW(1);
+static ALS_BOUNDARY_LOW_ATTR_RW(2);
+static ALS_BOUNDARY_LOW_ATTR_RW(3);
+
+static ALS_BOUNDARY_HIGH_ATTR_RW(0);
+static ALS_BOUNDARY_HIGH_ATTR_RW(1);
+static ALS_BOUNDARY_HIGH_ATTR_RW(2);
+static ALS_BOUNDARY_HIGH_ATTR_RW(3);
+
+#define ALS_TARGET_ATTR_RW(_mapper, _nr) \
+	LM3533_REG_ATTR_RW(target##_mapper##_##_nr, \
+		LM3533_REG_ALS_M##_mapper##_TARGET_##_nr, LM3533_ALS_TARGET_MAX)
+
+/* ALS Mapper targets
+ *
+ * target[1-3]_[0-4]		0-255
+ */
+static ALS_TARGET_ATTR_RW(1, 0);
+static ALS_TARGET_ATTR_RW(1, 1);
+static ALS_TARGET_ATTR_RW(1, 2);
+static ALS_TARGET_ATTR_RW(1, 3);
+static ALS_TARGET_ATTR_RW(1, 4);
+
+static ALS_TARGET_ATTR_RW(2, 0);
+static ALS_TARGET_ATTR_RW(2, 1);
+static ALS_TARGET_ATTR_RW(2, 2);
+static ALS_TARGET_ATTR_RW(2, 3);
+static ALS_TARGET_ATTR_RW(2, 4);
+
+static ALS_TARGET_ATTR_RW(3, 0);
+static ALS_TARGET_ATTR_RW(3, 1);
+static ALS_TARGET_ATTR_RW(3, 2);
+static ALS_TARGET_ATTR_RW(3, 3);
+static ALS_TARGET_ATTR_RW(3, 4);
+
+/* ALS ADC
+ *
+ * adc_average	0-255
+ * adc_raw	0-255
+ */
+static LM3533_REG_ATTR_RO(adc_average, LM3533_REG_ALS_READ_ADC_AVERAGE);
+static LM3533_REG_ATTR_RO(adc_raw, LM3533_REG_ALS_READ_ADC_RAW);
+
+/* ALS Gain resistor setting
+ *
+ * gain		0-31
+ */
+static LM3533_REG_ATTR_RW(gain, LM3533_REG_ALS_RESISTOR_SELECT,
+						LM3533_ALS_RESISTOR_MAX);
+/* ALS Current Zone
+ *
+ * zone		0-4
+ */
+static LM3533_ATTR_RO(zone);
+
+static struct attribute *lm3533_als_attributes[] = {
+	&lm3533_dev_attr_adc_average.dev_attr.attr,
+	&lm3533_dev_attr_adc_raw.dev_attr.attr,
+	&lm3533_dev_attr_boundary0_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary0_low.dev_attr.attr,
+	&lm3533_dev_attr_boundary1_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary1_low.dev_attr.attr,
+	&lm3533_dev_attr_boundary2_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary2_low.dev_attr.attr,
+	&lm3533_dev_attr_boundary3_high.dev_attr.attr,
+	&lm3533_dev_attr_boundary3_low.dev_attr.attr,
+	&lm3533_dev_attr_target1_0.dev_attr.attr,
+	&lm3533_dev_attr_target1_1.dev_attr.attr,
+	&lm3533_dev_attr_target1_2.dev_attr.attr,
+	&lm3533_dev_attr_target1_3.dev_attr.attr,
+	&lm3533_dev_attr_target1_4.dev_attr.attr,
+	&lm3533_dev_attr_target2_0.dev_attr.attr,
+	&lm3533_dev_attr_target2_1.dev_attr.attr,
+	&lm3533_dev_attr_target2_2.dev_attr.attr,
+	&lm3533_dev_attr_target2_3.dev_attr.attr,
+	&lm3533_dev_attr_target2_4.dev_attr.attr,
+	&lm3533_dev_attr_target3_0.dev_attr.attr,
+	&lm3533_dev_attr_target3_1.dev_attr.attr,
+	&lm3533_dev_attr_target3_2.dev_attr.attr,
+	&lm3533_dev_attr_target3_3.dev_attr.attr,
+	&lm3533_dev_attr_target3_4.dev_attr.attr,
+	&lm3533_dev_attr_gain.dev_attr.attr,
+	&dev_attr_zone.attr,
+	NULL,
+};
+
+static struct attribute_group lm3533_als_attribute_group = {
+	.attrs = lm3533_als_attributes
+};
+
+static int __devinit lm3533_als_set_input_mode(struct lm3533 *lm3533,
+								int pwm_mode)
+{
+	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
+	u8 val;
+	int ret;
+
+	if (pwm_mode)
+		val = mask;	/* pwm input */
+	else
+		val = 0;	/* analog input */
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret) {
+		dev_err(lm3533->dev,
+				"failed to set input mode %d\n", pwm_mode);
+	}
+
+	return ret;
+}
+
+static int __devinit lm3533_als_enable(struct lm3533 *lm3533)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to enable ALS\n");
+
+	return ret;
+}
+
+static int lm3533_als_disable(struct lm3533 *lm3533)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, 0, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to disable ALS\n");
+
+	return ret;
+}
+
+static int __devinit lm3533_als_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_als_platform_data *pdata;
+	struct lm3533_als *als;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	als = kzalloc(sizeof(*als), GFP_KERNEL);
+	if (!als)
+		return -ENOMEM;
+
+	als->lm3533 = lm3533;
+	if (pdata->int_mode)
+		als->irq = lm3533->irq;
+	else
+		als->irq = -EINVAL;
+
+	if (!pdata->poll_interval)
+		pdata->poll_interval = LM3533_ALS_POLL_INTERVAL;
+	als->poll_interval = msecs_to_jiffies(pdata->poll_interval);
+
+	als->cdev.name = "lm3533-als";
+	als->cdev.parent = pdev->dev.parent;
+	als->cdev.minor = MISC_DYNAMIC_MINOR;
+	als->cdev.fops = &lm3533_als_fops;
+
+	mutex_init(&als->mutex);
+	INIT_DELAYED_WORK(&als->dwork, lm3533_als_work);
+	init_waitqueue_head(&als->read_wait);
+
+	platform_set_drvdata(pdev, als);
+
+	ret = lm3533_als_set_input_mode(lm3533, pdata->pwm_mode);
+	if (ret)
+		goto err;
+
+	ret = lm3533_als_enable(lm3533);
+	if (ret)
+		goto err;
+
+	ret = misc_register(&als->cdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register ALS\n");
+		goto err_disable;
+	}
+
+	ret = sysfs_create_group(&als->cdev.this_device->kobj,
+						&lm3533_als_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	return 0;
+
+err_unregister:
+	misc_deregister(&als->cdev);
+err_disable:
+	lm3533_als_disable(lm3533);
+err:
+	kfree(als);
+
+	return ret;
+}
+
+static int __devexit lm3533_als_remove(struct platform_device *pdev)
+{
+	struct lm3533_als *als = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	sysfs_remove_group(&als->cdev.this_device->kobj,
+						&lm3533_als_attribute_group);
+	misc_deregister(&als->cdev);
+	lm3533_als_disable(als->lm3533);
+	kfree(als);
+
+	return 0;
+}
+
+static struct platform_driver lm3533_als_driver = {
+	.driver = {
+		.name = "lm3533-als",
+		.owner = THIS_MODULE,
+	},
+	.probe		= lm3533_als_probe,
+	.remove		= __devexit_p(lm3533_als_remove),
+};
+module_platform_driver(lm3533_als_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
-- 
1.7.8.5


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

* [PATCH 3/4] leds: add LM3533 LED driver
  2012-04-20 15:30 ` Johan Hovold
@ 2012-04-20 15:30   ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold

Add sub-driver for the LEDs in National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels, hardware accelerated blinking
as well as ambient-light-sensor and pwm input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/leds/Kconfig       |   13 +
 drivers/leds/Makefile      |    1 +
 drivers/leds/leds-lm3533.c |  713 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 727 insertions(+), 0 deletions(-)
 create mode 100644 drivers/leds/leds-lm3533.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index ff4b8cf..22a4e62 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -50,6 +50,19 @@ config LEDS_LM3530
 	  controlled manually or using PWM input or using ambient
 	  light automatically.
 
+config LEDS_LM3533
+	tristate "LED support for LM3533"
+	depends on LEDS_CLASS
+	depends on MFD_LM3533
+	help
+	  This option enables support for the LEDs on National Semiconductor /
+	  TI LM3533 Lighting Power chips.
+
+	  The LEDs can be controlled directly, through PWM input, or by the
+	  on-chip ambient light sensor. The chip supports hardware-accelerated
+	  blinking with maximum on and off periods of 9.8 and 77 seconds
+	  respectively.
+
 config LEDS_LOCOMO
 	tristate "LED Support for Locomo device"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 890481c..f39a526 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_ATMEL_PWM)		+= leds-atmel-pwm.o
 obj-$(CONFIG_LEDS_BD2802)		+= leds-bd2802.o
 obj-$(CONFIG_LEDS_LOCOMO)		+= leds-locomo.o
 obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
+obj-$(CONFIG_LEDS_LM3533)		+= leds-lm3533.o
 obj-$(CONFIG_LEDS_MIKROTIK_RB532)	+= leds-rb532.o
 obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
 obj-$(CONFIG_LEDS_NET48XX)		+= leds-net48xx.o
diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c
new file mode 100644
index 0000000..8e34b7a
--- /dev/null
+++ b/drivers/leds/leds-lm3533.c
@@ -0,0 +1,713 @@
+/*
+ * leds-lm3533.c -- LM3533 LED driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/mfd/core.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_LVCTRLBANK_MIN		2
+#define LM3533_LVCTRLBANK_MAX		5
+#define LM3533_LVCTRLBANK_COUNT		4
+#define LM3533_RISEFALLTIME_MAX		7
+#define LM3533_ALS_LV_MIN		2
+#define LM3533_ALS_LV_MAX		3
+
+#define LM3533_REG_CTRLBANK_BCONF_BASE		0x1b
+#define LM3533_REG_PATTERN_ENABLE		0x28
+#define LM3533_REG_PATTERN_LOW_TIME_BASE	0x71
+#define LM3533_REG_PATTERN_HIGH_TIME_BASE	0x72
+#define LM3533_REG_PATTERN_RISETIME_BASE	0x74
+#define LM3533_REG_PATTERN_FALLTIME_BASE	0x75
+
+#define LM3533_REG_PATTERN_STEP			0x10
+
+#define LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK	0x04
+#define LM3533_REG_CTRLBANK_BCONF_ALS_MASK	0x03
+
+#define LM3533_LED_FLAG_PATTERN_ENABLE		1
+
+
+struct lm3533_led {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct led_classdev cdev;
+	int id;
+
+	struct mutex mutex;
+	unsigned long flags;
+
+	struct work_struct work;
+	u8 new_brightness;
+};
+
+#define to_lm3533_led(_cdev) \
+	container_of(_cdev, struct lm3533_led, cdev)
+
+
+static inline int lm3533_led_get_ctrlbank_id(struct lm3533_led *led)
+{
+	return led->id + 2;
+}
+
+static inline u8 lm3533_led_get_lv_reg(struct lm3533_led *led, u8 base)
+{
+	return base + led->id;
+}
+
+static inline u8 lm3533_led_get_pattern(struct lm3533_led *led)
+{
+	return led->id;
+}
+
+static inline u8 lm3533_led_get_pattern_reg(struct lm3533_led *led,
+								u8 base)
+{
+	return base + lm3533_led_get_pattern(led) * LM3533_REG_PATTERN_STEP;
+}
+
+static int lm3533_led_pattern_enable(struct lm3533_led *led, int enable)
+{
+	u8 mask;
+	u8 val;
+	int pattern;
+	int state;
+	int ret = 0;
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, enable);
+
+	mutex_lock(&led->mutex);
+
+	state = test_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+	if ((enable && state) || (!enable && !state))
+		goto out;
+
+	pattern = lm3533_led_get_pattern(led);
+	mask = 1 << (2 * pattern);
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, LM3533_REG_PATTERN_ENABLE, val, mask);
+	if (ret) {
+		dev_err(led->cdev.dev, "failed to enable pattern %d (%d)\n",
+							pattern, enable);
+		goto out;
+	}
+
+	__change_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+out:
+	mutex_unlock(&led->mutex);
+
+	return ret;
+}
+
+static void lm3533_led_work(struct work_struct *work)
+{
+	struct lm3533_led *led = container_of(work, struct lm3533_led, work);
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, led->new_brightness);
+
+	if (led->new_brightness == 0)
+		lm3533_led_pattern_enable(led, 0);	/* disable blink */
+
+	lm3533_ctrlbank_set_brightness(&led->cb, led->new_brightness);
+}
+
+static void lm3533_led_set(struct led_classdev *cdev,
+						enum led_brightness value)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, value);
+
+	led->new_brightness = value;
+	schedule_work(&led->work);
+}
+
+static enum led_brightness lm3533_led_get(struct led_classdev *cdev)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&led->cb, &val);
+	if (ret)
+		return ret;
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, val);
+
+	return val;
+}
+
+/* Pattern generator defines -- delays in us */
+#define LM3533_LED_DELAY_GROUP1_BASE	0x00
+#define LM3533_LED_DELAY_GROUP2_BASE	0x3d
+#define LM3533_LED_DELAY_GROUP3_BASE	0x80
+#define LM3533_LED_DELAY_MAX		0xff
+
+#define LM3533_LED_DELAY_GROUP1_STEP	16384
+#define LM3533_LED_DELAY_GROUP2_STEP	131072
+#define LM3533_LED_DELAY_GROUP3_STEP	524288
+#define LM3533_LED_DELAY_GROUP1_MIN	16384
+#define LM3533_LED_DELAY_GROUP2_MIN	1130496
+#define LM3533_LED_DELAY_GROUP3_MIN	10305536
+#define LM3533_LED_DELAY_GROUP1_MAX	999424
+#define LM3533_LED_DELAY_GROUP2_MAX	9781248
+#define LM3533_LED_DELAY_GROUP3_MAX	76890112
+
+/* Delay limits in ms */
+#define LM3533_LED_DELAY_ON_MAX		9845
+#define LM3533_LED_DELAY_OFF_MAX	77140
+
+static int time_to_val(long *t, long t_min, long t_max, long t_step,
+							int v_min, int v_max)
+{
+	int val;
+
+	*t += t_step / 2;
+	val = (*t - t_min) / t_step + v_min;
+	val = clamp(val, v_min, v_max);
+	*t = t_step * (val - v_min) + t_min;
+
+	return val;
+}
+
+static int lm3533_led_get_delay(long *delay)
+{
+	int val;
+
+	*delay *= 1000;
+
+	if (*delay >= LM3533_LED_DELAY_GROUP3_MIN -
+					LM3533_LED_DELAY_GROUP3_STEP / 2) {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP3_MIN,
+					LM3533_LED_DELAY_GROUP3_MAX,
+					LM3533_LED_DELAY_GROUP3_STEP,
+					LM3533_LED_DELAY_GROUP3_BASE,
+					0xff);
+	} else if (*delay >= LM3533_LED_DELAY_GROUP2_MIN -
+					LM3533_LED_DELAY_GROUP2_STEP / 2) {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP2_MIN,
+					LM3533_LED_DELAY_GROUP2_MAX,
+					LM3533_LED_DELAY_GROUP2_STEP,
+					LM3533_LED_DELAY_GROUP2_BASE,
+					LM3533_LED_DELAY_GROUP3_BASE - 1);
+	} else {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP1_MIN,
+					LM3533_LED_DELAY_GROUP1_MAX,
+					LM3533_LED_DELAY_GROUP1_STEP,
+					LM3533_LED_DELAY_GROUP1_BASE,
+					LM3533_LED_DELAY_GROUP2_BASE - 1);
+	}
+
+	*delay /= 1000;
+
+	return val;
+}
+
+static int lm3533_led_delay_set(struct lm3533_led *led, u8 base,
+							unsigned long *delay)
+{
+	u8 val;
+	u8 reg;
+	long t;
+	int ret;
+
+	t = *delay;
+	val = lm3533_led_get_delay(&t);
+
+	dev_dbg(led->cdev.dev, "%s - %lu: %ld (0x%02x)\n", __func__,
+							*delay, t, val);
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
+
+	*delay = t;
+
+	return ret;
+}
+
+static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
+{
+	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP2_MAX / 1000);
+
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
+}
+
+static int lm3533_led_delay_off_set(struct lm3533_led *led, unsigned long *t)
+{
+	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP3_MAX / 1000);
+
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_LOW_TIME_BASE, t);
+}
+
+static int lm3533_led_blink_set(struct led_classdev *cdev,
+				unsigned long *delay_on,
+				unsigned long *delay_off)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	int ret;
+
+	dev_dbg(led->cdev.dev, "%s - on = %lu, off = %lu\n", __func__,
+							*delay_on, *delay_off);
+
+	if (*delay_on > LM3533_LED_DELAY_ON_MAX ||
+					*delay_off > LM3533_LED_DELAY_OFF_MAX)
+		return -EINVAL;
+
+	if (*delay_on == 0 && *delay_off == 0) {
+		*delay_on = 500;
+		*delay_off = 500;
+	}
+
+	ret = lm3533_led_delay_on_set(led, delay_on);
+	if (ret)
+		return ret;
+
+	ret = lm3533_led_delay_off_set(led, delay_off);
+	if (ret)
+		return ret;
+
+	return lm3533_led_pattern_enable(led, 1);
+}
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", led->id);
+}
+
+/*
+ * Pattern generator rise/fall times:
+ *
+ *   0 - 2048 us (default)
+ *   1 - 262 ms
+ *   2 - 524 ms
+ *   3 - 1.049 s
+ *   4 - 2.097 s
+ *   5 - 4.194 s
+ *   6 - 8.389 s
+ *   7 - 16.78 s
+ */
+static ssize_t show_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					char *buf, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	ssize_t ret;
+	u8 reg;
+	u8 val;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", val);
+}
+
+static ssize_t show_risetime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t show_falltime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+static ssize_t store_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	u8 reg;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > LM3533_RISEFALLTIME_MAX)
+		return -EINVAL;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t store_risetime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t store_falltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+/*
+ * ALS settings:
+ *
+ *   0 - ALS disabled
+ *   2 - ALS mapper 2
+ *   3 - ALS mapper 3
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int als;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	als = val & LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 als;
+	u8 reg;
+	u8 mask;
+	int ret;
+
+	if (kstrtou8(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	ret = lm3533_update(led->lm3533, reg, als, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int linear;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	if (val & LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	unsigned long linear;
+	u8 reg;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK;
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, reg, val, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define show_ctrlbank_attr(_name)					\
+static ssize_t show_##_name(struct device *dev,				\
+				struct device_attribute *attr,		\
+				char *buf)				\
+{									\
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);		\
+	struct lm3533_led *led = to_lm3533_led(led_cdev);		\
+	u8 val;								\
+	int ret;							\
+									\
+	ret = lm3533_ctrlbank_get_##_name(&led->cb, &val);		\
+	if (ret)							\
+		return ret;						\
+									\
+	return scnprintf(buf, PAGE_SIZE, "%d\n", val);			\
+}
+
+#define store_ctrlbank_attr(_name)					\
+static ssize_t store_##_name(struct device *dev,			\
+				struct device_attribute *attr,		\
+				const char *buf, size_t len)		\
+{									\
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);		\
+	struct lm3533_led *led = to_lm3533_led(led_cdev);		\
+	u8 val;								\
+	int ret;							\
+									\
+	if (kstrtou8(buf, 0, &val))					\
+		return -EINVAL;						\
+									\
+	ret = lm3533_ctrlbank_set_##_name(&led->cb, val);		\
+	if (ret)							\
+		return ret;						\
+									\
+	return len;							\
+}
+
+show_ctrlbank_attr(max_current);
+store_ctrlbank_attr(max_current);
+show_ctrlbank_attr(pwm);
+store_ctrlbank_attr(pwm);
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RW(falltime);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(max_current);
+static LM3533_ATTR_RW(pwm);
+static LM3533_ATTR_RW(risetime);
+
+static struct attribute *lm3533_led_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_falltime.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_max_current.attr,
+	&dev_attr_pwm.attr,
+	&dev_attr_risetime.attr,
+	NULL,
+};
+
+static mode_t lm3533_led_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	mode_t mode = attr->mode;
+
+	if (attr == &dev_attr_als.attr) {
+		if (!led->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_led_attribute_group = {
+	.is_visible	= lm3533_led_attr_is_visible,
+	.attrs		= lm3533_led_attributes
+};
+
+static int __devinit lm3533_led_setup(struct lm3533_led *led,
+					struct lm3533_led_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&led->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&led->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_led_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_led_platform_data *pdata;
+	struct lm3533_led *led;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_LVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal LED id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	led = kzalloc(sizeof(*led), GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	led->lm3533 = lm3533;
+	led->cdev.name = pdata->name;
+	led->cdev.default_trigger = pdata->default_trigger;
+	led->cdev.brightness_set = lm3533_led_set;
+	led->cdev.brightness_get = lm3533_led_get;
+	led->cdev.blink_set = lm3533_led_blink_set;
+	led->cdev.brightness = LED_OFF;
+	led->id = pdev->id;
+
+	mutex_init(&led->mutex);
+	INIT_WORK(&led->work, lm3533_led_work);
+
+	/* The class framework makes a callback to get brightness during
+	 * registration so use parent device (for error reporting) until
+	 * registered.
+	 */
+	led->cb.lm3533 = lm3533;
+	led->cb.id = lm3533_led_get_ctrlbank_id(led);
+	led->cb.dev = lm3533->dev;
+
+	platform_set_drvdata(pdev, led);
+
+	ret = led_classdev_register(pdev->dev.parent, &led->cdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id);
+		goto err_free;
+	}
+
+	led->cb.dev = led->cdev.dev;
+
+	ret = sysfs_create_group(&led->cdev.dev->kobj,
+						&lm3533_led_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	ret = lm3533_led_setup(led, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&led->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+err_unregister:
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+err_free:
+	kfree(led);
+
+	return ret;
+}
+
+static int __devexit lm3533_led_remove(struct platform_device *pdev)
+{
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+	kfree(led);
+
+	return 0;
+}
+
+static void lm3533_led_shutdown(struct platform_device *pdev)
+{
+
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	lm3533_led_set(&led->cdev, LED_OFF);		/* disable blink */
+	flush_work_sync(&led->work);
+}
+
+static struct platform_driver lm3533_led_driver = {
+	.driver = {
+		.name = "lm3533-leds",
+		.owner = THIS_MODULE,
+	},
+	.probe		= lm3533_led_probe,
+	.remove		= __devexit_p(lm3533_led_remove),
+	.shutdown	= lm3533_led_shutdown,
+};
+module_platform_driver(lm3533_led_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-leds");
-- 
1.7.8.5


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

* [PATCH 3/4] leds: add LM3533 LED driver
@ 2012-04-20 15:30   ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold

Add sub-driver for the LEDs in National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels, hardware accelerated blinking
as well as ambient-light-sensor and pwm input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/leds/Kconfig       |   13 +
 drivers/leds/Makefile      |    1 +
 drivers/leds/leds-lm3533.c |  713 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 727 insertions(+), 0 deletions(-)
 create mode 100644 drivers/leds/leds-lm3533.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index ff4b8cf..22a4e62 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -50,6 +50,19 @@ config LEDS_LM3530
 	  controlled manually or using PWM input or using ambient
 	  light automatically.
 
+config LEDS_LM3533
+	tristate "LED support for LM3533"
+	depends on LEDS_CLASS
+	depends on MFD_LM3533
+	help
+	  This option enables support for the LEDs on National Semiconductor /
+	  TI LM3533 Lighting Power chips.
+
+	  The LEDs can be controlled directly, through PWM input, or by the
+	  on-chip ambient light sensor. The chip supports hardware-accelerated
+	  blinking with maximum on and off periods of 9.8 and 77 seconds
+	  respectively.
+
 config LEDS_LOCOMO
 	tristate "LED Support for Locomo device"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 890481c..f39a526 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_ATMEL_PWM)		+= leds-atmel-pwm.o
 obj-$(CONFIG_LEDS_BD2802)		+= leds-bd2802.o
 obj-$(CONFIG_LEDS_LOCOMO)		+= leds-locomo.o
 obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
+obj-$(CONFIG_LEDS_LM3533)		+= leds-lm3533.o
 obj-$(CONFIG_LEDS_MIKROTIK_RB532)	+= leds-rb532.o
 obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
 obj-$(CONFIG_LEDS_NET48XX)		+= leds-net48xx.o
diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c
new file mode 100644
index 0000000..8e34b7a
--- /dev/null
+++ b/drivers/leds/leds-lm3533.c
@@ -0,0 +1,713 @@
+/*
+ * leds-lm3533.c -- LM3533 LED driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/mfd/core.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_LVCTRLBANK_MIN		2
+#define LM3533_LVCTRLBANK_MAX		5
+#define LM3533_LVCTRLBANK_COUNT		4
+#define LM3533_RISEFALLTIME_MAX		7
+#define LM3533_ALS_LV_MIN		2
+#define LM3533_ALS_LV_MAX		3
+
+#define LM3533_REG_CTRLBANK_BCONF_BASE		0x1b
+#define LM3533_REG_PATTERN_ENABLE		0x28
+#define LM3533_REG_PATTERN_LOW_TIME_BASE	0x71
+#define LM3533_REG_PATTERN_HIGH_TIME_BASE	0x72
+#define LM3533_REG_PATTERN_RISETIME_BASE	0x74
+#define LM3533_REG_PATTERN_FALLTIME_BASE	0x75
+
+#define LM3533_REG_PATTERN_STEP			0x10
+
+#define LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK	0x04
+#define LM3533_REG_CTRLBANK_BCONF_ALS_MASK	0x03
+
+#define LM3533_LED_FLAG_PATTERN_ENABLE		1
+
+
+struct lm3533_led {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct led_classdev cdev;
+	int id;
+
+	struct mutex mutex;
+	unsigned long flags;
+
+	struct work_struct work;
+	u8 new_brightness;
+};
+
+#define to_lm3533_led(_cdev) \
+	container_of(_cdev, struct lm3533_led, cdev)
+
+
+static inline int lm3533_led_get_ctrlbank_id(struct lm3533_led *led)
+{
+	return led->id + 2;
+}
+
+static inline u8 lm3533_led_get_lv_reg(struct lm3533_led *led, u8 base)
+{
+	return base + led->id;
+}
+
+static inline u8 lm3533_led_get_pattern(struct lm3533_led *led)
+{
+	return led->id;
+}
+
+static inline u8 lm3533_led_get_pattern_reg(struct lm3533_led *led,
+								u8 base)
+{
+	return base + lm3533_led_get_pattern(led) * LM3533_REG_PATTERN_STEP;
+}
+
+static int lm3533_led_pattern_enable(struct lm3533_led *led, int enable)
+{
+	u8 mask;
+	u8 val;
+	int pattern;
+	int state;
+	int ret = 0;
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, enable);
+
+	mutex_lock(&led->mutex);
+
+	state = test_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+	if ((enable && state) || (!enable && !state))
+		goto out;
+
+	pattern = lm3533_led_get_pattern(led);
+	mask = 1 << (2 * pattern);
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, LM3533_REG_PATTERN_ENABLE, val, mask);
+	if (ret) {
+		dev_err(led->cdev.dev, "failed to enable pattern %d (%d)\n",
+							pattern, enable);
+		goto out;
+	}
+
+	__change_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+out:
+	mutex_unlock(&led->mutex);
+
+	return ret;
+}
+
+static void lm3533_led_work(struct work_struct *work)
+{
+	struct lm3533_led *led = container_of(work, struct lm3533_led, work);
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, led->new_brightness);
+
+	if (led->new_brightness = 0)
+		lm3533_led_pattern_enable(led, 0);	/* disable blink */
+
+	lm3533_ctrlbank_set_brightness(&led->cb, led->new_brightness);
+}
+
+static void lm3533_led_set(struct led_classdev *cdev,
+						enum led_brightness value)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, value);
+
+	led->new_brightness = value;
+	schedule_work(&led->work);
+}
+
+static enum led_brightness lm3533_led_get(struct led_classdev *cdev)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&led->cb, &val);
+	if (ret)
+		return ret;
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, val);
+
+	return val;
+}
+
+/* Pattern generator defines -- delays in us */
+#define LM3533_LED_DELAY_GROUP1_BASE	0x00
+#define LM3533_LED_DELAY_GROUP2_BASE	0x3d
+#define LM3533_LED_DELAY_GROUP3_BASE	0x80
+#define LM3533_LED_DELAY_MAX		0xff
+
+#define LM3533_LED_DELAY_GROUP1_STEP	16384
+#define LM3533_LED_DELAY_GROUP2_STEP	131072
+#define LM3533_LED_DELAY_GROUP3_STEP	524288
+#define LM3533_LED_DELAY_GROUP1_MIN	16384
+#define LM3533_LED_DELAY_GROUP2_MIN	1130496
+#define LM3533_LED_DELAY_GROUP3_MIN	10305536
+#define LM3533_LED_DELAY_GROUP1_MAX	999424
+#define LM3533_LED_DELAY_GROUP2_MAX	9781248
+#define LM3533_LED_DELAY_GROUP3_MAX	76890112
+
+/* Delay limits in ms */
+#define LM3533_LED_DELAY_ON_MAX		9845
+#define LM3533_LED_DELAY_OFF_MAX	77140
+
+static int time_to_val(long *t, long t_min, long t_max, long t_step,
+							int v_min, int v_max)
+{
+	int val;
+
+	*t += t_step / 2;
+	val = (*t - t_min) / t_step + v_min;
+	val = clamp(val, v_min, v_max);
+	*t = t_step * (val - v_min) + t_min;
+
+	return val;
+}
+
+static int lm3533_led_get_delay(long *delay)
+{
+	int val;
+
+	*delay *= 1000;
+
+	if (*delay >= LM3533_LED_DELAY_GROUP3_MIN -
+					LM3533_LED_DELAY_GROUP3_STEP / 2) {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP3_MIN,
+					LM3533_LED_DELAY_GROUP3_MAX,
+					LM3533_LED_DELAY_GROUP3_STEP,
+					LM3533_LED_DELAY_GROUP3_BASE,
+					0xff);
+	} else if (*delay >= LM3533_LED_DELAY_GROUP2_MIN -
+					LM3533_LED_DELAY_GROUP2_STEP / 2) {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP2_MIN,
+					LM3533_LED_DELAY_GROUP2_MAX,
+					LM3533_LED_DELAY_GROUP2_STEP,
+					LM3533_LED_DELAY_GROUP2_BASE,
+					LM3533_LED_DELAY_GROUP3_BASE - 1);
+	} else {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP1_MIN,
+					LM3533_LED_DELAY_GROUP1_MAX,
+					LM3533_LED_DELAY_GROUP1_STEP,
+					LM3533_LED_DELAY_GROUP1_BASE,
+					LM3533_LED_DELAY_GROUP2_BASE - 1);
+	}
+
+	*delay /= 1000;
+
+	return val;
+}
+
+static int lm3533_led_delay_set(struct lm3533_led *led, u8 base,
+							unsigned long *delay)
+{
+	u8 val;
+	u8 reg;
+	long t;
+	int ret;
+
+	t = *delay;
+	val = lm3533_led_get_delay(&t);
+
+	dev_dbg(led->cdev.dev, "%s - %lu: %ld (0x%02x)\n", __func__,
+							*delay, t, val);
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
+
+	*delay = t;
+
+	return ret;
+}
+
+static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
+{
+	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP2_MAX / 1000);
+
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
+}
+
+static int lm3533_led_delay_off_set(struct lm3533_led *led, unsigned long *t)
+{
+	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP3_MAX / 1000);
+
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_LOW_TIME_BASE, t);
+}
+
+static int lm3533_led_blink_set(struct led_classdev *cdev,
+				unsigned long *delay_on,
+				unsigned long *delay_off)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	int ret;
+
+	dev_dbg(led->cdev.dev, "%s - on = %lu, off = %lu\n", __func__,
+							*delay_on, *delay_off);
+
+	if (*delay_on > LM3533_LED_DELAY_ON_MAX ||
+					*delay_off > LM3533_LED_DELAY_OFF_MAX)
+		return -EINVAL;
+
+	if (*delay_on = 0 && *delay_off = 0) {
+		*delay_on = 500;
+		*delay_off = 500;
+	}
+
+	ret = lm3533_led_delay_on_set(led, delay_on);
+	if (ret)
+		return ret;
+
+	ret = lm3533_led_delay_off_set(led, delay_off);
+	if (ret)
+		return ret;
+
+	return lm3533_led_pattern_enable(led, 1);
+}
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", led->id);
+}
+
+/*
+ * Pattern generator rise/fall times:
+ *
+ *   0 - 2048 us (default)
+ *   1 - 262 ms
+ *   2 - 524 ms
+ *   3 - 1.049 s
+ *   4 - 2.097 s
+ *   5 - 4.194 s
+ *   6 - 8.389 s
+ *   7 - 16.78 s
+ */
+static ssize_t show_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					char *buf, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	ssize_t ret;
+	u8 reg;
+	u8 val;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", val);
+}
+
+static ssize_t show_risetime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t show_falltime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+static ssize_t store_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	u8 reg;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > LM3533_RISEFALLTIME_MAX)
+		return -EINVAL;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t store_risetime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t store_falltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+/*
+ * ALS settings:
+ *
+ *   0 - ALS disabled
+ *   2 - ALS mapper 2
+ *   3 - ALS mapper 3
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int als;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	als = val & LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 als;
+	u8 reg;
+	u8 mask;
+	int ret;
+
+	if (kstrtou8(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	ret = lm3533_update(led->lm3533, reg, als, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int linear;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	if (val & LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	unsigned long linear;
+	u8 reg;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK;
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, reg, val, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define show_ctrlbank_attr(_name)					\
+static ssize_t show_##_name(struct device *dev,				\
+				struct device_attribute *attr,		\
+				char *buf)				\
+{									\
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);		\
+	struct lm3533_led *led = to_lm3533_led(led_cdev);		\
+	u8 val;								\
+	int ret;							\
+									\
+	ret = lm3533_ctrlbank_get_##_name(&led->cb, &val);		\
+	if (ret)							\
+		return ret;						\
+									\
+	return scnprintf(buf, PAGE_SIZE, "%d\n", val);			\
+}
+
+#define store_ctrlbank_attr(_name)					\
+static ssize_t store_##_name(struct device *dev,			\
+				struct device_attribute *attr,		\
+				const char *buf, size_t len)		\
+{									\
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);		\
+	struct lm3533_led *led = to_lm3533_led(led_cdev);		\
+	u8 val;								\
+	int ret;							\
+									\
+	if (kstrtou8(buf, 0, &val))					\
+		return -EINVAL;						\
+									\
+	ret = lm3533_ctrlbank_set_##_name(&led->cb, val);		\
+	if (ret)							\
+		return ret;						\
+									\
+	return len;							\
+}
+
+show_ctrlbank_attr(max_current);
+store_ctrlbank_attr(max_current);
+show_ctrlbank_attr(pwm);
+store_ctrlbank_attr(pwm);
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RW(falltime);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(max_current);
+static LM3533_ATTR_RW(pwm);
+static LM3533_ATTR_RW(risetime);
+
+static struct attribute *lm3533_led_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_falltime.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_max_current.attr,
+	&dev_attr_pwm.attr,
+	&dev_attr_risetime.attr,
+	NULL,
+};
+
+static mode_t lm3533_led_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	mode_t mode = attr->mode;
+
+	if (attr = &dev_attr_als.attr) {
+		if (!led->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_led_attribute_group = {
+	.is_visible	= lm3533_led_attr_is_visible,
+	.attrs		= lm3533_led_attributes
+};
+
+static int __devinit lm3533_led_setup(struct lm3533_led *led,
+					struct lm3533_led_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&led->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&led->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_led_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_led_platform_data *pdata;
+	struct lm3533_led *led;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_LVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal LED id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	led = kzalloc(sizeof(*led), GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	led->lm3533 = lm3533;
+	led->cdev.name = pdata->name;
+	led->cdev.default_trigger = pdata->default_trigger;
+	led->cdev.brightness_set = lm3533_led_set;
+	led->cdev.brightness_get = lm3533_led_get;
+	led->cdev.blink_set = lm3533_led_blink_set;
+	led->cdev.brightness = LED_OFF;
+	led->id = pdev->id;
+
+	mutex_init(&led->mutex);
+	INIT_WORK(&led->work, lm3533_led_work);
+
+	/* The class framework makes a callback to get brightness during
+	 * registration so use parent device (for error reporting) until
+	 * registered.
+	 */
+	led->cb.lm3533 = lm3533;
+	led->cb.id = lm3533_led_get_ctrlbank_id(led);
+	led->cb.dev = lm3533->dev;
+
+	platform_set_drvdata(pdev, led);
+
+	ret = led_classdev_register(pdev->dev.parent, &led->cdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id);
+		goto err_free;
+	}
+
+	led->cb.dev = led->cdev.dev;
+
+	ret = sysfs_create_group(&led->cdev.dev->kobj,
+						&lm3533_led_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	ret = lm3533_led_setup(led, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&led->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+err_unregister:
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+err_free:
+	kfree(led);
+
+	return ret;
+}
+
+static int __devexit lm3533_led_remove(struct platform_device *pdev)
+{
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+	kfree(led);
+
+	return 0;
+}
+
+static void lm3533_led_shutdown(struct platform_device *pdev)
+{
+
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	lm3533_led_set(&led->cdev, LED_OFF);		/* disable blink */
+	flush_work_sync(&led->work);
+}
+
+static struct platform_driver lm3533_led_driver = {
+	.driver = {
+		.name = "lm3533-leds",
+		.owner = THIS_MODULE,
+	},
+	.probe		= lm3533_led_probe,
+	.remove		= __devexit_p(lm3533_led_remove),
+	.shutdown	= lm3533_led_shutdown,
+};
+module_platform_driver(lm3533_led_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-leds");
-- 
1.7.8.5


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

* [PATCH 4/4] backlight: add LM3533 backlight driver
  2012-04-20 15:30 ` Johan Hovold
@ 2012-04-20 15:30   ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold

Add sub-driver for the backlights in National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels and ambient-light-sensor and pwm
input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/video/backlight/Kconfig     |   12 +
 drivers/video/backlight/Makefile    |    1 +
 drivers/video/backlight/lm3533_bl.c |  432 +++++++++++++++++++++++++++++++++++
 3 files changed, 445 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/backlight/lm3533_bl.c

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index af16884..45455e7 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -184,6 +184,18 @@ config BACKLIGHT_GENERIC
 	  known as the Corgi backlight driver. If you have a Sharp Zaurus
 	  SL-C7xx, SL-Cxx00 or SL-6000x say y.
 
+config BACKLIGHT_LM3533
+	tristate "Backlight Driver for LM3533"
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on MFD_LM3533
+	help
+	  Say Y to enable the backlight driver for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  The backlights can be controlled directly, through PWM input, or by
+	  the on-chip ambient light sensor. The chip supports 256 brightness
+	  levels.
+
 config BACKLIGHT_LOCOMO
 	tristate "Sharp LOCOMO LCD/Backlight Driver"
 	depends on SHARP_LOCOMO
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 36855ae..a2ac9cf 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_BACKLIGHT_EP93XX)	+= ep93xx_bl.o
 obj-$(CONFIG_BACKLIGHT_GENERIC)	+= generic_bl.o
 obj-$(CONFIG_BACKLIGHT_HP700)	+= jornada720_bl.o
 obj-$(CONFIG_BACKLIGHT_HP680)	+= hp680_bl.o
+obj-$(CONFIG_BACKLIGHT_LM3533)	+= lm3533_bl.o
 obj-$(CONFIG_BACKLIGHT_LOCOMO)	+= locomolcd.o
 obj-$(CONFIG_BACKLIGHT_LP855X)	+= lp855x_bl.o
 obj-$(CONFIG_BACKLIGHT_OMAP1)	+= omap1_bl.o
diff --git a/drivers/video/backlight/lm3533_bl.c b/drivers/video/backlight/lm3533_bl.c
new file mode 100644
index 0000000..4217e1f
--- /dev/null
+++ b/drivers/video/backlight/lm3533_bl.c
@@ -0,0 +1,432 @@
+/*
+ * lm3533-bl.c -- LM3533 Backlight driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_HVCTRLBANK_COUNT		2
+#define LM3533_BL_MAX_BRIGHTNESS	255
+
+#define LM3533_REG_CTRLBANK_AB_BCONF	0x1a
+
+
+struct lm3533_bl {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct backlight_device *bd;
+	int id;
+};
+
+
+static inline int lm3533_bl_get_ctrlbank_id(struct lm3533_bl *bl)
+{
+	return bl->id;
+}
+
+static int lm3533_bl_update_status(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	int brightness = bd->props.brightness;
+
+	if (bd->props.power != FB_BLANK_UNBLANK)
+		brightness = 0;
+	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	return lm3533_ctrlbank_set_brightness(&bl->cb, (u8)brightness);
+}
+
+static int lm3533_bl_get_brightness(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return val;
+}
+
+static const struct backlight_ops lm3533_bl_ops = {
+	.get_brightness	= lm3533_bl_get_brightness,
+	.update_status	= lm3533_bl_update_status,
+};
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", bl->id);
+}
+
+/*
+ * ALS settings:
+ *
+ *   0 - ALS disabled
+ *   1 - ALS mapper 1 (backlight 0)
+ *   2 - ALS mapper 2 (backlight 1)
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	u8 val;
+	u8 mask;
+	int als;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 2 * ctrlbank;
+	als = val & mask;
+	if (als)
+		als = ctrlbank + 1;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	int als;
+	u8 val;
+	u8 mask;
+	int ret;
+
+	if (kstrtoint(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als != ctrlbank + 1))
+		return -EINVAL;
+
+	mask = 1 << (2 * ctrlbank);
+
+	if (als)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	u8 mask;
+	int linear;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (val & mask)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	unsigned long linear;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define show_ctrlbank_attr(_name)					\
+static ssize_t show_##_name(struct device *dev,				\
+				struct device_attribute *attr,		\
+				char *buf)				\
+{									\
+	struct lm3533_bl *bl = dev_get_drvdata(dev);			\
+	u8 val;								\
+	int ret;							\
+									\
+	ret = lm3533_ctrlbank_get_##_name(&bl->cb, &val);		\
+	if (ret)							\
+		return ret;						\
+									\
+	return scnprintf(buf, PAGE_SIZE, "%d\n", val);			\
+}
+
+#define store_ctrlbank_attr(_name)					\
+static ssize_t store_##_name(struct device *dev,			\
+				struct device_attribute *attr,		\
+				const char *buf, size_t len)		\
+{									\
+	struct lm3533_bl *bl = dev_get_drvdata(dev);			\
+	u8 val;								\
+	int ret;							\
+									\
+	if (kstrtou8(buf, 0, &val))					\
+		return -EINVAL;						\
+									\
+	ret = lm3533_ctrlbank_set_##_name(&bl->cb, val);		\
+	if (ret)							\
+		return ret;						\
+									\
+	return len;							\
+}
+
+show_ctrlbank_attr(max_current);
+store_ctrlbank_attr(max_current);
+show_ctrlbank_attr(pwm);
+store_ctrlbank_attr(pwm);
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(max_current);
+static LM3533_ATTR_RW(pwm);
+
+static struct attribute *lm3533_bl_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_max_current.attr,
+	&dev_attr_pwm.attr,
+	NULL,
+};
+
+static mode_t lm3533_bl_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	mode_t mode = attr->mode;
+
+	if (attr == &dev_attr_als.attr) {
+		if (!bl->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_bl_attribute_group = {
+	.is_visible	= lm3533_bl_attr_is_visible,
+	.attrs		= lm3533_bl_attributes
+};
+
+static int __devinit lm3533_bl_setup(struct lm3533_bl *bl,
+					struct lm3533_bl_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&bl->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&bl->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_bl_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_bl_platform_data *pdata;
+	struct lm3533_bl *bl;
+	struct backlight_device *bd;
+	struct backlight_properties props;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_HVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal backlight id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	bl = kzalloc(sizeof(*bl), GFP_KERNEL);
+	if (!bl) {
+		dev_err(&pdev->dev,
+				"failed to allocate memory for backlight\n");
+		return -ENOMEM;
+	}
+
+	bl->lm3533 = lm3533;
+	bl->id = pdev->id;
+
+	bl->cb.lm3533 = lm3533;
+	bl->cb.id = lm3533_bl_get_ctrlbank_id(bl);
+	bl->cb.dev = NULL;			/* until registered */
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = LM3533_BL_MAX_BRIGHTNESS;
+	props.brightness = pdata->default_brightness;
+	bd = backlight_device_register(pdata->name, pdev->dev.parent, bl,
+						&lm3533_bl_ops, &props);
+	if (IS_ERR(bd)) {
+		dev_err(&pdev->dev, "failed to register backlight device\n");
+		ret = PTR_ERR(bd);
+		goto err_free;
+	}
+
+	bl->bd = bd;
+	bl->cb.dev = &bl->bd->dev;
+
+	platform_set_drvdata(pdev, bl);
+
+	ret = sysfs_create_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	backlight_update_status(bd);
+
+	ret = lm3533_bl_setup(bl, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&bl->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+err_unregister:
+	backlight_device_unregister(bd);
+err_free:
+	kfree(bl);
+
+	return ret;
+}
+
+static int __devexit lm3533_bl_remove(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+	struct backlight_device *bd = bl->bd;
+
+	dev_dbg(&bd->dev, "%s\n", __func__);
+
+	bd->props.power = FB_BLANK_POWERDOWN;
+	bd->props.brightness = 0;
+
+	lm3533_ctrlbank_disable(&bl->cb);
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	backlight_device_unregister(bd);
+	kfree(bl);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int lm3533_bl_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static int lm3533_bl_resume(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_enable(&bl->cb);
+}
+#else
+#define lm3533_bl_suspend	NULL
+#define lm3533_bl_resume	NULL
+#endif
+
+static void lm3533_bl_shutdown(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static struct platform_driver lm3533_bl_driver = {
+	.driver = {
+		.name	= "lm3533-backlight",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= lm3533_bl_probe,
+	.remove		= __devexit_p(lm3533_bl_remove),
+	.shutdown	= lm3533_bl_shutdown,
+	.suspend	= lm3533_bl_suspend,
+	.resume		= lm3533_bl_resume,
+};
+module_platform_driver(lm3533_bl_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Backlight driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-backlight");
-- 
1.7.8.5


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

* [PATCH 4/4] backlight: add LM3533 backlight driver
@ 2012-04-20 15:30   ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 15:30 UTC (permalink / raw)
  To: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat
  Cc: Andrew Morton, linux-kernel, linux-fbdev, Johan Hovold

Add sub-driver for the backlights in National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels and ambient-light-sensor and pwm
input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/video/backlight/Kconfig     |   12 +
 drivers/video/backlight/Makefile    |    1 +
 drivers/video/backlight/lm3533_bl.c |  432 +++++++++++++++++++++++++++++++++++
 3 files changed, 445 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/backlight/lm3533_bl.c

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index af16884..45455e7 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -184,6 +184,18 @@ config BACKLIGHT_GENERIC
 	  known as the Corgi backlight driver. If you have a Sharp Zaurus
 	  SL-C7xx, SL-Cxx00 or SL-6000x say y.
 
+config BACKLIGHT_LM3533
+	tristate "Backlight Driver for LM3533"
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on MFD_LM3533
+	help
+	  Say Y to enable the backlight driver for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  The backlights can be controlled directly, through PWM input, or by
+	  the on-chip ambient light sensor. The chip supports 256 brightness
+	  levels.
+
 config BACKLIGHT_LOCOMO
 	tristate "Sharp LOCOMO LCD/Backlight Driver"
 	depends on SHARP_LOCOMO
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 36855ae..a2ac9cf 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_BACKLIGHT_EP93XX)	+= ep93xx_bl.o
 obj-$(CONFIG_BACKLIGHT_GENERIC)	+= generic_bl.o
 obj-$(CONFIG_BACKLIGHT_HP700)	+= jornada720_bl.o
 obj-$(CONFIG_BACKLIGHT_HP680)	+= hp680_bl.o
+obj-$(CONFIG_BACKLIGHT_LM3533)	+= lm3533_bl.o
 obj-$(CONFIG_BACKLIGHT_LOCOMO)	+= locomolcd.o
 obj-$(CONFIG_BACKLIGHT_LP855X)	+= lp855x_bl.o
 obj-$(CONFIG_BACKLIGHT_OMAP1)	+= omap1_bl.o
diff --git a/drivers/video/backlight/lm3533_bl.c b/drivers/video/backlight/lm3533_bl.c
new file mode 100644
index 0000000..4217e1f
--- /dev/null
+++ b/drivers/video/backlight/lm3533_bl.c
@@ -0,0 +1,432 @@
+/*
+ * lm3533-bl.c -- LM3533 Backlight driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_HVCTRLBANK_COUNT		2
+#define LM3533_BL_MAX_BRIGHTNESS	255
+
+#define LM3533_REG_CTRLBANK_AB_BCONF	0x1a
+
+
+struct lm3533_bl {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct backlight_device *bd;
+	int id;
+};
+
+
+static inline int lm3533_bl_get_ctrlbank_id(struct lm3533_bl *bl)
+{
+	return bl->id;
+}
+
+static int lm3533_bl_update_status(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	int brightness = bd->props.brightness;
+
+	if (bd->props.power != FB_BLANK_UNBLANK)
+		brightness = 0;
+	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	return lm3533_ctrlbank_set_brightness(&bl->cb, (u8)brightness);
+}
+
+static int lm3533_bl_get_brightness(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return val;
+}
+
+static const struct backlight_ops lm3533_bl_ops = {
+	.get_brightness	= lm3533_bl_get_brightness,
+	.update_status	= lm3533_bl_update_status,
+};
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", bl->id);
+}
+
+/*
+ * ALS settings:
+ *
+ *   0 - ALS disabled
+ *   1 - ALS mapper 1 (backlight 0)
+ *   2 - ALS mapper 2 (backlight 1)
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	u8 val;
+	u8 mask;
+	int als;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 2 * ctrlbank;
+	als = val & mask;
+	if (als)
+		als = ctrlbank + 1;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	int als;
+	u8 val;
+	u8 mask;
+	int ret;
+
+	if (kstrtoint(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als != ctrlbank + 1))
+		return -EINVAL;
+
+	mask = 1 << (2 * ctrlbank);
+
+	if (als)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	u8 mask;
+	int linear;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (val & mask)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	unsigned long linear;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define show_ctrlbank_attr(_name)					\
+static ssize_t show_##_name(struct device *dev,				\
+				struct device_attribute *attr,		\
+				char *buf)				\
+{									\
+	struct lm3533_bl *bl = dev_get_drvdata(dev);			\
+	u8 val;								\
+	int ret;							\
+									\
+	ret = lm3533_ctrlbank_get_##_name(&bl->cb, &val);		\
+	if (ret)							\
+		return ret;						\
+									\
+	return scnprintf(buf, PAGE_SIZE, "%d\n", val);			\
+}
+
+#define store_ctrlbank_attr(_name)					\
+static ssize_t store_##_name(struct device *dev,			\
+				struct device_attribute *attr,		\
+				const char *buf, size_t len)		\
+{									\
+	struct lm3533_bl *bl = dev_get_drvdata(dev);			\
+	u8 val;								\
+	int ret;							\
+									\
+	if (kstrtou8(buf, 0, &val))					\
+		return -EINVAL;						\
+									\
+	ret = lm3533_ctrlbank_set_##_name(&bl->cb, val);		\
+	if (ret)							\
+		return ret;						\
+									\
+	return len;							\
+}
+
+show_ctrlbank_attr(max_current);
+store_ctrlbank_attr(max_current);
+show_ctrlbank_attr(pwm);
+store_ctrlbank_attr(pwm);
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(max_current);
+static LM3533_ATTR_RW(pwm);
+
+static struct attribute *lm3533_bl_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_max_current.attr,
+	&dev_attr_pwm.attr,
+	NULL,
+};
+
+static mode_t lm3533_bl_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	mode_t mode = attr->mode;
+
+	if (attr = &dev_attr_als.attr) {
+		if (!bl->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_bl_attribute_group = {
+	.is_visible	= lm3533_bl_attr_is_visible,
+	.attrs		= lm3533_bl_attributes
+};
+
+static int __devinit lm3533_bl_setup(struct lm3533_bl *bl,
+					struct lm3533_bl_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&bl->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&bl->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_bl_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_bl_platform_data *pdata;
+	struct lm3533_bl *bl;
+	struct backlight_device *bd;
+	struct backlight_properties props;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_HVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal backlight id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	bl = kzalloc(sizeof(*bl), GFP_KERNEL);
+	if (!bl) {
+		dev_err(&pdev->dev,
+				"failed to allocate memory for backlight\n");
+		return -ENOMEM;
+	}
+
+	bl->lm3533 = lm3533;
+	bl->id = pdev->id;
+
+	bl->cb.lm3533 = lm3533;
+	bl->cb.id = lm3533_bl_get_ctrlbank_id(bl);
+	bl->cb.dev = NULL;			/* until registered */
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = LM3533_BL_MAX_BRIGHTNESS;
+	props.brightness = pdata->default_brightness;
+	bd = backlight_device_register(pdata->name, pdev->dev.parent, bl,
+						&lm3533_bl_ops, &props);
+	if (IS_ERR(bd)) {
+		dev_err(&pdev->dev, "failed to register backlight device\n");
+		ret = PTR_ERR(bd);
+		goto err_free;
+	}
+
+	bl->bd = bd;
+	bl->cb.dev = &bl->bd->dev;
+
+	platform_set_drvdata(pdev, bl);
+
+	ret = sysfs_create_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	backlight_update_status(bd);
+
+	ret = lm3533_bl_setup(bl, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&bl->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+err_unregister:
+	backlight_device_unregister(bd);
+err_free:
+	kfree(bl);
+
+	return ret;
+}
+
+static int __devexit lm3533_bl_remove(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+	struct backlight_device *bd = bl->bd;
+
+	dev_dbg(&bd->dev, "%s\n", __func__);
+
+	bd->props.power = FB_BLANK_POWERDOWN;
+	bd->props.brightness = 0;
+
+	lm3533_ctrlbank_disable(&bl->cb);
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	backlight_device_unregister(bd);
+	kfree(bl);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int lm3533_bl_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static int lm3533_bl_resume(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_enable(&bl->cb);
+}
+#else
+#define lm3533_bl_suspend	NULL
+#define lm3533_bl_resume	NULL
+#endif
+
+static void lm3533_bl_shutdown(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static struct platform_driver lm3533_bl_driver = {
+	.driver = {
+		.name	= "lm3533-backlight",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= lm3533_bl_probe,
+	.remove		= __devexit_p(lm3533_bl_remove),
+	.shutdown	= lm3533_bl_shutdown,
+	.suspend	= lm3533_bl_suspend,
+	.resume		= lm3533_bl_resume,
+};
+module_platform_driver(lm3533_bl_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Backlight driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-backlight");
-- 
1.7.8.5


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

* Re: [PATCH 2/4] misc: add LM3533 ambient light sensor driver
  2012-04-20 15:30   ` Johan Hovold
@ 2012-04-20 15:57     ` Greg Kroah-Hartman
  -1 siblings, 0 replies; 131+ messages in thread
From: Greg Kroah-Hartman @ 2012-04-20 15:57 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Richard Purdie, Samuel Ortiz, Arnd Bergmann,
	Florian Tobias Schandinat, Andrew Morton, linux-kernel,
	linux-fbdev

On Fri, Apr 20, 2012 at 05:30:24PM +0200, Johan Hovold wrote:
> Add sub-driver for the ambient light sensor in National Semiconductor /
> TI LM3533 lighting power chips.
> 
> Raw ADC values as well as current ALS zone can be retrieved through
> sysfs. The ALS zone can also be read using a character device
> (/dev/lm3533-als) which is updated on zone changes (interrupt driven or
> polled).
> 
> The driver provides a configuration interface through sysfs.

Which seems to not be documented at all :(

What about using the iio interface for this instead?  Doesn't that
already provide this standard interface you are looking for?

thanks,

greg k-h

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

* Re: [PATCH 2/4] misc: add LM3533 ambient light sensor driver
@ 2012-04-20 15:57     ` Greg Kroah-Hartman
  0 siblings, 0 replies; 131+ messages in thread
From: Greg Kroah-Hartman @ 2012-04-20 15:57 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Richard Purdie, Samuel Ortiz, Arnd Bergmann,
	Florian Tobias Schandinat, Andrew Morton, linux-kernel,
	linux-fbdev

On Fri, Apr 20, 2012 at 05:30:24PM +0200, Johan Hovold wrote:
> Add sub-driver for the ambient light sensor in National Semiconductor /
> TI LM3533 lighting power chips.
> 
> Raw ADC values as well as current ALS zone can be retrieved through
> sysfs. The ALS zone can also be read using a character device
> (/dev/lm3533-als) which is updated on zone changes (interrupt driven or
> polled).
> 
> The driver provides a configuration interface through sysfs.

Which seems to not be documented at all :(

What about using the iio interface for this instead?  Doesn't that
already provide this standard interface you are looking for?

thanks,

greg k-h

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

* Re: [PATCH 3/4] leds: add LM3533 LED driver
  2012-04-20 15:30   ` Johan Hovold
  (?)
@ 2012-04-20 16:10   ` Arnd Bergmann
  2012-04-20 16:45       ` Johan Hovold
  -1 siblings, 1 reply; 131+ messages in thread
From: Arnd Bergmann @ 2012-04-20 16:10 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Richard Purdie, Samuel Ortiz, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Andrew Morton, linux-kernel,
	linux-fbdev

On Friday 20 April 2012, Johan Hovold wrote:
> Add sub-driver for the LEDs in National Semiconductor / TI LM3533
> lighting power chips.
> 
> The chip provides 256 brightness levels, hardware accelerated blinking
> as well as ambient-light-sensor and pwm input control.
> 
> Signed-off-by: Johan Hovold <jhovold@gmail.com>

I notice that there is already driver for lm3530, which sounds related.
Is there an opportunity to share code between these, or are they completely
different devices?

> +
> +#define show_ctrlbank_attr(_name)					\
> +static ssize_t show_##_name(struct device *dev,				\
> +				struct device_attribute *attr,		\
> +				char *buf)				\
> +{									\
> +	struct led_classdev *led_cdev = dev_get_drvdata(dev);		\
> +	struct lm3533_led *led = to_lm3533_led(led_cdev);		\
> +	u8 val;								\
> +	int ret;							\
> +									\
> +	ret = lm3533_ctrlbank_get_##_name(&led->cb, &val);		\
> +	if (ret)							\
> +		return ret;						\
> +									\
> +	return scnprintf(buf, PAGE_SIZE, "%d\n", val);			\
> +}

IMHO this macro adds more in terms of complexity than it saves in terms
of lines of code, and it would be better to open-code the two instances.
If you need more than two or three instances, I would recommend creating
keying the number off of the attribute pointer, either by comparing the
pointer or by adding a data structure derived from device_attribute and
using container_of to get at the other data.

	Arnd

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

* Re: [PATCH 3/4] leds: add LM3533 LED driver
  2012-04-20 16:10   ` Arnd Bergmann
@ 2012-04-20 16:45       ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 16:45 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Richard Purdie, Samuel Ortiz, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Andrew Morton, linux-kernel,
	linux-fbdev

On Fri, Apr 20, 2012 at 04:10:15PM +0000, Arnd Bergmann wrote:
> On Friday 20 April 2012, Johan Hovold wrote:
> > Add sub-driver for the LEDs in National Semiconductor / TI LM3533
> > lighting power chips.
> > 
> > The chip provides 256 brightness levels, hardware accelerated blinking
> > as well as ambient-light-sensor and pwm input control.
> > 
> > Signed-off-by: Johan Hovold <jhovold@gmail.com>
> 
> I notice that there is already driver for lm3530, which sounds related.
> Is there an opportunity to share code between these, or are they completely
> different devices?

Unfortunately not. They are really very different devices despite
similar naming and terminology.

> > +
> > +#define show_ctrlbank_attr(_name)					\
> > +static ssize_t show_##_name(struct device *dev,				\
> > +				struct device_attribute *attr,		\
> > +				char *buf)				\
> > +{									\
> > +	struct led_classdev *led_cdev = dev_get_drvdata(dev);		\
> > +	struct lm3533_led *led = to_lm3533_led(led_cdev);		\
> > +	u8 val;								\
> > +	int ret;							\
> > +									\
> > +	ret = lm3533_ctrlbank_get_##_name(&led->cb, &val);		\
> > +	if (ret)							\
> > +		return ret;						\
> > +									\
> > +	return scnprintf(buf, PAGE_SIZE, "%d\n", val);			\
> > +}
> 
> IMHO this macro adds more in terms of complexity than it saves in terms
> of lines of code, and it would be better to open-code the two instances.
> If you need more than two or three instances, I would recommend creating
> keying the number off of the attribute pointer, either by comparing the
> pointer or by adding a data structure derived from device_attribute and
> using container_of to get at the other data.

Agreed. I'll simply open-code them for now, and do the same with the
equivalent instances in the backlight driver.

Thanks,
Johan

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

* Re: [PATCH 3/4] leds: add LM3533 LED driver
@ 2012-04-20 16:45       ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 16:45 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Richard Purdie, Samuel Ortiz, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Andrew Morton, linux-kernel,
	linux-fbdev

On Fri, Apr 20, 2012 at 04:10:15PM +0000, Arnd Bergmann wrote:
> On Friday 20 April 2012, Johan Hovold wrote:
> > Add sub-driver for the LEDs in National Semiconductor / TI LM3533
> > lighting power chips.
> > 
> > The chip provides 256 brightness levels, hardware accelerated blinking
> > as well as ambient-light-sensor and pwm input control.
> > 
> > Signed-off-by: Johan Hovold <jhovold@gmail.com>
> 
> I notice that there is already driver for lm3530, which sounds related.
> Is there an opportunity to share code between these, or are they completely
> different devices?

Unfortunately not. They are really very different devices despite
similar naming and terminology.

> > +
> > +#define show_ctrlbank_attr(_name)					\
> > +static ssize_t show_##_name(struct device *dev,				\
> > +				struct device_attribute *attr,		\
> > +				char *buf)				\
> > +{									\
> > +	struct led_classdev *led_cdev = dev_get_drvdata(dev);		\
> > +	struct lm3533_led *led = to_lm3533_led(led_cdev);		\
> > +	u8 val;								\
> > +	int ret;							\
> > +									\
> > +	ret = lm3533_ctrlbank_get_##_name(&led->cb, &val);		\
> > +	if (ret)							\
> > +		return ret;						\
> > +									\
> > +	return scnprintf(buf, PAGE_SIZE, "%d\n", val);			\
> > +}
> 
> IMHO this macro adds more in terms of complexity than it saves in terms
> of lines of code, and it would be better to open-code the two instances.
> If you need more than two or three instances, I would recommend creating
> keying the number off of the attribute pointer, either by comparing the
> pointer or by adding a data structure derived from device_attribute and
> using container_of to get at the other data.

Agreed. I'll simply open-code them for now, and do the same with the
equivalent instances in the backlight driver.

Thanks,
Johan

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

* Re: [PATCH 2/4] misc: add LM3533 ambient light sensor driver
  2012-04-20 15:57     ` Greg Kroah-Hartman
@ 2012-04-20 17:28       ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 17:28 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Richard Purdie, Samuel Ortiz, Arnd Bergmann,
	Florian Tobias Schandinat, Andrew Morton, linux-kernel,
	linux-fbdev

On Fri, Apr 20, 2012 at 08:57:34AM -0700, Greg Kroah-Hartman wrote:
> On Fri, Apr 20, 2012 at 05:30:24PM +0200, Johan Hovold wrote:
> > Add sub-driver for the ambient light sensor in National Semiconductor /
> > TI LM3533 lighting power chips.
> > 
> > Raw ADC values as well as current ALS zone can be retrieved through
> > sysfs. The ALS zone can also be read using a character device
> > (/dev/lm3533-als) which is updated on zone changes (interrupt driven or
> > polled).
> > 
> > The driver provides a configuration interface through sysfs.
> 
> Which seems to not be documented at all :(

There are the following sysfs entries for configuring ALS control:

boundary0_high
boundary0_low
boundary1_high
boundary1_low
boundary2_high
boundary2_low
boundary3_high
boundary3_low

gain

target1_0
target1_1
target1_2
target1_3
target1_4

target2_0
target2_1
target2_2
target2_3
target2_4

target3_0
target3_1
target3_2
target3_3
target3_4

These define the "five light zones and three sets of corresponding
brightness target levels" mentioned in the Kconfig entry and provides a
gain setting.

Each entry also corresponds to an 8-bit register, which is documented
along with the overall ALS functionality in the datasheets (which will
be published on the TI web page soon). So I think anyone integrating
this IC (or anyone who has access to the datasheets) will have no
problem with this interface, but I'd be happy to write something to put
under Documentation as well.

The end-customer insisted on sysfs configurability, but I'll probably
add these settings to the platform data later as well. 

> What about using the iio interface for this instead?  Doesn't that
> already provide this standard interface you are looking for?

I had a look at iio last fall and decided not to use it at the time. I
can't remember exactly what the reasons were right now, so I'll have
to get back to you on this.

Thanks,
Johan

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

* Re: [PATCH 2/4] misc: add LM3533 ambient light sensor driver
@ 2012-04-20 17:28       ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-20 17:28 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Richard Purdie, Samuel Ortiz, Arnd Bergmann,
	Florian Tobias Schandinat, Andrew Morton, linux-kernel,
	linux-fbdev

On Fri, Apr 20, 2012 at 08:57:34AM -0700, Greg Kroah-Hartman wrote:
> On Fri, Apr 20, 2012 at 05:30:24PM +0200, Johan Hovold wrote:
> > Add sub-driver for the ambient light sensor in National Semiconductor /
> > TI LM3533 lighting power chips.
> > 
> > Raw ADC values as well as current ALS zone can be retrieved through
> > sysfs. The ALS zone can also be read using a character device
> > (/dev/lm3533-als) which is updated on zone changes (interrupt driven or
> > polled).
> > 
> > The driver provides a configuration interface through sysfs.
> 
> Which seems to not be documented at all :(

There are the following sysfs entries for configuring ALS control:

boundary0_high
boundary0_low
boundary1_high
boundary1_low
boundary2_high
boundary2_low
boundary3_high
boundary3_low

gain

target1_0
target1_1
target1_2
target1_3
target1_4

target2_0
target2_1
target2_2
target2_3
target2_4

target3_0
target3_1
target3_2
target3_3
target3_4

These define the "five light zones and three sets of corresponding
brightness target levels" mentioned in the Kconfig entry and provides a
gain setting.

Each entry also corresponds to an 8-bit register, which is documented
along with the overall ALS functionality in the datasheets (which will
be published on the TI web page soon). So I think anyone integrating
this IC (or anyone who has access to the datasheets) will have no
problem with this interface, but I'd be happy to write something to put
under Documentation as well.

The end-customer insisted on sysfs configurability, but I'll probably
add these settings to the platform data later as well. 

> What about using the iio interface for this instead?  Doesn't that
> already provide this standard interface you are looking for?

I had a look at iio last fall and decided not to use it at the time. I
can't remember exactly what the reasons were right now, so I'll have
to get back to you on this.

Thanks,
Johan

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

* Re: [PATCH 2/4] misc: add LM3533 ambient light sensor driver
  2012-04-20 17:28       ` Johan Hovold
@ 2012-04-20 17:37         ` Greg Kroah-Hartman
  -1 siblings, 0 replies; 131+ messages in thread
From: Greg Kroah-Hartman @ 2012-04-20 17:37 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Richard Purdie, Samuel Ortiz, Arnd Bergmann,
	Florian Tobias Schandinat, Andrew Morton, linux-kernel,
	linux-fbdev

On Fri, Apr 20, 2012 at 07:28:49PM +0200, Johan Hovold wrote:
> On Fri, Apr 20, 2012 at 08:57:34AM -0700, Greg Kroah-Hartman wrote:
> > On Fri, Apr 20, 2012 at 05:30:24PM +0200, Johan Hovold wrote:
> > > Add sub-driver for the ambient light sensor in National Semiconductor /
> > > TI LM3533 lighting power chips.
> > > 
> > > Raw ADC values as well as current ALS zone can be retrieved through
> > > sysfs. The ALS zone can also be read using a character device
> > > (/dev/lm3533-als) which is updated on zone changes (interrupt driven or
> > > polled).
> > > 
> > > The driver provides a configuration interface through sysfs.
> > 
> > Which seems to not be documented at all :(
> 
> There are the following sysfs entries for configuring ALS control:

<snip>

That's fine, but you need a Documentation/ABI entry for any new sysfs
file you create.
> > What about using the iio interface for this instead?  Doesn't that
> > already provide this standard interface you are looking for?
> 
> I had a look at iio last fall and decided not to use it at the time. I
> can't remember exactly what the reasons were right now, so I'll have
> to get back to you on this.

Please look into this, the iio framework is about to move out of the
staging tree for 3.5, so there should not be any reason for you to
create yet-another user api for this type of thing.

thanks,

greg k-h

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

* Re: [PATCH 2/4] misc: add LM3533 ambient light sensor driver
@ 2012-04-20 17:37         ` Greg Kroah-Hartman
  0 siblings, 0 replies; 131+ messages in thread
From: Greg Kroah-Hartman @ 2012-04-20 17:37 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Richard Purdie, Samuel Ortiz, Arnd Bergmann,
	Florian Tobias Schandinat, Andrew Morton, linux-kernel,
	linux-fbdev

On Fri, Apr 20, 2012 at 07:28:49PM +0200, Johan Hovold wrote:
> On Fri, Apr 20, 2012 at 08:57:34AM -0700, Greg Kroah-Hartman wrote:
> > On Fri, Apr 20, 2012 at 05:30:24PM +0200, Johan Hovold wrote:
> > > Add sub-driver for the ambient light sensor in National Semiconductor /
> > > TI LM3533 lighting power chips.
> > > 
> > > Raw ADC values as well as current ALS zone can be retrieved through
> > > sysfs. The ALS zone can also be read using a character device
> > > (/dev/lm3533-als) which is updated on zone changes (interrupt driven or
> > > polled).
> > > 
> > > The driver provides a configuration interface through sysfs.
> > 
> > Which seems to not be documented at all :(
> 
> There are the following sysfs entries for configuring ALS control:

<snip>

That's fine, but you need a Documentation/ABI entry for any new sysfs
file you create.
> > What about using the iio interface for this instead?  Doesn't that
> > already provide this standard interface you are looking for?
> 
> I had a look at iio last fall and decided not to use it at the time. I
> can't remember exactly what the reasons were right now, so I'll have
> to get back to you on this.

Please look into this, the iio framework is about to move out of the
staging tree for 3.5, so there should not be any reason for you to
create yet-another user api for this type of thing.

thanks,

greg k-h

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

* Re: [PATCH 2/4] misc: add LM3533 ambient light sensor driver
  2012-04-20 17:37         ` Greg Kroah-Hartman
@ 2012-04-26 11:52           ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-26 11:52 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Richard Purdie, Samuel Ortiz, Arnd Bergmann,
	Florian Tobias Schandinat, Andrew Morton, linux-kernel,
	linux-fbdev

On Fri, Apr 20, 2012 at 10:37:54AM -0700, Greg Kroah-Hartman wrote:
> On Fri, Apr 20, 2012 at 07:28:49PM +0200, Johan Hovold wrote:
> > On Fri, Apr 20, 2012 at 08:57:34AM -0700, Greg Kroah-Hartman wrote:
> > > On Fri, Apr 20, 2012 at 05:30:24PM +0200, Johan Hovold wrote:
> > > > Add sub-driver for the ambient light sensor in National Semiconductor /
> > > > TI LM3533 lighting power chips.
> > > > 
> > > > Raw ADC values as well as current ALS zone can be retrieved through
> > > > sysfs. The ALS zone can also be read using a character device
> > > > (/dev/lm3533-als) which is updated on zone changes (interrupt driven or
> > > > polled).
> > > > 
> > > > The driver provides a configuration interface through sysfs.
> > > 
> > > Which seems to not be documented at all :(
> > 
> > There are the following sysfs entries for configuring ALS control:
> 
> <snip>
> 
> That's fine, but you need a Documentation/ABI entry for any new sysfs
> file you create.
> > > What about using the iio interface for this instead?  Doesn't that
> > > already provide this standard interface you are looking for?
> > 
> > I had a look at iio last fall and decided not to use it at the time. I
> > can't remember exactly what the reasons were right now, so I'll have
> > to get back to you on this.
> 
> Please look into this, the iio framework is about to move out of the
> staging tree for 3.5, so there should not be any reason for you to
> create yet-another user api for this type of thing.

We had an initial requirement to support fairly old kernels, but this
have since been relaxed (and has of course never in itself been a valid
reason to not use iio for upstream). As iio is moving out of staging, I
see no problems using it, and the required changes appear quite small.

I'll submit a v2 against iio and make sure to document the sysfs-entries.

Thanks,
Johan

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

* Re: [PATCH 2/4] misc: add LM3533 ambient light sensor driver
@ 2012-04-26 11:52           ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-04-26 11:52 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Richard Purdie, Samuel Ortiz, Arnd Bergmann,
	Florian Tobias Schandinat, Andrew Morton, linux-kernel,
	linux-fbdev

On Fri, Apr 20, 2012 at 10:37:54AM -0700, Greg Kroah-Hartman wrote:
> On Fri, Apr 20, 2012 at 07:28:49PM +0200, Johan Hovold wrote:
> > On Fri, Apr 20, 2012 at 08:57:34AM -0700, Greg Kroah-Hartman wrote:
> > > On Fri, Apr 20, 2012 at 05:30:24PM +0200, Johan Hovold wrote:
> > > > Add sub-driver for the ambient light sensor in National Semiconductor /
> > > > TI LM3533 lighting power chips.
> > > > 
> > > > Raw ADC values as well as current ALS zone can be retrieved through
> > > > sysfs. The ALS zone can also be read using a character device
> > > > (/dev/lm3533-als) which is updated on zone changes (interrupt driven or
> > > > polled).
> > > > 
> > > > The driver provides a configuration interface through sysfs.
> > > 
> > > Which seems to not be documented at all :(
> > 
> > There are the following sysfs entries for configuring ALS control:
> 
> <snip>
> 
> That's fine, but you need a Documentation/ABI entry for any new sysfs
> file you create.
> > > What about using the iio interface for this instead?  Doesn't that
> > > already provide this standard interface you are looking for?
> > 
> > I had a look at iio last fall and decided not to use it at the time. I
> > can't remember exactly what the reasons were right now, so I'll have
> > to get back to you on this.
> 
> Please look into this, the iio framework is about to move out of the
> staging tree for 3.5, so there should not be any reason for you to
> create yet-another user api for this type of thing.

We had an initial requirement to support fairly old kernels, but this
have since been relaxed (and has of course never in itself been a valid
reason to not use iio for upstream). As iio is moving out of staging, I
see no problems using it, and the required changes appear quite small.

I'll submit a v2 against iio and make sure to document the sysfs-entries.

Thanks,
Johan

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

* Re: [PATCH 1/4] mfd: add LM3533 lighting-power core driver
  2012-04-20 15:30   ` Johan Hovold
@ 2012-04-26 12:41     ` Mark Brown
  -1 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-04-26 12:41 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Andrew Morton, linux-kernel,
	linux-fbdev

On Fri, Apr 20, 2012 at 05:30:23PM +0200, Johan Hovold wrote:

> +static int __lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
> +{
> +	int ret;
> +
> +	ret = lm3533->read(lm3533, reg, val);
> +	if (ret < 0) {

Looks like you could save a bunch of code by using regmap for the
register I/O.  This would also give you access to the cache and
diagnostic infrastructure it has.

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

* Re: [PATCH 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-04-26 12:41     ` Mark Brown
  0 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-04-26 12:41 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Richard Purdie, Samuel Ortiz, Arnd Bergmann, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Andrew Morton, linux-kernel,
	linux-fbdev

On Fri, Apr 20, 2012 at 05:30:23PM +0200, Johan Hovold wrote:

> +static int __lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
> +{
> +	int ret;
> +
> +	ret = lm3533->read(lm3533, reg, val);
> +	if (ret < 0) {

Looks like you could save a bunch of code by using regmap for the
register I/O.  This would also give you access to the cache and
diagnostic infrastructure it has.

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

* Re: [PATCH 1/4] mfd: add LM3533 lighting-power core driver
  2012-04-26 12:41     ` Mark Brown
@ 2012-05-03 10:15       ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:15 UTC (permalink / raw)
  To: Mark Brown
  Cc: Samuel Ortiz, Florian Tobias Schandinat, Arnd Bergmann,
	Greg Kroah-Hartman, linux-fbdev, linux-kernel, Andrew Morton,
	Richard Purdie, Johan Hovold

[ Sorry for the resend -- Android/gmail apparently added HTML to my previous
  attempt. ] 

On Apr 26, 2012 2:41 PM, "Mark Brown" <broonie@opensource.wolfsonmicro.com>
wrote:
> On Fri, Apr 20, 2012 at 05:30:23PM +0200, Johan Hovold wrote:
> > +static int __lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
> > +{
> > +     int ret;
> > +
> > +     ret = lm3533->read(lm3533, reg, val);
> > +     if (ret < 0) {
>
> Looks like you could save a bunch of code by using regmap for the
> register I/O.  This would also give you access to the cache and
> diagnostic infrastructure it has.

Using regmap only saves about ten lines for the actual io implementation,
but it does allow me to get rid of the custom debugfs interface. I'm not
enabling caching at this point but it'll probably come in handy later.

Thanks,
Johan

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

* Re: [PATCH 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-03 10:15       ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:15 UTC (permalink / raw)
  To: Mark Brown
  Cc: Samuel Ortiz, Florian Tobias Schandinat, Arnd Bergmann,
	Greg Kroah-Hartman, linux-fbdev, linux-kernel, Andrew Morton,
	Richard Purdie, Johan Hovold

[ Sorry for the resend -- Android/gmail apparently added HTML to my previous
  attempt. ] 

On Apr 26, 2012 2:41 PM, "Mark Brown" <broonie@opensource.wolfsonmicro.com>
wrote:
> On Fri, Apr 20, 2012 at 05:30:23PM +0200, Johan Hovold wrote:
> > +static int __lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
> > +{
> > +     int ret;
> > +
> > +     ret = lm3533->read(lm3533, reg, val);
> > +     if (ret < 0) {
>
> Looks like you could save a bunch of code by using regmap for the
> register I/O.  This would also give you access to the cache and
> diagnostic infrastructure it has.

Using regmap only saves about ten lines for the actual io implementation,
but it does allow me to get rid of the custom debugfs interface. I'm not
enabling caching at this point but it'll probably come in handy later.

Thanks,
Johan

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

* Re: [PATCH 1/4] mfd: add LM3533 lighting-power core driver
  2012-04-26 12:41     ` Mark Brown
@ 2012-05-03 10:22       ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:22 UTC (permalink / raw)
  To: Mark Brown
  Cc: Samuel Ortiz, Florian Tobias Schandinat, Arnd Bergmann,
	Greg Kroah-Hartman, linux-fbdev, linux-kernel, Andrew Morton,
	Richard Purdie, Johan Hovold

[ Sorry for the resend -- Android/gmail apparently added HTML to my previous
  attempt. ] 

On Apr 26, 2012 2:41 PM, "Mark Brown" <broonie@opensource.wolfsonmicro.com>
wrote:
> On Fri, Apr 20, 2012 at 05:30:23PM +0200, Johan Hovold wrote:
> > +static int __lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
> > +{
> > +     int ret;
> > +
> > +     ret = lm3533->read(lm3533, reg, val);
> > +     if (ret < 0) {
>
> Looks like you could save a bunch of code by using regmap for the
> register I/O.  This would also give you access to the cache and
> diagnostic infrastructure it has.

Using regmap only saves about ten lines for the actual io implementation,
but it does allow me to get rid of the custom debugfs interface. I'm not
enabling caching at this point but it'll probably come in handy later.

Thanks,
Johan

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

* Re: [PATCH 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-03 10:22       ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:22 UTC (permalink / raw)
  To: Mark Brown
  Cc: Samuel Ortiz, Florian Tobias Schandinat, Arnd Bergmann,
	Greg Kroah-Hartman, linux-fbdev, linux-kernel, Andrew Morton,
	Richard Purdie, Johan Hovold

[ Sorry for the resend -- Android/gmail apparently added HTML to my previous
  attempt. ] 

On Apr 26, 2012 2:41 PM, "Mark Brown" <broonie@opensource.wolfsonmicro.com>
wrote:
> On Fri, Apr 20, 2012 at 05:30:23PM +0200, Johan Hovold wrote:
> > +static int __lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
> > +{
> > +     int ret;
> > +
> > +     ret = lm3533->read(lm3533, reg, val);
> > +     if (ret < 0) {
>
> Looks like you could save a bunch of code by using regmap for the
> register I/O.  This would also give you access to the cache and
> diagnostic infrastructure it has.

Using regmap only saves about ten lines for the actual io implementation,
but it does allow me to get rid of the custom debugfs interface. I'm not
enabling caching at this point but it'll probably come in handy later.

Thanks,
Johan

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

* [PATCH v2 0/4] mfd: add LM3533 lighting-power chip driver
  2012-04-20 15:30 ` Johan Hovold
@ 2012-05-03 10:26   ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:26 UTC (permalink / raw)
  To: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat
  Cc: Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev, Johan Hovold

These patches (against v3.4-rc5) add support for the National Semiconductor /
Texas Instruments LM3533 lighting-power chip.

This multi-function device has four LEDs, two backlights and an
ambient-light-sensor interface.

The LEDs and backlights can be controlled directly, through PWM input,
or by the ambient-light-sensor interface. Hardware-accelerated blinking is
provided for the LEDs.

ALS control is done through defining five light zones and three sets of
corresponding brightness target levels. The ALS iio-driver provides raw and
mean adc readings along with the current light zone through sysfs. A threshold
event can be generated on zone changes.

Further details and specifications are now available from:

        http://www.ti.com/product/lm3533

Changes since v1 includes a rewrite of the ambient-light-sensor driver against
iio, the addition of sysfs-ABI documentation, and a switch to regmap for
register io.

This work has been done on behalf of National Semiconductor / Texas
Instruments.

Thanks,
Johan


Johan Hovold (4):
  mfd: add LM3533 lighting-power core driver
  iio: add LM3533 ambient light sensor driver
  leds: add LM3533 LED driver
  backlight: add LM3533 backlight driver

 .../ABI/testing/sysfs-bus-i2c-devices-lm3533       |   38 +
 .../testing/sysfs-class-backlight-driver-lm3533    |   50 ++
 .../ABI/testing/sysfs-class-led-driver-lm3533      |   67 ++
 drivers/leds/Kconfig                               |   13 +
 drivers/leds/Makefile                              |    1 +
 drivers/leds/leds-lm3533.c                         |  741 ++++++++++++++++++++
 drivers/mfd/Kconfig                                |   13 +
 drivers/mfd/Makefile                               |    1 +
 drivers/mfd/lm3533-core.c                          |  717 +++++++++++++++++++
 drivers/mfd/lm3533-ctrlbank.c                      |  134 ++++
 .../Documentation/sysfs-bus-iio-light-lm3533-als   |   62 ++
 drivers/staging/iio/light/Kconfig                  |   16 +
 drivers/staging/iio/light/Makefile                 |    1 +
 drivers/staging/iio/light/lm3533-als.c             |  617 ++++++++++++++++
 drivers/video/backlight/Kconfig                    |   12 +
 drivers/video/backlight/Makefile                   |    1 +
 drivers/video/backlight/lm3533_bl.c                |  458 ++++++++++++
 include/linux/mfd/lm3533.h                         |   89 +++
 18 files changed, 3031 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
 create mode 100644 Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
 create mode 100644 Documentation/ABI/testing/sysfs-class-led-driver-lm3533
 create mode 100644 drivers/leds/leds-lm3533.c
 create mode 100644 drivers/mfd/lm3533-core.c
 create mode 100644 drivers/mfd/lm3533-ctrlbank.c
 create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
 create mode 100644 drivers/staging/iio/light/lm3533-als.c
 create mode 100644 drivers/video/backlight/lm3533_bl.c
 create mode 100644 include/linux/mfd/lm3533.h

-- 
1.7.8.5


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

* [PATCH v2 0/4] mfd: add LM3533 lighting-power chip driver
@ 2012-05-03 10:26   ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:26 UTC (permalink / raw)
  To: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat
  Cc: Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev, Johan Hovold

These patches (against v3.4-rc5) add support for the National Semiconductor /
Texas Instruments LM3533 lighting-power chip.

This multi-function device has four LEDs, two backlights and an
ambient-light-sensor interface.

The LEDs and backlights can be controlled directly, through PWM input,
or by the ambient-light-sensor interface. Hardware-accelerated blinking is
provided for the LEDs.

ALS control is done through defining five light zones and three sets of
corresponding brightness target levels. The ALS iio-driver provides raw and
mean adc readings along with the current light zone through sysfs. A threshold
event can be generated on zone changes.

Further details and specifications are now available from:

        http://www.ti.com/product/lm3533

Changes since v1 includes a rewrite of the ambient-light-sensor driver against
iio, the addition of sysfs-ABI documentation, and a switch to regmap for
register io.

This work has been done on behalf of National Semiconductor / Texas
Instruments.

Thanks,
Johan


Johan Hovold (4):
  mfd: add LM3533 lighting-power core driver
  iio: add LM3533 ambient light sensor driver
  leds: add LM3533 LED driver
  backlight: add LM3533 backlight driver

 .../ABI/testing/sysfs-bus-i2c-devices-lm3533       |   38 +
 .../testing/sysfs-class-backlight-driver-lm3533    |   50 ++
 .../ABI/testing/sysfs-class-led-driver-lm3533      |   67 ++
 drivers/leds/Kconfig                               |   13 +
 drivers/leds/Makefile                              |    1 +
 drivers/leds/leds-lm3533.c                         |  741 ++++++++++++++++++++
 drivers/mfd/Kconfig                                |   13 +
 drivers/mfd/Makefile                               |    1 +
 drivers/mfd/lm3533-core.c                          |  717 +++++++++++++++++++
 drivers/mfd/lm3533-ctrlbank.c                      |  134 ++++
 .../Documentation/sysfs-bus-iio-light-lm3533-als   |   62 ++
 drivers/staging/iio/light/Kconfig                  |   16 +
 drivers/staging/iio/light/Makefile                 |    1 +
 drivers/staging/iio/light/lm3533-als.c             |  617 ++++++++++++++++
 drivers/video/backlight/Kconfig                    |   12 +
 drivers/video/backlight/Makefile                   |    1 +
 drivers/video/backlight/lm3533_bl.c                |  458 ++++++++++++
 include/linux/mfd/lm3533.h                         |   89 +++
 18 files changed, 3031 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
 create mode 100644 Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
 create mode 100644 Documentation/ABI/testing/sysfs-class-led-driver-lm3533
 create mode 100644 drivers/leds/leds-lm3533.c
 create mode 100644 drivers/mfd/lm3533-core.c
 create mode 100644 drivers/mfd/lm3533-ctrlbank.c
 create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
 create mode 100644 drivers/staging/iio/light/lm3533-als.c
 create mode 100644 drivers/video/backlight/lm3533_bl.c
 create mode 100644 include/linux/mfd/lm3533.h

-- 
1.7.8.5


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

* [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-03 10:26   ` Johan Hovold
@ 2012-05-03 10:26     ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:26 UTC (permalink / raw)
  To: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat
  Cc: Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev, Johan Hovold

Add support for National Semiconductor / TI LM3533 lighting power chips.

This is the core driver which provides register access over I2C and
registers the ambient-light-sensor, LED and backlight sub-drivers.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2:
 - add sysfs-ABI documentation
 - merge i2c implementation with core
 - use regmap and kill custom debugfs interface


 .../ABI/testing/sysfs-bus-i2c-devices-lm3533       |   38 +
 drivers/mfd/Kconfig                                |   13 +
 drivers/mfd/Makefile                               |    1 +
 drivers/mfd/lm3533-core.c                          |  717 ++++++++++++++++++++
 drivers/mfd/lm3533-ctrlbank.c                      |  134 ++++
 include/linux/mfd/lm3533.h                         |   89 +++
 6 files changed, 992 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
 create mode 100644 drivers/mfd/lm3533-core.c
 create mode 100644 drivers/mfd/lm3533-ctrlbank.c
 create mode 100644 include/linux/mfd/lm3533.h

diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533 b/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
new file mode 100644
index 0000000..5700721
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
@@ -0,0 +1,38 @@
+What:		/sys/bus/i2c/devices/.../boost_freq
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the boost converter switching frequency (0, 1), where
+
+		0 -  500Hz
+		1 - 1000Hz
+
+What:		/sys/bus/i2c/devices/.../boost_ovp
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the boost converter over-voltage protection threshold
+		(0..3), where
+
+		0 - 16V
+		1 - 24V
+		2 - 32V
+		3 - 40V
+
+What:		/sys/bus/i2c/devices/.../output_hvled[n]
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the controlling backlight device for high-voltage current
+		sink HVLED[n] (n = 1, 2) (0, 1).
+
+What:		/sys/bus/i2c/devices/.../output_lvled[n]
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the controlling led device for low-voltage current sink
+		LVLED[n] (n = 1..5) (0..3).
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 11e4438..8fe0771 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -106,6 +106,19 @@ config UCB1400_CORE
 	  To compile this driver as a module, choose M here: the
 	  module will be called ucb1400_core.
 
+config MFD_LM3533
+	tristate "LM3533 Lighting Power chip"
+	depends on I2C
+	select MFD_CORE
+	select REGMAP_I2C
+	help
+	  Say yes here to enable support for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  This driver provides common support for accessing the device;
+	  additional drivers must be enabled in order to use the LED,
+	  backlight or ambient-light-sensor functionality of the device.
+
 config TPS6105X
 	tristate "TPS61050/61052 Boost Converters"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 05fa538..b6fe0a5 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -116,3 +116,4 @@ obj-$(CONFIG_MFD_INTEL_MSIC)	+= intel_msic.o
 obj-$(CONFIG_MFD_RC5T583)	+= rc5t583.o rc5t583-irq.o
 obj-$(CONFIG_MFD_S5M_CORE)	+= s5m-core.o s5m-irq.o
 obj-$(CONFIG_MFD_ANATOP)	+= anatop-mfd.o
+obj-$(CONFIG_MFD_LM3533)	+= lm3533-core.o lm3533-ctrlbank.o
diff --git a/drivers/mfd/lm3533-core.c b/drivers/mfd/lm3533-core.c
new file mode 100644
index 0000000..75f4b7f
--- /dev/null
+++ b/drivers/mfd/lm3533-core.c
@@ -0,0 +1,717 @@
+/*
+ * lm3533-core.c -- LM3533 Core
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/regmap.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_BOOST_OVP_MAX		0x03
+#define LM3533_BOOST_OVP_MASK		0x06
+#define LM3533_BOOST_OVP_SHIFT		1
+
+#define LM3533_BOOST_FREQ_MAX		0x01
+#define LM3533_BOOST_FREQ_MASK		0x01
+#define LM3533_BOOST_FREQ_SHIFT		0
+
+#define LM3533_BL_ID_MASK		1
+#define LM3533_LED_ID_MASK		3
+#define LM3533_BL_ID_MAX		1
+#define LM3533_LED_ID_MAX		3
+
+#define LM3533_HVLED_ID_MAX		2
+#define LM3533_LVLED_ID_MAX		5
+
+#define LM3533_REG_OUTPUT_CONF1		0x10
+#define LM3533_REG_OUTPUT_CONF2		0x11
+#define LM3533_REG_BOOST_PWM		0x2c
+
+#define LM3533_REG_MAX			0xb2
+
+
+static struct mfd_cell lm3533_als_devs[] = {
+	{
+		.name	= "lm3533-als",
+		.id	= -1,
+	},
+};
+
+static struct mfd_cell lm3533_bl_devs[] = {
+	{
+		.name	= "lm3533-backlight",
+		.id	= 0,
+	},
+	{
+		.name	= "lm3533-backlight",
+		.id	= 1,
+	},
+};
+
+static struct mfd_cell lm3533_led_devs[] = {
+	{
+		.name	= "lm3533-leds",
+		.id	= 0,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 1,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 2,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 3,
+	},
+};
+
+int lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
+{
+	int tmp;
+	int ret;
+
+	ret = regmap_read(lm3533->regmap, reg, &tmp);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to read register %02x: %d\n",
+								reg, ret);
+		return ret;
+	}
+
+	*val = tmp;
+
+	dev_dbg(lm3533->dev, "read [%02x]: %02x\n", reg, *val);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_read);
+
+int lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val)
+{
+	int ret;
+
+	dev_dbg(lm3533->dev, "write [%02x]: %02x\n", reg, val);
+
+	ret = regmap_write(lm3533->regmap, reg, val);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to write register %02x: %d\n",
+								reg, ret);
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_write);
+
+int lm3533_update(struct lm3533 *lm3533, u8 reg, u8 val, u8 mask)
+{
+	int ret;
+
+	dev_dbg(lm3533->dev, "update [%02x]: %02x/%02x\n", reg, val, mask);
+
+	ret = regmap_update_bits(lm3533->regmap, reg, val, mask);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to update register %02x: %d\n",
+								reg, ret);
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_update);
+
+/*
+ * HVLED output config -- output hvled controlled by backlight bl
+ */
+static int lm3533_set_hvled_config(struct lm3533 *lm3533, u8 hvled, u8 bl)
+{
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (hvled == 0 || hvled > LM3533_HVLED_ID_MAX)
+		return -EINVAL;
+
+	if (bl > LM3533_BL_ID_MAX)
+		return -EINVAL;
+
+	shift = hvled - 1;
+	mask = LM3533_BL_ID_MASK << shift;
+	val = bl << shift;
+
+	ret = lm3533_update(lm3533, LM3533_REG_OUTPUT_CONF1, val, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to set hvled config\n");
+
+	return ret;
+}
+
+/*
+ * LVLED output config -- output lvled controlled by LED led
+ */
+static int lm3533_set_lvled_config(struct lm3533 *lm3533, u8 lvled, u8 led)
+{
+	u8 reg;
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (lvled == 0 || lvled > LM3533_LVLED_ID_MAX)
+		return -EINVAL;
+
+	if (led > LM3533_LED_ID_MAX)
+		return -EINVAL;
+
+	if (lvled < 4) {
+		reg = LM3533_REG_OUTPUT_CONF1;
+		shift = 2 * lvled;
+	} else {
+		reg = LM3533_REG_OUTPUT_CONF2;
+		shift = 2 * (lvled - 4);
+	}
+
+	mask = LM3533_LED_ID_MASK << shift;
+	val = led << shift;
+
+	ret = lm3533_update(lm3533, reg, val, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to set lvled config\n");
+
+	return ret;
+}
+
+static void lm3533_enable(struct lm3533 *lm3533)
+{
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_set_value(lm3533->gpio_hwen, 1);
+}
+
+static void lm3533_disable(struct lm3533 *lm3533)
+{
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_set_value(lm3533->gpio_hwen, 0);
+}
+
+enum lm3533_attribute_type {
+	LM3533_ATTR_TYPE_BACKLIGHT,
+	LM3533_ATTR_TYPE_LED,
+};
+
+struct lm3533_device_attribute {
+	struct device_attribute dev_attr;
+	enum lm3533_attribute_type type;
+	union {
+		struct {
+			u8 id;
+		} output;
+		struct {
+			u8 reg;
+			u8 shift;
+			u8 mask;
+			u8 max;
+		} generic;
+	} u;
+};
+
+#define to_lm3533_dev_attr(_attr) \
+	container_of(_attr, struct lm3533_device_attribute, dev_attr)
+
+static ssize_t show_lm3533_reg(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(lm3533, lattr->u.generic.reg, &val);
+	if (ret)
+		return ret;
+
+	val = (val & lattr->u.generic.mask) >> lattr->u.generic.shift;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_lm3533_reg(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t len)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > lattr->u.generic.max)
+		return -EINVAL;
+
+	val = val << lattr->u.generic.shift;
+	ret = lm3533_update(lm3533, lattr->u.generic.reg, val,
+							lattr->u.generic.mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define GENERIC_ATTR(_reg, _max, _mask, _shift) \
+	{ .reg		= _reg, \
+	  .max		= _max, \
+	  .mask		= _mask, \
+	  .shift	= _shift }
+
+#define LM3533_GENERIC_ATTR(_name, _mode, _show, _store, _type,	\
+						_reg, _max, _mask, _shift) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name = { \
+		.dev_attr	= __ATTR(_name, _mode, _show, _store), \
+		.type		= _type, \
+		.u.generic	= GENERIC_ATTR(_reg, _max, _mask, _shift) }
+
+#define LM3533_GENERIC_ATTR_RW(_name, _type, _reg, _max, _mask, _shift) \
+	LM3533_GENERIC_ATTR(_name, S_IRUGO | S_IWUSR, \
+					show_lm3533_reg, store_lm3533_reg, \
+					_type, _reg, _max, _mask, _shift)
+
+#define LM3533_BOOST_ATTR_RW(_name, _NAME) \
+	LM3533_GENERIC_ATTR_RW(_name, LM3533_ATTR_TYPE_BACKLIGHT, \
+				LM3533_REG_BOOST_PWM, LM3533_##_NAME##_MAX, \
+				LM3533_##_NAME##_MASK, LM3533_##_NAME##_SHIFT)
+/*
+ * Boost Over Voltage Protection Select
+ *
+ *   0 - 16 V (default)
+ *   1 - 24 V
+ *   2 - 32 V
+ *   3 - 40 V
+ */
+static LM3533_BOOST_ATTR_RW(boost_ovp, BOOST_OVP);
+
+/*
+ * Boost Frequency Select
+ *
+ *   0 - 500 kHz (default)
+ *   1 - 1 MHz
+ */
+static LM3533_BOOST_ATTR_RW(boost_freq, BOOST_FREQ);
+
+static ssize_t show_output(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	int id = lattr->u.output.id;
+	u8 reg;
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (lattr->type == LM3533_ATTR_TYPE_BACKLIGHT) {
+		reg = LM3533_REG_OUTPUT_CONF1;
+		shift = id - 1;
+		mask = LM3533_BL_ID_MASK << shift;
+	} else {
+		if (id < 4) {
+			reg = LM3533_REG_OUTPUT_CONF1;
+			shift = 2 * id;
+		} else {
+			reg = LM3533_REG_OUTPUT_CONF2;
+			shift = 2 * (id - 4);
+		}
+		mask = LM3533_LED_ID_MASK << shift;
+	}
+
+	ret = lm3533_read(lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	val = (val & mask) >> shift;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_output(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	int id = lattr->u.output.id;
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	if (lattr->type == LM3533_ATTR_TYPE_BACKLIGHT)
+		ret = lm3533_set_hvled_config(lm3533, id, val);
+	else
+		ret = lm3533_set_lvled_config(lm3533, id, val);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define LM3533_OUTPUT_ATTR(_name, _mode, _show, _store, _type, _id) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name = \
+		{ .dev_attr	= __ATTR(_name, _mode, _show, _store), \
+		  .type		= _type, \
+		  .u.output	= { .id = _id }, }
+
+#define LM3533_OUTPUT_ATTR_RW(_name, _type, _id) \
+	LM3533_OUTPUT_ATTR(output_##_name, S_IRUGO | S_IWUSR, \
+					show_output, store_output, _type, _id)
+
+#define LM3533_OUTPUT_HVLED_ATTR_RW(_nr) \
+	LM3533_OUTPUT_ATTR_RW(hvled##_nr, LM3533_ATTR_TYPE_BACKLIGHT, _nr)
+#define LM3533_OUTPUT_LVLED_ATTR_RW(_nr) \
+	LM3533_OUTPUT_ATTR_RW(lvled##_nr, LM3533_ATTR_TYPE_LED, _nr)
+/*
+ * Output config:
+ *
+ * output_hvled<nr>	0-1
+ * output_lvled<nr>	0-3
+ */
+static LM3533_OUTPUT_HVLED_ATTR_RW(1);
+static LM3533_OUTPUT_HVLED_ATTR_RW(2);
+static LM3533_OUTPUT_LVLED_ATTR_RW(1);
+static LM3533_OUTPUT_LVLED_ATTR_RW(2);
+static LM3533_OUTPUT_LVLED_ATTR_RW(3);
+static LM3533_OUTPUT_LVLED_ATTR_RW(4);
+static LM3533_OUTPUT_LVLED_ATTR_RW(5);
+
+static struct attribute *lm3533_attributes[] = {
+	&lm3533_dev_attr_boost_freq.dev_attr.attr,
+	&lm3533_dev_attr_boost_ovp.dev_attr.attr,
+	&lm3533_dev_attr_output_hvled1.dev_attr.attr,
+	&lm3533_dev_attr_output_hvled2.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled1.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled2.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled3.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled4.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled5.dev_attr.attr,
+	NULL,
+};
+
+#define to_dev_attr(_attr) \
+	container_of(_attr, struct device_attribute, attr)
+
+static mode_t lm3533_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct device_attribute *dattr = to_dev_attr(attr);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(dattr);
+	enum lm3533_attribute_type type = lattr->type;
+	mode_t mode = attr->mode;
+
+	if (!lm3533->have_backlights && type == LM3533_ATTR_TYPE_BACKLIGHT)
+		mode = 0;
+	else if (!lm3533->have_leds && type == LM3533_ATTR_TYPE_LED)
+		mode = 0;
+
+	return mode;
+};
+
+static struct attribute_group lm3533_attribute_group = {
+	.is_visible	= lm3533_attr_is_visible,
+	.attrs		= lm3533_attributes
+};
+
+static int __devinit lm3533_device_als_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int ret;
+
+	if (!pdata->als)
+		return 0;
+
+	lm3533_als_devs[0].platform_data = pdata->als;
+	lm3533_als_devs[0].pdata_size = sizeof(*pdata->als);
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_als_devs, 1, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add ALS device\n");
+		return ret;
+	}
+
+	lm3533->have_als = 1;
+
+	return 0;
+}
+
+static int __devinit lm3533_device_bl_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int i;
+	int ret;
+
+	if (!pdata->backlights || pdata->num_backlights == 0)
+		return 0;
+
+	if (pdata->num_backlights > ARRAY_SIZE(lm3533_bl_devs))
+		pdata->num_backlights = ARRAY_SIZE(lm3533_bl_devs);
+
+	for (i = 0; i < pdata->num_backlights; ++i) {
+		lm3533_bl_devs[i].platform_data = &pdata->backlights[i];
+		lm3533_bl_devs[i].pdata_size = sizeof(pdata->backlights[i]);
+	}
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_bl_devs,
+					pdata->num_backlights, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add backlight devices\n");
+		return ret;
+	}
+
+	lm3533->have_backlights = 1;
+
+	return 0;
+}
+
+static int __devinit lm3533_device_led_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int i;
+	int ret;
+
+	if (!pdata->leds || pdata->num_leds == 0)
+		return 0;
+
+	if (pdata->num_leds > ARRAY_SIZE(lm3533_led_devs))
+		pdata->num_leds = ARRAY_SIZE(lm3533_led_devs);
+
+	for (i = 0; i < pdata->num_leds; ++i) {
+		lm3533_led_devs[i].platform_data = &pdata->leds[i];
+		lm3533_led_devs[i].pdata_size = sizeof(pdata->leds[i]);
+	}
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_led_devs,
+						pdata->num_leds, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add LED devices\n");
+		return ret;
+	}
+
+	lm3533->have_leds = 1;
+
+	return 0;
+}
+
+static int __devinit lm3533_device_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int ret;
+
+	dev_dbg(lm3533->dev, "%s\n", __func__);
+
+	if (!pdata) {
+		dev_err(lm3533->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	lm3533->gpio_hwen = pdata->gpio_hwen;
+
+	dev_set_drvdata(lm3533->dev, lm3533);
+
+	if (gpio_is_valid(lm3533->gpio_hwen)) {
+		ret = gpio_request_one(lm3533->gpio_hwen, GPIOF_OUT_INIT_LOW,
+								"lm3533-hwen");
+		if (ret < 0) {
+			dev_err(lm3533->dev,
+				"failed to request HWEN GPIO %d\n",
+				lm3533->gpio_hwen);
+			return ret;
+		}
+	}
+
+	lm3533_enable(lm3533);
+
+	lm3533_device_als_init(lm3533);
+	lm3533_device_bl_init(lm3533);
+	lm3533_device_led_init(lm3533);
+
+	ret = sysfs_create_group(&lm3533->dev->kobj, &lm3533_attribute_group);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	return 0;
+
+err_unregister:
+	mfd_remove_devices(lm3533->dev);
+	lm3533_disable(lm3533);
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_free(lm3533->gpio_hwen);
+
+	return ret;
+}
+
+static void __devexit lm3533_device_exit(struct lm3533 *lm3533)
+{
+	dev_dbg(lm3533->dev, "%s\n", __func__);
+
+	sysfs_remove_group(&lm3533->dev->kobj, &lm3533_attribute_group);
+
+	mfd_remove_devices(lm3533->dev);
+	lm3533_disable(lm3533);
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_free(lm3533->gpio_hwen);
+}
+
+static bool lm3533_readable_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case 0x10 ... 0x2c:
+	case 0x30 ... 0x38:
+	case 0x40 ... 0x45:
+	case 0x50 ... 0x57:
+	case 0x60 ... 0x6e:
+	case 0x70 ... 0x75:
+	case 0x80 ... 0x85:
+	case 0x90 ... 0x95:
+	case 0xa0 ... 0xa5:
+	case 0xb0 ... 0xb2:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool lm3533_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case 0x34:		/* zone */
+	case 0x37 ... 0x38:	/* adc */
+	case 0xb0 ... 0xb1:	/* fault */
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool lm3533_precious_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case 0x34:		/* zone */
+		return true;
+	default:
+		return false;
+	}
+}
+
+static struct regmap_config regmap_config = {
+	.reg_bits	= 8,
+	.val_bits	= 8,
+	.max_register	= LM3533_REG_MAX,
+	.readable_reg	= lm3533_readable_register,
+	.volatile_reg	= lm3533_volatile_register,
+	.precious_reg	= lm3533_precious_register,
+};
+
+static int __devinit lm3533_i2c_probe(struct i2c_client *i2c,
+					const struct i2c_device_id *id)
+{
+	struct lm3533 *lm3533;
+	int ret;
+
+	dev_dbg(&i2c->dev, "%s\n", __func__);
+
+	lm3533 = kzalloc(sizeof(*lm3533), GFP_KERNEL);
+	if (!lm3533)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, lm3533);
+
+	lm3533->regmap = regmap_init_i2c(i2c, &regmap_config);
+	if (IS_ERR(lm3533->regmap)) {
+		ret = PTR_ERR(lm3533->regmap);
+		goto err_regmap;
+	}
+
+	lm3533->dev = &i2c->dev;
+	lm3533->irq = i2c->irq;
+
+	ret = lm3533_device_init(lm3533);
+	if (ret)
+		goto err_dev;
+
+	return 0;
+
+err_dev:
+	regmap_exit(lm3533->regmap);
+err_regmap:
+	kfree(lm3533);
+
+	return ret;
+}
+
+static int __devexit lm3533_i2c_remove(struct i2c_client *i2c)
+{
+	struct lm3533 *lm3533 = i2c_get_clientdata(i2c);
+
+	dev_dbg(&i2c->dev, "%s\n", __func__);
+
+	lm3533_device_exit(lm3533);
+	regmap_exit(lm3533->regmap);
+
+	kfree(lm3533);
+
+	return 0;
+}
+
+static const struct i2c_device_id lm3533_i2c_ids[] = {
+	{ "lm3533", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, lm3533_i2c_ids);
+
+static struct i2c_driver lm3533_i2c_driver = {
+	.driver = {
+		   .name = "lm3533",
+		   .owner = THIS_MODULE,
+	},
+	.id_table	= lm3533_i2c_ids,
+	.probe		= lm3533_i2c_probe,
+	.remove		= __devexit_p(lm3533_i2c_remove),
+};
+
+static int __init lm3533_i2c_init(void)
+{
+	return i2c_add_driver(&lm3533_i2c_driver);
+}
+subsys_initcall(lm3533_i2c_init);
+
+static void __exit lm3533_i2c_exit(void)
+{
+	i2c_del_driver(&lm3533_i2c_driver);
+}
+module_exit(lm3533_i2c_exit);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/lm3533-ctrlbank.c b/drivers/mfd/lm3533-ctrlbank.c
new file mode 100644
index 0000000..c2732a3
--- /dev/null
+++ b/drivers/mfd/lm3533-ctrlbank.c
@@ -0,0 +1,134 @@
+/*
+ * lm3533-ctrlbank.c -- LM3533 Generic Control Bank interface
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_BRIGHTNESS_MAX		255
+#define LM3533_MAX_CURRENT_MAX		31
+#define LM3533_PWM_MAX			0x3f
+
+#define LM3533_REG_PWM_BASE		0x14
+#define LM3533_REG_MAX_CURRENT_BASE	0x1f
+#define LM3533_REG_CTRLBANK_ENABLE	0x27
+#define LM3533_REG_BRIGHTNESS_BASE	0x40
+
+
+static inline u8 lm3533_ctrlbank_get_reg(struct lm3533_ctrlbank *cb, u8 base)
+{
+	return base + cb->id;
+}
+
+int lm3533_ctrlbank_enable(struct lm3533_ctrlbank *cb)
+{
+	u8 mask;
+	int ret;
+
+	dev_dbg(cb->dev, "%s - %d\n", __func__, cb->id);
+
+	mask = 1 << cb->id;
+	ret = lm3533_update(cb->lm3533, LM3533_REG_CTRLBANK_ENABLE,
+								mask, mask);
+	if (ret)
+		dev_err(cb->dev, "failed to enable ctrlbank %d\n", cb->id);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_enable);
+
+int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb)
+{
+	u8 mask;
+	int ret;
+
+	dev_dbg(cb->dev, "%s - %d\n", __func__, cb->id);
+
+	mask = 1 << cb->id;
+	ret = lm3533_update(cb->lm3533, LM3533_REG_CTRLBANK_ENABLE, 0, mask);
+	if (ret)
+		dev_err(cb->dev, "failed to disable ctrlbank %d\n", cb->id);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_disable);
+
+#define lm3533_ctrlbank_set(_name, _NAME)				\
+int lm3533_ctrlbank_set_##_name(struct lm3533_ctrlbank *cb, u8 val)	\
+{									\
+	u8 reg;								\
+	int ret;							\
+									\
+	if (val > LM3533_##_NAME##_MAX)					\
+		return -EINVAL;						\
+									\
+	reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_##_NAME##_BASE);	\
+	ret = lm3533_write(cb->lm3533, reg, val);			\
+	if (ret)							\
+		dev_err(cb->dev, "failed to set " #_name "\n");		\
+									\
+	return ret;							\
+}									\
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_set_##_name);
+
+#define lm3533_ctrlbank_get(_name, _NAME)				\
+int lm3533_ctrlbank_get_##_name(struct lm3533_ctrlbank *cb, u8 *val)	\
+{									\
+	u8 reg;								\
+	int ret;							\
+									\
+	reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_##_NAME##_BASE);	\
+	ret = lm3533_read(cb->lm3533, reg, val);			\
+	if (ret)							\
+		dev_err(cb->dev, "failed to get " #_name "\n");		\
+									\
+	return ret;							\
+}									\
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_get_##_name);
+
+lm3533_ctrlbank_set(brightness, BRIGHTNESS);
+lm3533_ctrlbank_get(brightness, BRIGHTNESS);
+
+/*
+ * Full scale current.
+ *
+ * Imax = 5 + val * 0.8 mA, e.g.:
+ *
+ *    0 - 5 mA
+ *     ...
+ *   19 - 20.2 mA (default)
+ *     ...
+ *   31 - 29.8 mA
+ */
+lm3533_ctrlbank_set(max_current, MAX_CURRENT);
+lm3533_ctrlbank_get(max_current, MAX_CURRENT);
+
+/*
+ * PWM-input control mask:
+ *
+ *   bit 5 - PWM-input enabled in Zone 4
+ *   bit 4 - PWM-input enabled in Zone 3
+ *   bit 3 - PWM-input enabled in Zone 2
+ *   bit 2 - PWM-input enabled in Zone 1
+ *   bit 1 - PWM-input enabled in Zone 0
+ *   bit 0 - PWM-input enabled
+ */
+lm3533_ctrlbank_set(pwm, PWM);
+lm3533_ctrlbank_get(pwm, PWM);
+
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Control Bank interface");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
new file mode 100644
index 0000000..75f85f3
--- /dev/null
+++ b/include/linux/mfd/lm3533.h
@@ -0,0 +1,89 @@
+/*
+ * lm3533.h -- LM3533 interface
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_MFD_LM3533_H
+#define __LINUX_MFD_LM3533_H
+
+#define LM3533_ATTR_RO(_name) \
+	DEVICE_ATTR(_name, S_IRUGO, show_##_name, NULL)
+#define LM3533_ATTR_RW(_name) \
+	DEVICE_ATTR(_name, S_IRUGO | S_IWUSR , show_##_name, store_##_name)
+
+struct device;
+struct regmap;
+
+struct lm3533 {
+	struct device *dev;
+
+	struct regmap *regmap;
+
+	int gpio_hwen;
+	int irq;
+
+	unsigned have_als:1;
+	unsigned have_backlights:1;
+	unsigned have_leds:1;
+};
+
+struct lm3533_ctrlbank {
+	struct lm3533 *lm3533;
+	struct device *dev;
+	int id;
+};
+
+struct lm3533_als_platform_data {
+	unsigned pwm_mode:1;		/* PWM input mode (default analog) */
+};
+
+struct lm3533_bl_platform_data {
+	char *name;
+	u8 default_brightness;		/* 0 - 255 */
+	u8 max_current;			/* 0 - 31 */
+	u8 pwm;				/* 0 - 0x3f */
+};
+
+struct lm3533_led_platform_data {
+	char *name;
+	const char *default_trigger;
+	u8 max_current;			/* 0 - 31 */
+	u8 pwm;				/* 0 - 0x3f */
+};
+
+struct lm3533_platform_data {
+	int gpio_hwen;
+
+	struct lm3533_als_platform_data *als;
+
+	struct lm3533_bl_platform_data *backlights;
+	int num_backlights;
+
+	struct lm3533_led_platform_data *leds;
+	int num_leds;
+};
+
+extern int lm3533_ctrlbank_enable(struct lm3533_ctrlbank *cb);
+extern int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb);
+
+extern int lm3533_ctrlbank_set_brightness(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_brightness(struct lm3533_ctrlbank *cb, u8 *val);
+extern int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_max_current(struct lm3533_ctrlbank *cb,
+								u8 *val);
+extern int lm3533_ctrlbank_set_pwm(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_pwm(struct lm3533_ctrlbank *cb, u8 *val);
+
+extern int lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val);
+extern int lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val);
+extern int lm3533_update(struct lm3533 *lm3533, u8 reg, u8 val, u8 mask);
+
+#endif	/* __LINUX_MFD_LM3533_H */
-- 
1.7.8.5


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

* [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-03 10:26     ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:26 UTC (permalink / raw)
  To: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat
  Cc: Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev, Johan Hovold

Add support for National Semiconductor / TI LM3533 lighting power chips.

This is the core driver which provides register access over I2C and
registers the ambient-light-sensor, LED and backlight sub-drivers.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2:
 - add sysfs-ABI documentation
 - merge i2c implementation with core
 - use regmap and kill custom debugfs interface


 .../ABI/testing/sysfs-bus-i2c-devices-lm3533       |   38 +
 drivers/mfd/Kconfig                                |   13 +
 drivers/mfd/Makefile                               |    1 +
 drivers/mfd/lm3533-core.c                          |  717 ++++++++++++++++++++
 drivers/mfd/lm3533-ctrlbank.c                      |  134 ++++
 include/linux/mfd/lm3533.h                         |   89 +++
 6 files changed, 992 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
 create mode 100644 drivers/mfd/lm3533-core.c
 create mode 100644 drivers/mfd/lm3533-ctrlbank.c
 create mode 100644 include/linux/mfd/lm3533.h

diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533 b/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
new file mode 100644
index 0000000..5700721
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
@@ -0,0 +1,38 @@
+What:		/sys/bus/i2c/devices/.../boost_freq
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the boost converter switching frequency (0, 1), where
+
+		0 -  500Hz
+		1 - 1000Hz
+
+What:		/sys/bus/i2c/devices/.../boost_ovp
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the boost converter over-voltage protection threshold
+		(0..3), where
+
+		0 - 16V
+		1 - 24V
+		2 - 32V
+		3 - 40V
+
+What:		/sys/bus/i2c/devices/.../output_hvled[n]
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the controlling backlight device for high-voltage current
+		sink HVLED[n] (n = 1, 2) (0, 1).
+
+What:		/sys/bus/i2c/devices/.../output_lvled[n]
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the controlling led device for low-voltage current sink
+		LVLED[n] (n = 1..5) (0..3).
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 11e4438..8fe0771 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -106,6 +106,19 @@ config UCB1400_CORE
 	  To compile this driver as a module, choose M here: the
 	  module will be called ucb1400_core.
 
+config MFD_LM3533
+	tristate "LM3533 Lighting Power chip"
+	depends on I2C
+	select MFD_CORE
+	select REGMAP_I2C
+	help
+	  Say yes here to enable support for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  This driver provides common support for accessing the device;
+	  additional drivers must be enabled in order to use the LED,
+	  backlight or ambient-light-sensor functionality of the device.
+
 config TPS6105X
 	tristate "TPS61050/61052 Boost Converters"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 05fa538..b6fe0a5 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -116,3 +116,4 @@ obj-$(CONFIG_MFD_INTEL_MSIC)	+= intel_msic.o
 obj-$(CONFIG_MFD_RC5T583)	+= rc5t583.o rc5t583-irq.o
 obj-$(CONFIG_MFD_S5M_CORE)	+= s5m-core.o s5m-irq.o
 obj-$(CONFIG_MFD_ANATOP)	+= anatop-mfd.o
+obj-$(CONFIG_MFD_LM3533)	+= lm3533-core.o lm3533-ctrlbank.o
diff --git a/drivers/mfd/lm3533-core.c b/drivers/mfd/lm3533-core.c
new file mode 100644
index 0000000..75f4b7f
--- /dev/null
+++ b/drivers/mfd/lm3533-core.c
@@ -0,0 +1,717 @@
+/*
+ * lm3533-core.c -- LM3533 Core
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/regmap.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_BOOST_OVP_MAX		0x03
+#define LM3533_BOOST_OVP_MASK		0x06
+#define LM3533_BOOST_OVP_SHIFT		1
+
+#define LM3533_BOOST_FREQ_MAX		0x01
+#define LM3533_BOOST_FREQ_MASK		0x01
+#define LM3533_BOOST_FREQ_SHIFT		0
+
+#define LM3533_BL_ID_MASK		1
+#define LM3533_LED_ID_MASK		3
+#define LM3533_BL_ID_MAX		1
+#define LM3533_LED_ID_MAX		3
+
+#define LM3533_HVLED_ID_MAX		2
+#define LM3533_LVLED_ID_MAX		5
+
+#define LM3533_REG_OUTPUT_CONF1		0x10
+#define LM3533_REG_OUTPUT_CONF2		0x11
+#define LM3533_REG_BOOST_PWM		0x2c
+
+#define LM3533_REG_MAX			0xb2
+
+
+static struct mfd_cell lm3533_als_devs[] = {
+	{
+		.name	= "lm3533-als",
+		.id	= -1,
+	},
+};
+
+static struct mfd_cell lm3533_bl_devs[] = {
+	{
+		.name	= "lm3533-backlight",
+		.id	= 0,
+	},
+	{
+		.name	= "lm3533-backlight",
+		.id	= 1,
+	},
+};
+
+static struct mfd_cell lm3533_led_devs[] = {
+	{
+		.name	= "lm3533-leds",
+		.id	= 0,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 1,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 2,
+	},
+	{
+		.name	= "lm3533-leds",
+		.id	= 3,
+	},
+};
+
+int lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val)
+{
+	int tmp;
+	int ret;
+
+	ret = regmap_read(lm3533->regmap, reg, &tmp);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to read register %02x: %d\n",
+								reg, ret);
+		return ret;
+	}
+
+	*val = tmp;
+
+	dev_dbg(lm3533->dev, "read [%02x]: %02x\n", reg, *val);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_read);
+
+int lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val)
+{
+	int ret;
+
+	dev_dbg(lm3533->dev, "write [%02x]: %02x\n", reg, val);
+
+	ret = regmap_write(lm3533->regmap, reg, val);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to write register %02x: %d\n",
+								reg, ret);
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_write);
+
+int lm3533_update(struct lm3533 *lm3533, u8 reg, u8 val, u8 mask)
+{
+	int ret;
+
+	dev_dbg(lm3533->dev, "update [%02x]: %02x/%02x\n", reg, val, mask);
+
+	ret = regmap_update_bits(lm3533->regmap, reg, val, mask);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to update register %02x: %d\n",
+								reg, ret);
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_update);
+
+/*
+ * HVLED output config -- output hvled controlled by backlight bl
+ */
+static int lm3533_set_hvled_config(struct lm3533 *lm3533, u8 hvled, u8 bl)
+{
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (hvled = 0 || hvled > LM3533_HVLED_ID_MAX)
+		return -EINVAL;
+
+	if (bl > LM3533_BL_ID_MAX)
+		return -EINVAL;
+
+	shift = hvled - 1;
+	mask = LM3533_BL_ID_MASK << shift;
+	val = bl << shift;
+
+	ret = lm3533_update(lm3533, LM3533_REG_OUTPUT_CONF1, val, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to set hvled config\n");
+
+	return ret;
+}
+
+/*
+ * LVLED output config -- output lvled controlled by LED led
+ */
+static int lm3533_set_lvled_config(struct lm3533 *lm3533, u8 lvled, u8 led)
+{
+	u8 reg;
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (lvled = 0 || lvled > LM3533_LVLED_ID_MAX)
+		return -EINVAL;
+
+	if (led > LM3533_LED_ID_MAX)
+		return -EINVAL;
+
+	if (lvled < 4) {
+		reg = LM3533_REG_OUTPUT_CONF1;
+		shift = 2 * lvled;
+	} else {
+		reg = LM3533_REG_OUTPUT_CONF2;
+		shift = 2 * (lvled - 4);
+	}
+
+	mask = LM3533_LED_ID_MASK << shift;
+	val = led << shift;
+
+	ret = lm3533_update(lm3533, reg, val, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to set lvled config\n");
+
+	return ret;
+}
+
+static void lm3533_enable(struct lm3533 *lm3533)
+{
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_set_value(lm3533->gpio_hwen, 1);
+}
+
+static void lm3533_disable(struct lm3533 *lm3533)
+{
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_set_value(lm3533->gpio_hwen, 0);
+}
+
+enum lm3533_attribute_type {
+	LM3533_ATTR_TYPE_BACKLIGHT,
+	LM3533_ATTR_TYPE_LED,
+};
+
+struct lm3533_device_attribute {
+	struct device_attribute dev_attr;
+	enum lm3533_attribute_type type;
+	union {
+		struct {
+			u8 id;
+		} output;
+		struct {
+			u8 reg;
+			u8 shift;
+			u8 mask;
+			u8 max;
+		} generic;
+	} u;
+};
+
+#define to_lm3533_dev_attr(_attr) \
+	container_of(_attr, struct lm3533_device_attribute, dev_attr)
+
+static ssize_t show_lm3533_reg(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(lm3533, lattr->u.generic.reg, &val);
+	if (ret)
+		return ret;
+
+	val = (val & lattr->u.generic.mask) >> lattr->u.generic.shift;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_lm3533_reg(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t len)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > lattr->u.generic.max)
+		return -EINVAL;
+
+	val = val << lattr->u.generic.shift;
+	ret = lm3533_update(lm3533, lattr->u.generic.reg, val,
+							lattr->u.generic.mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define GENERIC_ATTR(_reg, _max, _mask, _shift) \
+	{ .reg		= _reg, \
+	  .max		= _max, \
+	  .mask		= _mask, \
+	  .shift	= _shift }
+
+#define LM3533_GENERIC_ATTR(_name, _mode, _show, _store, _type,	\
+						_reg, _max, _mask, _shift) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name = { \
+		.dev_attr	= __ATTR(_name, _mode, _show, _store), \
+		.type		= _type, \
+		.u.generic	= GENERIC_ATTR(_reg, _max, _mask, _shift) }
+
+#define LM3533_GENERIC_ATTR_RW(_name, _type, _reg, _max, _mask, _shift) \
+	LM3533_GENERIC_ATTR(_name, S_IRUGO | S_IWUSR, \
+					show_lm3533_reg, store_lm3533_reg, \
+					_type, _reg, _max, _mask, _shift)
+
+#define LM3533_BOOST_ATTR_RW(_name, _NAME) \
+	LM3533_GENERIC_ATTR_RW(_name, LM3533_ATTR_TYPE_BACKLIGHT, \
+				LM3533_REG_BOOST_PWM, LM3533_##_NAME##_MAX, \
+				LM3533_##_NAME##_MASK, LM3533_##_NAME##_SHIFT)
+/*
+ * Boost Over Voltage Protection Select
+ *
+ *   0 - 16 V (default)
+ *   1 - 24 V
+ *   2 - 32 V
+ *   3 - 40 V
+ */
+static LM3533_BOOST_ATTR_RW(boost_ovp, BOOST_OVP);
+
+/*
+ * Boost Frequency Select
+ *
+ *   0 - 500 kHz (default)
+ *   1 - 1 MHz
+ */
+static LM3533_BOOST_ATTR_RW(boost_freq, BOOST_FREQ);
+
+static ssize_t show_output(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	int id = lattr->u.output.id;
+	u8 reg;
+	u8 val;
+	u8 mask;
+	int shift;
+	int ret;
+
+	if (lattr->type = LM3533_ATTR_TYPE_BACKLIGHT) {
+		reg = LM3533_REG_OUTPUT_CONF1;
+		shift = id - 1;
+		mask = LM3533_BL_ID_MASK << shift;
+	} else {
+		if (id < 4) {
+			reg = LM3533_REG_OUTPUT_CONF1;
+			shift = 2 * id;
+		} else {
+			reg = LM3533_REG_OUTPUT_CONF2;
+			shift = 2 * (id - 4);
+		}
+		mask = LM3533_LED_ID_MASK << shift;
+	}
+
+	ret = lm3533_read(lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	val = (val & mask) >> shift;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_output(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
+	int id = lattr->u.output.id;
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	if (lattr->type = LM3533_ATTR_TYPE_BACKLIGHT)
+		ret = lm3533_set_hvled_config(lm3533, id, val);
+	else
+		ret = lm3533_set_lvled_config(lm3533, id, val);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define LM3533_OUTPUT_ATTR(_name, _mode, _show, _store, _type, _id) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name = \
+		{ .dev_attr	= __ATTR(_name, _mode, _show, _store), \
+		  .type		= _type, \
+		  .u.output	= { .id = _id }, }
+
+#define LM3533_OUTPUT_ATTR_RW(_name, _type, _id) \
+	LM3533_OUTPUT_ATTR(output_##_name, S_IRUGO | S_IWUSR, \
+					show_output, store_output, _type, _id)
+
+#define LM3533_OUTPUT_HVLED_ATTR_RW(_nr) \
+	LM3533_OUTPUT_ATTR_RW(hvled##_nr, LM3533_ATTR_TYPE_BACKLIGHT, _nr)
+#define LM3533_OUTPUT_LVLED_ATTR_RW(_nr) \
+	LM3533_OUTPUT_ATTR_RW(lvled##_nr, LM3533_ATTR_TYPE_LED, _nr)
+/*
+ * Output config:
+ *
+ * output_hvled<nr>	0-1
+ * output_lvled<nr>	0-3
+ */
+static LM3533_OUTPUT_HVLED_ATTR_RW(1);
+static LM3533_OUTPUT_HVLED_ATTR_RW(2);
+static LM3533_OUTPUT_LVLED_ATTR_RW(1);
+static LM3533_OUTPUT_LVLED_ATTR_RW(2);
+static LM3533_OUTPUT_LVLED_ATTR_RW(3);
+static LM3533_OUTPUT_LVLED_ATTR_RW(4);
+static LM3533_OUTPUT_LVLED_ATTR_RW(5);
+
+static struct attribute *lm3533_attributes[] = {
+	&lm3533_dev_attr_boost_freq.dev_attr.attr,
+	&lm3533_dev_attr_boost_ovp.dev_attr.attr,
+	&lm3533_dev_attr_output_hvled1.dev_attr.attr,
+	&lm3533_dev_attr_output_hvled2.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled1.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled2.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled3.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled4.dev_attr.attr,
+	&lm3533_dev_attr_output_lvled5.dev_attr.attr,
+	NULL,
+};
+
+#define to_dev_attr(_attr) \
+	container_of(_attr, struct device_attribute, attr)
+
+static mode_t lm3533_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533 *lm3533 = dev_get_drvdata(dev);
+	struct device_attribute *dattr = to_dev_attr(attr);
+	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(dattr);
+	enum lm3533_attribute_type type = lattr->type;
+	mode_t mode = attr->mode;
+
+	if (!lm3533->have_backlights && type = LM3533_ATTR_TYPE_BACKLIGHT)
+		mode = 0;
+	else if (!lm3533->have_leds && type = LM3533_ATTR_TYPE_LED)
+		mode = 0;
+
+	return mode;
+};
+
+static struct attribute_group lm3533_attribute_group = {
+	.is_visible	= lm3533_attr_is_visible,
+	.attrs		= lm3533_attributes
+};
+
+static int __devinit lm3533_device_als_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int ret;
+
+	if (!pdata->als)
+		return 0;
+
+	lm3533_als_devs[0].platform_data = pdata->als;
+	lm3533_als_devs[0].pdata_size = sizeof(*pdata->als);
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_als_devs, 1, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add ALS device\n");
+		return ret;
+	}
+
+	lm3533->have_als = 1;
+
+	return 0;
+}
+
+static int __devinit lm3533_device_bl_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int i;
+	int ret;
+
+	if (!pdata->backlights || pdata->num_backlights = 0)
+		return 0;
+
+	if (pdata->num_backlights > ARRAY_SIZE(lm3533_bl_devs))
+		pdata->num_backlights = ARRAY_SIZE(lm3533_bl_devs);
+
+	for (i = 0; i < pdata->num_backlights; ++i) {
+		lm3533_bl_devs[i].platform_data = &pdata->backlights[i];
+		lm3533_bl_devs[i].pdata_size = sizeof(pdata->backlights[i]);
+	}
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_bl_devs,
+					pdata->num_backlights, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add backlight devices\n");
+		return ret;
+	}
+
+	lm3533->have_backlights = 1;
+
+	return 0;
+}
+
+static int __devinit lm3533_device_led_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int i;
+	int ret;
+
+	if (!pdata->leds || pdata->num_leds = 0)
+		return 0;
+
+	if (pdata->num_leds > ARRAY_SIZE(lm3533_led_devs))
+		pdata->num_leds = ARRAY_SIZE(lm3533_led_devs);
+
+	for (i = 0; i < pdata->num_leds; ++i) {
+		lm3533_led_devs[i].platform_data = &pdata->leds[i];
+		lm3533_led_devs[i].pdata_size = sizeof(pdata->leds[i]);
+	}
+
+	ret = mfd_add_devices(lm3533->dev, 0, lm3533_led_devs,
+						pdata->num_leds, NULL, 0);
+	if (ret) {
+		dev_err(lm3533->dev, "failed to add LED devices\n");
+		return ret;
+	}
+
+	lm3533->have_leds = 1;
+
+	return 0;
+}
+
+static int __devinit lm3533_device_init(struct lm3533 *lm3533)
+{
+	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
+	int ret;
+
+	dev_dbg(lm3533->dev, "%s\n", __func__);
+
+	if (!pdata) {
+		dev_err(lm3533->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	lm3533->gpio_hwen = pdata->gpio_hwen;
+
+	dev_set_drvdata(lm3533->dev, lm3533);
+
+	if (gpio_is_valid(lm3533->gpio_hwen)) {
+		ret = gpio_request_one(lm3533->gpio_hwen, GPIOF_OUT_INIT_LOW,
+								"lm3533-hwen");
+		if (ret < 0) {
+			dev_err(lm3533->dev,
+				"failed to request HWEN GPIO %d\n",
+				lm3533->gpio_hwen);
+			return ret;
+		}
+	}
+
+	lm3533_enable(lm3533);
+
+	lm3533_device_als_init(lm3533);
+	lm3533_device_bl_init(lm3533);
+	lm3533_device_led_init(lm3533);
+
+	ret = sysfs_create_group(&lm3533->dev->kobj, &lm3533_attribute_group);
+	if (ret < 0) {
+		dev_err(lm3533->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	return 0;
+
+err_unregister:
+	mfd_remove_devices(lm3533->dev);
+	lm3533_disable(lm3533);
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_free(lm3533->gpio_hwen);
+
+	return ret;
+}
+
+static void __devexit lm3533_device_exit(struct lm3533 *lm3533)
+{
+	dev_dbg(lm3533->dev, "%s\n", __func__);
+
+	sysfs_remove_group(&lm3533->dev->kobj, &lm3533_attribute_group);
+
+	mfd_remove_devices(lm3533->dev);
+	lm3533_disable(lm3533);
+	if (gpio_is_valid(lm3533->gpio_hwen))
+		gpio_free(lm3533->gpio_hwen);
+}
+
+static bool lm3533_readable_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case 0x10 ... 0x2c:
+	case 0x30 ... 0x38:
+	case 0x40 ... 0x45:
+	case 0x50 ... 0x57:
+	case 0x60 ... 0x6e:
+	case 0x70 ... 0x75:
+	case 0x80 ... 0x85:
+	case 0x90 ... 0x95:
+	case 0xa0 ... 0xa5:
+	case 0xb0 ... 0xb2:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool lm3533_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case 0x34:		/* zone */
+	case 0x37 ... 0x38:	/* adc */
+	case 0xb0 ... 0xb1:	/* fault */
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool lm3533_precious_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case 0x34:		/* zone */
+		return true;
+	default:
+		return false;
+	}
+}
+
+static struct regmap_config regmap_config = {
+	.reg_bits	= 8,
+	.val_bits	= 8,
+	.max_register	= LM3533_REG_MAX,
+	.readable_reg	= lm3533_readable_register,
+	.volatile_reg	= lm3533_volatile_register,
+	.precious_reg	= lm3533_precious_register,
+};
+
+static int __devinit lm3533_i2c_probe(struct i2c_client *i2c,
+					const struct i2c_device_id *id)
+{
+	struct lm3533 *lm3533;
+	int ret;
+
+	dev_dbg(&i2c->dev, "%s\n", __func__);
+
+	lm3533 = kzalloc(sizeof(*lm3533), GFP_KERNEL);
+	if (!lm3533)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, lm3533);
+
+	lm3533->regmap = regmap_init_i2c(i2c, &regmap_config);
+	if (IS_ERR(lm3533->regmap)) {
+		ret = PTR_ERR(lm3533->regmap);
+		goto err_regmap;
+	}
+
+	lm3533->dev = &i2c->dev;
+	lm3533->irq = i2c->irq;
+
+	ret = lm3533_device_init(lm3533);
+	if (ret)
+		goto err_dev;
+
+	return 0;
+
+err_dev:
+	regmap_exit(lm3533->regmap);
+err_regmap:
+	kfree(lm3533);
+
+	return ret;
+}
+
+static int __devexit lm3533_i2c_remove(struct i2c_client *i2c)
+{
+	struct lm3533 *lm3533 = i2c_get_clientdata(i2c);
+
+	dev_dbg(&i2c->dev, "%s\n", __func__);
+
+	lm3533_device_exit(lm3533);
+	regmap_exit(lm3533->regmap);
+
+	kfree(lm3533);
+
+	return 0;
+}
+
+static const struct i2c_device_id lm3533_i2c_ids[] = {
+	{ "lm3533", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, lm3533_i2c_ids);
+
+static struct i2c_driver lm3533_i2c_driver = {
+	.driver = {
+		   .name = "lm3533",
+		   .owner = THIS_MODULE,
+	},
+	.id_table	= lm3533_i2c_ids,
+	.probe		= lm3533_i2c_probe,
+	.remove		= __devexit_p(lm3533_i2c_remove),
+};
+
+static int __init lm3533_i2c_init(void)
+{
+	return i2c_add_driver(&lm3533_i2c_driver);
+}
+subsys_initcall(lm3533_i2c_init);
+
+static void __exit lm3533_i2c_exit(void)
+{
+	i2c_del_driver(&lm3533_i2c_driver);
+}
+module_exit(lm3533_i2c_exit);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/lm3533-ctrlbank.c b/drivers/mfd/lm3533-ctrlbank.c
new file mode 100644
index 0000000..c2732a3
--- /dev/null
+++ b/drivers/mfd/lm3533-ctrlbank.c
@@ -0,0 +1,134 @@
+/*
+ * lm3533-ctrlbank.c -- LM3533 Generic Control Bank interface
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_BRIGHTNESS_MAX		255
+#define LM3533_MAX_CURRENT_MAX		31
+#define LM3533_PWM_MAX			0x3f
+
+#define LM3533_REG_PWM_BASE		0x14
+#define LM3533_REG_MAX_CURRENT_BASE	0x1f
+#define LM3533_REG_CTRLBANK_ENABLE	0x27
+#define LM3533_REG_BRIGHTNESS_BASE	0x40
+
+
+static inline u8 lm3533_ctrlbank_get_reg(struct lm3533_ctrlbank *cb, u8 base)
+{
+	return base + cb->id;
+}
+
+int lm3533_ctrlbank_enable(struct lm3533_ctrlbank *cb)
+{
+	u8 mask;
+	int ret;
+
+	dev_dbg(cb->dev, "%s - %d\n", __func__, cb->id);
+
+	mask = 1 << cb->id;
+	ret = lm3533_update(cb->lm3533, LM3533_REG_CTRLBANK_ENABLE,
+								mask, mask);
+	if (ret)
+		dev_err(cb->dev, "failed to enable ctrlbank %d\n", cb->id);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_enable);
+
+int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb)
+{
+	u8 mask;
+	int ret;
+
+	dev_dbg(cb->dev, "%s - %d\n", __func__, cb->id);
+
+	mask = 1 << cb->id;
+	ret = lm3533_update(cb->lm3533, LM3533_REG_CTRLBANK_ENABLE, 0, mask);
+	if (ret)
+		dev_err(cb->dev, "failed to disable ctrlbank %d\n", cb->id);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_disable);
+
+#define lm3533_ctrlbank_set(_name, _NAME)				\
+int lm3533_ctrlbank_set_##_name(struct lm3533_ctrlbank *cb, u8 val)	\
+{									\
+	u8 reg;								\
+	int ret;							\
+									\
+	if (val > LM3533_##_NAME##_MAX)					\
+		return -EINVAL;						\
+									\
+	reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_##_NAME##_BASE);	\
+	ret = lm3533_write(cb->lm3533, reg, val);			\
+	if (ret)							\
+		dev_err(cb->dev, "failed to set " #_name "\n");		\
+									\
+	return ret;							\
+}									\
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_set_##_name);
+
+#define lm3533_ctrlbank_get(_name, _NAME)				\
+int lm3533_ctrlbank_get_##_name(struct lm3533_ctrlbank *cb, u8 *val)	\
+{									\
+	u8 reg;								\
+	int ret;							\
+									\
+	reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_##_NAME##_BASE);	\
+	ret = lm3533_read(cb->lm3533, reg, val);			\
+	if (ret)							\
+		dev_err(cb->dev, "failed to get " #_name "\n");		\
+									\
+	return ret;							\
+}									\
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_get_##_name);
+
+lm3533_ctrlbank_set(brightness, BRIGHTNESS);
+lm3533_ctrlbank_get(brightness, BRIGHTNESS);
+
+/*
+ * Full scale current.
+ *
+ * Imax = 5 + val * 0.8 mA, e.g.:
+ *
+ *    0 - 5 mA
+ *     ...
+ *   19 - 20.2 mA (default)
+ *     ...
+ *   31 - 29.8 mA
+ */
+lm3533_ctrlbank_set(max_current, MAX_CURRENT);
+lm3533_ctrlbank_get(max_current, MAX_CURRENT);
+
+/*
+ * PWM-input control mask:
+ *
+ *   bit 5 - PWM-input enabled in Zone 4
+ *   bit 4 - PWM-input enabled in Zone 3
+ *   bit 3 - PWM-input enabled in Zone 2
+ *   bit 2 - PWM-input enabled in Zone 1
+ *   bit 1 - PWM-input enabled in Zone 0
+ *   bit 0 - PWM-input enabled
+ */
+lm3533_ctrlbank_set(pwm, PWM);
+lm3533_ctrlbank_get(pwm, PWM);
+
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Control Bank interface");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
new file mode 100644
index 0000000..75f85f3
--- /dev/null
+++ b/include/linux/mfd/lm3533.h
@@ -0,0 +1,89 @@
+/*
+ * lm3533.h -- LM3533 interface
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_MFD_LM3533_H
+#define __LINUX_MFD_LM3533_H
+
+#define LM3533_ATTR_RO(_name) \
+	DEVICE_ATTR(_name, S_IRUGO, show_##_name, NULL)
+#define LM3533_ATTR_RW(_name) \
+	DEVICE_ATTR(_name, S_IRUGO | S_IWUSR , show_##_name, store_##_name)
+
+struct device;
+struct regmap;
+
+struct lm3533 {
+	struct device *dev;
+
+	struct regmap *regmap;
+
+	int gpio_hwen;
+	int irq;
+
+	unsigned have_als:1;
+	unsigned have_backlights:1;
+	unsigned have_leds:1;
+};
+
+struct lm3533_ctrlbank {
+	struct lm3533 *lm3533;
+	struct device *dev;
+	int id;
+};
+
+struct lm3533_als_platform_data {
+	unsigned pwm_mode:1;		/* PWM input mode (default analog) */
+};
+
+struct lm3533_bl_platform_data {
+	char *name;
+	u8 default_brightness;		/* 0 - 255 */
+	u8 max_current;			/* 0 - 31 */
+	u8 pwm;				/* 0 - 0x3f */
+};
+
+struct lm3533_led_platform_data {
+	char *name;
+	const char *default_trigger;
+	u8 max_current;			/* 0 - 31 */
+	u8 pwm;				/* 0 - 0x3f */
+};
+
+struct lm3533_platform_data {
+	int gpio_hwen;
+
+	struct lm3533_als_platform_data *als;
+
+	struct lm3533_bl_platform_data *backlights;
+	int num_backlights;
+
+	struct lm3533_led_platform_data *leds;
+	int num_leds;
+};
+
+extern int lm3533_ctrlbank_enable(struct lm3533_ctrlbank *cb);
+extern int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb);
+
+extern int lm3533_ctrlbank_set_brightness(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_brightness(struct lm3533_ctrlbank *cb, u8 *val);
+extern int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_max_current(struct lm3533_ctrlbank *cb,
+								u8 *val);
+extern int lm3533_ctrlbank_set_pwm(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_get_pwm(struct lm3533_ctrlbank *cb, u8 *val);
+
+extern int lm3533_read(struct lm3533 *lm3533, u8 reg, u8 *val);
+extern int lm3533_write(struct lm3533 *lm3533, u8 reg, u8 val);
+extern int lm3533_update(struct lm3533 *lm3533, u8 reg, u8 val, u8 mask);
+
+#endif	/* __LINUX_MFD_LM3533_H */
-- 
1.7.8.5


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

* [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
  2012-05-03 10:26   ` Johan Hovold
@ 2012-05-03 10:26     ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:26 UTC (permalink / raw)
  To: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat
  Cc: Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev, Johan Hovold

Add sub-driver for the ambient light sensor interface on National
Semiconductor / TI LM3533 lighting power chips.

The sensor interface can be used to control the LEDs and backlights of
the chip through defining five light zones and three sets of
corresponding brightness target levels.

The driver provides raw and mean adc readings along with the current
light zone through sysfs. A threshold event can be generated on zone
changes.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2:
 - reimplement using iio
 - add sysfs-ABI documentation


 .../Documentation/sysfs-bus-iio-light-lm3533-als   |   62 ++
 drivers/staging/iio/light/Kconfig                  |   16 +
 drivers/staging/iio/light/Makefile                 |    1 +
 drivers/staging/iio/light/lm3533-als.c             |  617 ++++++++++++++++++++
 4 files changed, 696 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
 create mode 100644 drivers/staging/iio/light/lm3533-als.c

diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
new file mode 100644
index 0000000..9849d14
--- /dev/null
+++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
@@ -0,0 +1,62 @@
+What:		/sys/bus/iio/devices/iio:deviceX/gain
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS gain-resistor setting (0..127) for analog input
+		mode, where
+
+		0000000 - ALS input is high impedance
+		0000001 - 200kOhm (10uA at 2V full-scale)
+		0000010 - 100kOhm (20uA at 2V full-scale)
+		...
+		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
+		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
+
+		R_als = 2V / (10uA * gain)	(gain > 0)
+
+What:		/sys/bus/iio/devices/iio:deviceX/illuminance_zone
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the current light zone (0..4) as defined by the
+		in_illuminance_thresh[n]_{falling,rising} thresholds.
+
+What:		/sys/.../events/in_illuminance_thresh_either_en
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Event generated when channel passes one of the four threshold
+		in either direction (rising|falling) and a zone change occurs.
+		The corresponding light zone can be read from
+		illuminance_zone.
+
+What:		/sys/.../events/illuminance_thresh0_falling_value
+What:		/sys/.../events/illuminance_thresh0_raising_value
+What:		/sys/.../events/illuminance_thresh1_falling_value
+What:		/sys/.../events/illuminance_thresh1_raising_value
+What:		/sys/.../events/illuminance_thresh2_falling_value
+What:		/sys/.../events/illuminance_thresh2_raising_value
+What:		/sys/.../events/illuminance_thresh3_falling_value
+What:		/sys/.../events/illuminance_thresh3_raising_value
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Specifies the value of threshold that the device is comparing
+		against for the events enabled by
+		in_illuminance_thresh_either_en, and defines the
+		the five light zones.
+
+		These thresholds correspond to the eight zone-boundary
+		registers (boundary[n]_{low,high}).
+
+What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the target brightness for ALS-mapper m in light zone n
+		(0..255), where m in 1..3 and n in 0..4.
diff --git a/drivers/staging/iio/light/Kconfig b/drivers/staging/iio/light/Kconfig
index e7e9159..263e44a 100644
--- a/drivers/staging/iio/light/Kconfig
+++ b/drivers/staging/iio/light/Kconfig
@@ -24,6 +24,22 @@ config SENSORS_TSL2563
 	 This driver can also be built as a module.  If so, the module
 	 will be called tsl2563.
 
+config SENSORS_LM3533
+	tristate "LM3533 ambient light sensor"
+	depends on MFD_LM3533
+	help
+	  If you say yes here you get support for the ambient light sensor
+	  interface on National Semiconductor / TI LM3533 Lighting Power
+	  chips.
+
+	  The sensor interface can be used to control the LEDs and backlights
+	  of the chip through defining five light zones and three sets of
+	  corresponding brightness target levels.
+
+	  The driver provides raw and mean adc readings along with the current
+	  light zone through sysfs. A threshold event can be generated on zone
+	  changes.
+
 config TSL2583
 	tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters"
 	depends on I2C
diff --git a/drivers/staging/iio/light/Makefile b/drivers/staging/iio/light/Makefile
index 3011fbf..16a60a2 100644
--- a/drivers/staging/iio/light/Makefile
+++ b/drivers/staging/iio/light/Makefile
@@ -4,4 +4,5 @@
 
 obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o
 obj-$(CONFIG_SENSORS_ISL29018)	+= isl29018.o
+obj-$(CONFIG_SENSORS_LM3533)	+= lm3533-als.o
 obj-$(CONFIG_TSL2583)	+= tsl2583.o
diff --git a/drivers/staging/iio/light/lm3533-als.c b/drivers/staging/iio/light/lm3533-als.c
new file mode 100644
index 0000000..e2c9be6
--- /dev/null
+++ b/drivers/staging/iio/light/lm3533-als.c
@@ -0,0 +1,617 @@
+/*
+ * lm3533-als.c -- LM3533 Ambient Light Sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/atomic.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+#include "../events.h"
+#include "../iio.h"
+
+
+#define LM3533_ALS_RESISTOR_MAX			0x7f
+#define LM3533_ALS_ADC_MAX			0xff
+#define LM3533_ALS_BOUNDARY_MAX			LM3533_ALS_ADC_MAX
+#define LM3533_ALS_TARGET_MAX			LM3533_ALS_ADC_MAX
+#define LM3533_ALS_ZONE_MAX			4
+
+#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
+#define LM3533_REG_ALS_CONF			0x31
+#define LM3533_REG_ALS_ZONE_INFO		0x34
+#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x37
+#define LM3533_REG_ALS_READ_ADC_RAW		0x38
+#define LM3533_REG_ALS_BOUNDARY0_HIGH		0x50
+#define LM3533_REG_ALS_BOUNDARY0_LOW		0x51
+#define LM3533_REG_ALS_BOUNDARY1_HIGH		0x52
+#define LM3533_REG_ALS_BOUNDARY1_LOW		0x53
+#define LM3533_REG_ALS_BOUNDARY2_HIGH		0x54
+#define LM3533_REG_ALS_BOUNDARY2_LOW		0x55
+#define LM3533_REG_ALS_BOUNDARY3_HIGH		0x56
+#define LM3533_REG_ALS_BOUNDARY3_LOW		0x57
+#define LM3533_REG_ALS_M1_TARGET_0		0x60
+#define LM3533_REG_ALS_M1_TARGET_1		0x61
+#define LM3533_REG_ALS_M1_TARGET_2		0x62
+#define LM3533_REG_ALS_M1_TARGET_3		0x63
+#define LM3533_REG_ALS_M1_TARGET_4		0x64
+#define LM3533_REG_ALS_M2_TARGET_0		0x65
+#define LM3533_REG_ALS_M2_TARGET_1		0x66
+#define LM3533_REG_ALS_M2_TARGET_2		0x67
+#define LM3533_REG_ALS_M2_TARGET_3		0x68
+#define LM3533_REG_ALS_M2_TARGET_4		0x69
+#define LM3533_REG_ALS_M3_TARGET_0		0x6a
+#define LM3533_REG_ALS_M3_TARGET_1		0x6b
+#define LM3533_REG_ALS_M3_TARGET_2		0x6c
+#define LM3533_REG_ALS_M3_TARGET_3		0x6d
+#define LM3533_REG_ALS_M3_TARGET_4		0x6e
+
+#define LM3533_ALS_ENABLE_MASK			0x01
+#define LM3533_ALS_INPUT_MODE_MASK		0x02
+#define LM3533_ALS_INT_ENABLE_MASK		0x01
+
+#define LM3533_ALS_ZONE_SHIFT			2
+#define LM3533_ALS_ZONE_MASK			0x1c
+
+#define LM3533_ALS_FLAG_INT_ENABLED		1
+
+
+struct lm3533_als {
+	struct lm3533 *lm3533;
+
+	unsigned long flags;
+	int irq;
+
+	atomic_t zone;
+};
+
+
+static int lm3533_als_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val1, int *val2, long mask)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	u8 val;
+	int ret;
+
+	switch (mask) {
+	case 0:
+		reg = LM3533_REG_ALS_READ_ADC_RAW;
+		break;
+	case IIO_CHAN_INFO_AVERAGE_RAW:
+		reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = lm3533_read(als->lm3533, reg, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to read adc\n");
+		return ret;
+	}
+
+	*val1 = val;
+
+	return IIO_VAL_INT;
+}
+
+static const struct iio_chan_spec lm3533_als_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask = IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT,
+		.channel = 0,
+	}
+};
+
+static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to read zone\n");
+		return ret;
+	}
+
+	val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
+	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
+
+	return 0;
+}
+
+static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
+{
+
+	struct iio_dev *indio_dev = dev_id;
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 zone;
+	int ret;
+
+	/* Clear interrupt by reading the ALS zone register. */
+	ret = lm3533_als_get_zone(indio_dev, &zone);
+	if (ret)
+		goto out;
+
+	atomic_set(&als->zone, zone);
+
+	iio_push_event(indio_dev,
+		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+					    0,
+					    IIO_EV_TYPE_THRESH,
+					    IIO_EV_DIR_EITHER),
+		       iio_get_time_ns());
+out:
+	return IRQ_HANDLED;
+}
+
+static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+	int ret;
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
+								enable);
+	}
+
+	return ret;
+}
+
+static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to get int mode\n");
+		return ret;
+	}
+
+	*enable = !!(val & mask);
+
+	return 0;
+}
+
+static int show_thresh_either_en(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	int enable;
+	int ret;
+
+	if (als->irq) {
+		ret = lm3533_als_get_int_mode(indio_dev, &enable);
+		if (ret)
+			return ret;
+	} else {
+		enable = 0;
+	}
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
+}
+
+static int store_thresh_either_en(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	unsigned long enable;
+	bool int_enabled;
+	u8 zone;
+	int ret;
+
+	if (!als->irq)
+		return -EBUSY;
+
+	if (kstrtoul(buf, 0, &enable))
+		return -EINVAL;
+
+	int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+	if (enable && !int_enabled) {
+		ret = lm3533_als_get_zone(indio_dev, &zone);
+		if (ret)
+			return ret;
+
+		atomic_set(&als->zone, zone);
+
+		set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+	}
+
+	ret = lm3533_als_set_int_mode(indio_dev, enable);
+	if (ret) {
+		if (!int_enabled)
+			clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+		return ret;
+	}
+
+	if (!enable)
+		clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+	return len;
+}
+
+static ssize_t show_zone(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 zone;
+	int ret;
+
+	if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) {
+		zone = atomic_read(&als->zone);
+	} else {
+		ret = lm3533_als_get_zone(indio_dev, &zone);
+		if (ret)
+			return ret;
+	}
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
+}
+
+struct lm3533_device_attribute {
+	struct device_attribute dev_attr;
+	u8 reg;
+	u8 max;
+};
+
+#define to_lm3533_dev_attr(_dev_attr) \
+	container_of(_dev_attr, struct lm3533_device_attribute, dev_attr)
+
+static ssize_t show_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, lm3533_attr->reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > lm3533_attr->max)
+		return -EINVAL;
+
+	ret = lm3533_write(als->lm3533, lm3533_attr->reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	{ .dev_attr = __ATTR(_name, _mode, _show, _store), \
+	  .reg = _reg, \
+	  .max = _max }
+
+#define LM3533_REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name \
+		= REG_ATTR(_name, _mode, _show, _store, _reg, _max)
+
+#define LM3533_REG_ATTR_RW(_name, _reg, _max) \
+	LM3533_REG_ATTR(_name, S_IRUGO | S_IWUSR, show_lm3533_als_reg, \
+					store_lm3533_als_reg, _reg, _max)
+
+#define ALS_THRESH_FALLING_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(in_illuminance_thresh##_nr##_falling_value, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_LOW, LM3533_ALS_BOUNDARY_MAX)
+
+#define ALS_THRESH_RAISING_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(in_illuminance_thresh##_nr##_raising_value, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_HIGH, LM3533_ALS_BOUNDARY_MAX)
+
+/* ALS Zone thresholds (boundaries)
+ *
+ * in_illuminance_thresh[0-3]_falling_value	0-255
+ * in_illuminance_thresh[0-3]_raising_value	0-255
+ */
+static ALS_THRESH_FALLING_ATTR_RW(0);
+static ALS_THRESH_FALLING_ATTR_RW(1);
+static ALS_THRESH_FALLING_ATTR_RW(2);
+static ALS_THRESH_FALLING_ATTR_RW(3);
+
+static ALS_THRESH_RAISING_ATTR_RW(0);
+static ALS_THRESH_RAISING_ATTR_RW(1);
+static ALS_THRESH_RAISING_ATTR_RW(2);
+static ALS_THRESH_RAISING_ATTR_RW(3);
+
+#define LM3533_ALS_ATTR_RO(_name) \
+	DEVICE_ATTR(in_illuminance_##_name, S_IRUGO, show_##_name, NULL)
+#define LM3533_ALS_ATTR_RW(_name) \
+	DEVICE_ATTR(in_illuminance_##_name, S_IRUGO | S_IWUSR , \
+						show_##_name, store_##_name)
+
+/* ALS Zone threshold-event enable
+ *
+ * in_illuminance_thresh_either_en		0,1
+ */
+static LM3533_ALS_ATTR_RW(thresh_either_en);
+
+/* ALS Current Zone
+ *
+ * in_illuminance_zone		0-4
+ */
+static LM3533_ALS_ATTR_RO(zone);
+
+#define ALS_TARGET_ATTR_RW(_mapper, _nr) \
+	LM3533_REG_ATTR_RW(target##_mapper##_##_nr, \
+		LM3533_REG_ALS_M##_mapper##_TARGET_##_nr, LM3533_ALS_TARGET_MAX)
+
+/* ALS Mapper targets
+ *
+ * target[1-3]_[0-4]		0-255
+ */
+static ALS_TARGET_ATTR_RW(1, 0);
+static ALS_TARGET_ATTR_RW(1, 1);
+static ALS_TARGET_ATTR_RW(1, 2);
+static ALS_TARGET_ATTR_RW(1, 3);
+static ALS_TARGET_ATTR_RW(1, 4);
+
+static ALS_TARGET_ATTR_RW(2, 0);
+static ALS_TARGET_ATTR_RW(2, 1);
+static ALS_TARGET_ATTR_RW(2, 2);
+static ALS_TARGET_ATTR_RW(2, 3);
+static ALS_TARGET_ATTR_RW(2, 4);
+
+static ALS_TARGET_ATTR_RW(3, 0);
+static ALS_TARGET_ATTR_RW(3, 1);
+static ALS_TARGET_ATTR_RW(3, 2);
+static ALS_TARGET_ATTR_RW(3, 3);
+static ALS_TARGET_ATTR_RW(3, 4);
+
+/* ALS Gain resistor setting
+ *
+ * gain		0-127
+ */
+static LM3533_REG_ATTR_RW(gain, LM3533_REG_ALS_RESISTOR_SELECT,
+						LM3533_ALS_RESISTOR_MAX);
+
+static struct attribute *lm3533_als_event_attributes[] = {
+	&dev_attr_in_illuminance_thresh_either_en.attr,
+	&lm3533_dev_attr_in_illuminance_thresh0_falling_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh0_raising_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh1_falling_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh1_raising_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh2_falling_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh2_raising_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh3_falling_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh3_raising_value.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group lm3533_als_event_attribute_group = {
+	.attrs = lm3533_als_event_attributes
+};
+
+static struct attribute *lm3533_als_attributes[] = {
+	&lm3533_dev_attr_target1_0.dev_attr.attr,
+	&lm3533_dev_attr_target1_1.dev_attr.attr,
+	&lm3533_dev_attr_target1_2.dev_attr.attr,
+	&lm3533_dev_attr_target1_3.dev_attr.attr,
+	&lm3533_dev_attr_target1_4.dev_attr.attr,
+	&lm3533_dev_attr_target2_0.dev_attr.attr,
+	&lm3533_dev_attr_target2_1.dev_attr.attr,
+	&lm3533_dev_attr_target2_2.dev_attr.attr,
+	&lm3533_dev_attr_target2_3.dev_attr.attr,
+	&lm3533_dev_attr_target2_4.dev_attr.attr,
+	&lm3533_dev_attr_target3_0.dev_attr.attr,
+	&lm3533_dev_attr_target3_1.dev_attr.attr,
+	&lm3533_dev_attr_target3_2.dev_attr.attr,
+	&lm3533_dev_attr_target3_3.dev_attr.attr,
+	&lm3533_dev_attr_target3_4.dev_attr.attr,
+	&lm3533_dev_attr_gain.dev_attr.attr,
+	&dev_attr_in_illuminance_zone.attr,
+	NULL
+};
+
+static struct attribute_group lm3533_als_attribute_group = {
+	.attrs = lm3533_als_attributes
+};
+
+static int __devinit lm3533_als_set_input_mode(struct lm3533 *lm3533,
+								int pwm_mode)
+{
+	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
+	u8 val;
+	int ret;
+
+	if (pwm_mode)
+		val = mask;	/* pwm input */
+	else
+		val = 0;	/* analog input */
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret) {
+		dev_err(lm3533->dev,
+				"failed to set input mode %d\n", pwm_mode);
+	}
+
+	return ret;
+}
+
+static int __devinit lm3533_als_enable(struct lm3533 *lm3533)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to enable ALS\n");
+
+	return ret;
+}
+
+static int lm3533_als_disable(struct lm3533 *lm3533)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, 0, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to disable ALS\n");
+
+	return ret;
+}
+
+static const struct iio_info lm3533_als_info = {
+	.attrs		= &lm3533_als_attribute_group,
+	.event_attrs	= &lm3533_als_event_attribute_group,
+	.driver_module	= THIS_MODULE,
+	.read_raw	= &lm3533_als_read_raw,
+};
+
+static int __devinit lm3533_als_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_als_platform_data *pdata;
+	struct lm3533_als *als;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	indio_dev = iio_allocate_device(sizeof(*als));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	indio_dev->info = &lm3533_als_info;
+	indio_dev->channels = lm3533_als_channels;
+	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
+	indio_dev->name = "lm3533-als";
+	indio_dev->dev.parent = pdev->dev.parent;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	als = iio_priv(indio_dev);
+	als->lm3533 = lm3533;
+	als->irq = lm3533->irq;
+	atomic_set(&als->zone, 0);
+
+	if (als->irq) {
+		ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					indio_dev->name, indio_dev);
+		if (ret) {
+			dev_err(&indio_dev->dev, "failed to request irq %d\n",
+								lm3533->irq);
+			goto err_free;
+		}
+	}
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	ret = lm3533_als_set_input_mode(lm3533, pdata->pwm_mode);
+	if (ret)
+		goto err_free;
+
+	ret = lm3533_als_enable(lm3533);
+	if (ret)
+		goto err_free;
+
+	ret = iio_device_register(indio_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register ALS\n");
+		goto err_disable;
+	}
+
+	return 0;
+
+err_disable:
+	lm3533_als_disable(lm3533);
+err_free:
+	iio_free_device(indio_dev);
+
+	return ret;
+}
+
+static int __devexit lm3533_als_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	iio_device_unregister(indio_dev);
+	lm3533_als_disable(als->lm3533);
+	if (als->irq)
+		free_irq(als->irq, indio_dev);
+	iio_free_device(indio_dev);
+
+	return 0;
+}
+
+static struct platform_driver lm3533_als_driver = {
+	.driver = {
+		.name = "lm3533-als",
+		.owner = THIS_MODULE,
+	},
+	.probe		= lm3533_als_probe,
+	.remove		= __devexit_p(lm3533_als_remove),
+};
+module_platform_driver(lm3533_als_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
-- 
1.7.8.5


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

* [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
@ 2012-05-03 10:26     ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:26 UTC (permalink / raw)
  To: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat
  Cc: Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev, Johan Hovold

Add sub-driver for the ambient light sensor interface on National
Semiconductor / TI LM3533 lighting power chips.

The sensor interface can be used to control the LEDs and backlights of
the chip through defining five light zones and three sets of
corresponding brightness target levels.

The driver provides raw and mean adc readings along with the current
light zone through sysfs. A threshold event can be generated on zone
changes.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2:
 - reimplement using iio
 - add sysfs-ABI documentation


 .../Documentation/sysfs-bus-iio-light-lm3533-als   |   62 ++
 drivers/staging/iio/light/Kconfig                  |   16 +
 drivers/staging/iio/light/Makefile                 |    1 +
 drivers/staging/iio/light/lm3533-als.c             |  617 ++++++++++++++++++++
 4 files changed, 696 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
 create mode 100644 drivers/staging/iio/light/lm3533-als.c

diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
new file mode 100644
index 0000000..9849d14
--- /dev/null
+++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
@@ -0,0 +1,62 @@
+What:		/sys/bus/iio/devices/iio:deviceX/gain
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS gain-resistor setting (0..127) for analog input
+		mode, where
+
+		0000000 - ALS input is high impedance
+		0000001 - 200kOhm (10uA at 2V full-scale)
+		0000010 - 100kOhm (20uA at 2V full-scale)
+		...
+		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
+		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
+
+		R_als = 2V / (10uA * gain)	(gain > 0)
+
+What:		/sys/bus/iio/devices/iio:deviceX/illuminance_zone
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the current light zone (0..4) as defined by the
+		in_illuminance_thresh[n]_{falling,rising} thresholds.
+
+What:		/sys/.../events/in_illuminance_thresh_either_en
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Event generated when channel passes one of the four threshold
+		in either direction (rising|falling) and a zone change occurs.
+		The corresponding light zone can be read from
+		illuminance_zone.
+
+What:		/sys/.../events/illuminance_thresh0_falling_value
+What:		/sys/.../events/illuminance_thresh0_raising_value
+What:		/sys/.../events/illuminance_thresh1_falling_value
+What:		/sys/.../events/illuminance_thresh1_raising_value
+What:		/sys/.../events/illuminance_thresh2_falling_value
+What:		/sys/.../events/illuminance_thresh2_raising_value
+What:		/sys/.../events/illuminance_thresh3_falling_value
+What:		/sys/.../events/illuminance_thresh3_raising_value
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Specifies the value of threshold that the device is comparing
+		against for the events enabled by
+		in_illuminance_thresh_either_en, and defines the
+		the five light zones.
+
+		These thresholds correspond to the eight zone-boundary
+		registers (boundary[n]_{low,high}).
+
+What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the target brightness for ALS-mapper m in light zone n
+		(0..255), where m in 1..3 and n in 0..4.
diff --git a/drivers/staging/iio/light/Kconfig b/drivers/staging/iio/light/Kconfig
index e7e9159..263e44a 100644
--- a/drivers/staging/iio/light/Kconfig
+++ b/drivers/staging/iio/light/Kconfig
@@ -24,6 +24,22 @@ config SENSORS_TSL2563
 	 This driver can also be built as a module.  If so, the module
 	 will be called tsl2563.
 
+config SENSORS_LM3533
+	tristate "LM3533 ambient light sensor"
+	depends on MFD_LM3533
+	help
+	  If you say yes here you get support for the ambient light sensor
+	  interface on National Semiconductor / TI LM3533 Lighting Power
+	  chips.
+
+	  The sensor interface can be used to control the LEDs and backlights
+	  of the chip through defining five light zones and three sets of
+	  corresponding brightness target levels.
+
+	  The driver provides raw and mean adc readings along with the current
+	  light zone through sysfs. A threshold event can be generated on zone
+	  changes.
+
 config TSL2583
 	tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters"
 	depends on I2C
diff --git a/drivers/staging/iio/light/Makefile b/drivers/staging/iio/light/Makefile
index 3011fbf..16a60a2 100644
--- a/drivers/staging/iio/light/Makefile
+++ b/drivers/staging/iio/light/Makefile
@@ -4,4 +4,5 @@
 
 obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o
 obj-$(CONFIG_SENSORS_ISL29018)	+= isl29018.o
+obj-$(CONFIG_SENSORS_LM3533)	+= lm3533-als.o
 obj-$(CONFIG_TSL2583)	+= tsl2583.o
diff --git a/drivers/staging/iio/light/lm3533-als.c b/drivers/staging/iio/light/lm3533-als.c
new file mode 100644
index 0000000..e2c9be6
--- /dev/null
+++ b/drivers/staging/iio/light/lm3533-als.c
@@ -0,0 +1,617 @@
+/*
+ * lm3533-als.c -- LM3533 Ambient Light Sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/atomic.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+#include "../events.h"
+#include "../iio.h"
+
+
+#define LM3533_ALS_RESISTOR_MAX			0x7f
+#define LM3533_ALS_ADC_MAX			0xff
+#define LM3533_ALS_BOUNDARY_MAX			LM3533_ALS_ADC_MAX
+#define LM3533_ALS_TARGET_MAX			LM3533_ALS_ADC_MAX
+#define LM3533_ALS_ZONE_MAX			4
+
+#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
+#define LM3533_REG_ALS_CONF			0x31
+#define LM3533_REG_ALS_ZONE_INFO		0x34
+#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x37
+#define LM3533_REG_ALS_READ_ADC_RAW		0x38
+#define LM3533_REG_ALS_BOUNDARY0_HIGH		0x50
+#define LM3533_REG_ALS_BOUNDARY0_LOW		0x51
+#define LM3533_REG_ALS_BOUNDARY1_HIGH		0x52
+#define LM3533_REG_ALS_BOUNDARY1_LOW		0x53
+#define LM3533_REG_ALS_BOUNDARY2_HIGH		0x54
+#define LM3533_REG_ALS_BOUNDARY2_LOW		0x55
+#define LM3533_REG_ALS_BOUNDARY3_HIGH		0x56
+#define LM3533_REG_ALS_BOUNDARY3_LOW		0x57
+#define LM3533_REG_ALS_M1_TARGET_0		0x60
+#define LM3533_REG_ALS_M1_TARGET_1		0x61
+#define LM3533_REG_ALS_M1_TARGET_2		0x62
+#define LM3533_REG_ALS_M1_TARGET_3		0x63
+#define LM3533_REG_ALS_M1_TARGET_4		0x64
+#define LM3533_REG_ALS_M2_TARGET_0		0x65
+#define LM3533_REG_ALS_M2_TARGET_1		0x66
+#define LM3533_REG_ALS_M2_TARGET_2		0x67
+#define LM3533_REG_ALS_M2_TARGET_3		0x68
+#define LM3533_REG_ALS_M2_TARGET_4		0x69
+#define LM3533_REG_ALS_M3_TARGET_0		0x6a
+#define LM3533_REG_ALS_M3_TARGET_1		0x6b
+#define LM3533_REG_ALS_M3_TARGET_2		0x6c
+#define LM3533_REG_ALS_M3_TARGET_3		0x6d
+#define LM3533_REG_ALS_M3_TARGET_4		0x6e
+
+#define LM3533_ALS_ENABLE_MASK			0x01
+#define LM3533_ALS_INPUT_MODE_MASK		0x02
+#define LM3533_ALS_INT_ENABLE_MASK		0x01
+
+#define LM3533_ALS_ZONE_SHIFT			2
+#define LM3533_ALS_ZONE_MASK			0x1c
+
+#define LM3533_ALS_FLAG_INT_ENABLED		1
+
+
+struct lm3533_als {
+	struct lm3533 *lm3533;
+
+	unsigned long flags;
+	int irq;
+
+	atomic_t zone;
+};
+
+
+static int lm3533_als_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val1, int *val2, long mask)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	u8 val;
+	int ret;
+
+	switch (mask) {
+	case 0:
+		reg = LM3533_REG_ALS_READ_ADC_RAW;
+		break;
+	case IIO_CHAN_INFO_AVERAGE_RAW:
+		reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = lm3533_read(als->lm3533, reg, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to read adc\n");
+		return ret;
+	}
+
+	*val1 = val;
+
+	return IIO_VAL_INT;
+}
+
+static const struct iio_chan_spec lm3533_als_channels[] = {
+	{
+		.type = IIO_LIGHT,
+		.info_mask = IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT,
+		.channel = 0,
+	}
+};
+
+static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to read zone\n");
+		return ret;
+	}
+
+	val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
+	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
+
+	return 0;
+}
+
+static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
+{
+
+	struct iio_dev *indio_dev = dev_id;
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 zone;
+	int ret;
+
+	/* Clear interrupt by reading the ALS zone register. */
+	ret = lm3533_als_get_zone(indio_dev, &zone);
+	if (ret)
+		goto out;
+
+	atomic_set(&als->zone, zone);
+
+	iio_push_event(indio_dev,
+		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+					    0,
+					    IIO_EV_TYPE_THRESH,
+					    IIO_EV_DIR_EITHER),
+		       iio_get_time_ns());
+out:
+	return IRQ_HANDLED;
+}
+
+static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+	int ret;
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
+								enable);
+	}
+
+	return ret;
+}
+
+static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to get int mode\n");
+		return ret;
+	}
+
+	*enable = !!(val & mask);
+
+	return 0;
+}
+
+static int show_thresh_either_en(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	int enable;
+	int ret;
+
+	if (als->irq) {
+		ret = lm3533_als_get_int_mode(indio_dev, &enable);
+		if (ret)
+			return ret;
+	} else {
+		enable = 0;
+	}
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
+}
+
+static int store_thresh_either_en(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	unsigned long enable;
+	bool int_enabled;
+	u8 zone;
+	int ret;
+
+	if (!als->irq)
+		return -EBUSY;
+
+	if (kstrtoul(buf, 0, &enable))
+		return -EINVAL;
+
+	int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+	if (enable && !int_enabled) {
+		ret = lm3533_als_get_zone(indio_dev, &zone);
+		if (ret)
+			return ret;
+
+		atomic_set(&als->zone, zone);
+
+		set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+	}
+
+	ret = lm3533_als_set_int_mode(indio_dev, enable);
+	if (ret) {
+		if (!int_enabled)
+			clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+		return ret;
+	}
+
+	if (!enable)
+		clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+	return len;
+}
+
+static ssize_t show_zone(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 zone;
+	int ret;
+
+	if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) {
+		zone = atomic_read(&als->zone);
+	} else {
+		ret = lm3533_als_get_zone(indio_dev, &zone);
+		if (ret)
+			return ret;
+	}
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
+}
+
+struct lm3533_device_attribute {
+	struct device_attribute dev_attr;
+	u8 reg;
+	u8 max;
+};
+
+#define to_lm3533_dev_attr(_dev_attr) \
+	container_of(_dev_attr, struct lm3533_device_attribute, dev_attr)
+
+static ssize_t show_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, lm3533_attr->reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > lm3533_attr->max)
+		return -EINVAL;
+
+	ret = lm3533_write(als->lm3533, lm3533_attr->reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	{ .dev_attr = __ATTR(_name, _mode, _show, _store), \
+	  .reg = _reg, \
+	  .max = _max }
+
+#define LM3533_REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name \
+		= REG_ATTR(_name, _mode, _show, _store, _reg, _max)
+
+#define LM3533_REG_ATTR_RW(_name, _reg, _max) \
+	LM3533_REG_ATTR(_name, S_IRUGO | S_IWUSR, show_lm3533_als_reg, \
+					store_lm3533_als_reg, _reg, _max)
+
+#define ALS_THRESH_FALLING_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(in_illuminance_thresh##_nr##_falling_value, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_LOW, LM3533_ALS_BOUNDARY_MAX)
+
+#define ALS_THRESH_RAISING_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(in_illuminance_thresh##_nr##_raising_value, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_HIGH, LM3533_ALS_BOUNDARY_MAX)
+
+/* ALS Zone thresholds (boundaries)
+ *
+ * in_illuminance_thresh[0-3]_falling_value	0-255
+ * in_illuminance_thresh[0-3]_raising_value	0-255
+ */
+static ALS_THRESH_FALLING_ATTR_RW(0);
+static ALS_THRESH_FALLING_ATTR_RW(1);
+static ALS_THRESH_FALLING_ATTR_RW(2);
+static ALS_THRESH_FALLING_ATTR_RW(3);
+
+static ALS_THRESH_RAISING_ATTR_RW(0);
+static ALS_THRESH_RAISING_ATTR_RW(1);
+static ALS_THRESH_RAISING_ATTR_RW(2);
+static ALS_THRESH_RAISING_ATTR_RW(3);
+
+#define LM3533_ALS_ATTR_RO(_name) \
+	DEVICE_ATTR(in_illuminance_##_name, S_IRUGO, show_##_name, NULL)
+#define LM3533_ALS_ATTR_RW(_name) \
+	DEVICE_ATTR(in_illuminance_##_name, S_IRUGO | S_IWUSR , \
+						show_##_name, store_##_name)
+
+/* ALS Zone threshold-event enable
+ *
+ * in_illuminance_thresh_either_en		0,1
+ */
+static LM3533_ALS_ATTR_RW(thresh_either_en);
+
+/* ALS Current Zone
+ *
+ * in_illuminance_zone		0-4
+ */
+static LM3533_ALS_ATTR_RO(zone);
+
+#define ALS_TARGET_ATTR_RW(_mapper, _nr) \
+	LM3533_REG_ATTR_RW(target##_mapper##_##_nr, \
+		LM3533_REG_ALS_M##_mapper##_TARGET_##_nr, LM3533_ALS_TARGET_MAX)
+
+/* ALS Mapper targets
+ *
+ * target[1-3]_[0-4]		0-255
+ */
+static ALS_TARGET_ATTR_RW(1, 0);
+static ALS_TARGET_ATTR_RW(1, 1);
+static ALS_TARGET_ATTR_RW(1, 2);
+static ALS_TARGET_ATTR_RW(1, 3);
+static ALS_TARGET_ATTR_RW(1, 4);
+
+static ALS_TARGET_ATTR_RW(2, 0);
+static ALS_TARGET_ATTR_RW(2, 1);
+static ALS_TARGET_ATTR_RW(2, 2);
+static ALS_TARGET_ATTR_RW(2, 3);
+static ALS_TARGET_ATTR_RW(2, 4);
+
+static ALS_TARGET_ATTR_RW(3, 0);
+static ALS_TARGET_ATTR_RW(3, 1);
+static ALS_TARGET_ATTR_RW(3, 2);
+static ALS_TARGET_ATTR_RW(3, 3);
+static ALS_TARGET_ATTR_RW(3, 4);
+
+/* ALS Gain resistor setting
+ *
+ * gain		0-127
+ */
+static LM3533_REG_ATTR_RW(gain, LM3533_REG_ALS_RESISTOR_SELECT,
+						LM3533_ALS_RESISTOR_MAX);
+
+static struct attribute *lm3533_als_event_attributes[] = {
+	&dev_attr_in_illuminance_thresh_either_en.attr,
+	&lm3533_dev_attr_in_illuminance_thresh0_falling_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh0_raising_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh1_falling_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh1_raising_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh2_falling_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh2_raising_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh3_falling_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance_thresh3_raising_value.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group lm3533_als_event_attribute_group = {
+	.attrs = lm3533_als_event_attributes
+};
+
+static struct attribute *lm3533_als_attributes[] = {
+	&lm3533_dev_attr_target1_0.dev_attr.attr,
+	&lm3533_dev_attr_target1_1.dev_attr.attr,
+	&lm3533_dev_attr_target1_2.dev_attr.attr,
+	&lm3533_dev_attr_target1_3.dev_attr.attr,
+	&lm3533_dev_attr_target1_4.dev_attr.attr,
+	&lm3533_dev_attr_target2_0.dev_attr.attr,
+	&lm3533_dev_attr_target2_1.dev_attr.attr,
+	&lm3533_dev_attr_target2_2.dev_attr.attr,
+	&lm3533_dev_attr_target2_3.dev_attr.attr,
+	&lm3533_dev_attr_target2_4.dev_attr.attr,
+	&lm3533_dev_attr_target3_0.dev_attr.attr,
+	&lm3533_dev_attr_target3_1.dev_attr.attr,
+	&lm3533_dev_attr_target3_2.dev_attr.attr,
+	&lm3533_dev_attr_target3_3.dev_attr.attr,
+	&lm3533_dev_attr_target3_4.dev_attr.attr,
+	&lm3533_dev_attr_gain.dev_attr.attr,
+	&dev_attr_in_illuminance_zone.attr,
+	NULL
+};
+
+static struct attribute_group lm3533_als_attribute_group = {
+	.attrs = lm3533_als_attributes
+};
+
+static int __devinit lm3533_als_set_input_mode(struct lm3533 *lm3533,
+								int pwm_mode)
+{
+	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
+	u8 val;
+	int ret;
+
+	if (pwm_mode)
+		val = mask;	/* pwm input */
+	else
+		val = 0;	/* analog input */
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret) {
+		dev_err(lm3533->dev,
+				"failed to set input mode %d\n", pwm_mode);
+	}
+
+	return ret;
+}
+
+static int __devinit lm3533_als_enable(struct lm3533 *lm3533)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to enable ALS\n");
+
+	return ret;
+}
+
+static int lm3533_als_disable(struct lm3533 *lm3533)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, 0, mask);
+	if (ret)
+		dev_err(lm3533->dev, "failed to disable ALS\n");
+
+	return ret;
+}
+
+static const struct iio_info lm3533_als_info = {
+	.attrs		= &lm3533_als_attribute_group,
+	.event_attrs	= &lm3533_als_event_attribute_group,
+	.driver_module	= THIS_MODULE,
+	.read_raw	= &lm3533_als_read_raw,
+};
+
+static int __devinit lm3533_als_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_als_platform_data *pdata;
+	struct lm3533_als *als;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	indio_dev = iio_allocate_device(sizeof(*als));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	indio_dev->info = &lm3533_als_info;
+	indio_dev->channels = lm3533_als_channels;
+	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
+	indio_dev->name = "lm3533-als";
+	indio_dev->dev.parent = pdev->dev.parent;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	als = iio_priv(indio_dev);
+	als->lm3533 = lm3533;
+	als->irq = lm3533->irq;
+	atomic_set(&als->zone, 0);
+
+	if (als->irq) {
+		ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					indio_dev->name, indio_dev);
+		if (ret) {
+			dev_err(&indio_dev->dev, "failed to request irq %d\n",
+								lm3533->irq);
+			goto err_free;
+		}
+	}
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	ret = lm3533_als_set_input_mode(lm3533, pdata->pwm_mode);
+	if (ret)
+		goto err_free;
+
+	ret = lm3533_als_enable(lm3533);
+	if (ret)
+		goto err_free;
+
+	ret = iio_device_register(indio_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register ALS\n");
+		goto err_disable;
+	}
+
+	return 0;
+
+err_disable:
+	lm3533_als_disable(lm3533);
+err_free:
+	iio_free_device(indio_dev);
+
+	return ret;
+}
+
+static int __devexit lm3533_als_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	iio_device_unregister(indio_dev);
+	lm3533_als_disable(als->lm3533);
+	if (als->irq)
+		free_irq(als->irq, indio_dev);
+	iio_free_device(indio_dev);
+
+	return 0;
+}
+
+static struct platform_driver lm3533_als_driver = {
+	.driver = {
+		.name = "lm3533-als",
+		.owner = THIS_MODULE,
+	},
+	.probe		= lm3533_als_probe,
+	.remove		= __devexit_p(lm3533_als_remove),
+};
+module_platform_driver(lm3533_als_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
-- 
1.7.8.5


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

* [PATCH v2 3/4] leds: add LM3533 LED driver
  2012-05-03 10:26   ` Johan Hovold
@ 2012-05-03 10:26     ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:26 UTC (permalink / raw)
  To: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat
  Cc: Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev, Johan Hovold

Add sub-driver for the LEDs on National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels, hardware accelerated blinking
as well as ambient-light-sensor and pwm input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2:
 - add sysfs-ABI documentation
 - open code max_current/pwm macros


 .../ABI/testing/sysfs-class-led-driver-lm3533      |   67 ++
 drivers/leds/Kconfig                               |   13 +
 drivers/leds/Makefile                              |    1 +
 drivers/leds/leds-lm3533.c                         |  741 ++++++++++++++++++++
 4 files changed, 822 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-led-driver-lm3533
 create mode 100644 drivers/leds/leds-lm3533.c

diff --git a/Documentation/ABI/testing/sysfs-class-led-driver-lm3533 b/Documentation/ABI/testing/sysfs-class-led-driver-lm3533
new file mode 100644
index 0000000..fc1ee04
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-led-driver-lm3533
@@ -0,0 +1,67 @@
+What:		/sys/class/leds/<led>/als
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS-control mode (0, 2, 3), where
+
+		0 - disabled
+		2 - ALS-mapper 2
+		3 - ALS-mapper 3
+
+What:		/sys/class/leds/<led>/falltime
+What:		/sys/class/leds/<led>/risetime
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the pattern generator fall and rise times (0..7), where
+
+		0 - 2048 us
+		1 - 262 ms
+		2 - 524 ms
+		3 - 1.049 s
+		4 - 2.097 s
+		5 - 4.194 s
+		6 - 8.389 s
+		7 - 16.78 s
+
+What:		/sys/class/leds/<led>/id
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the id of this led (0..3).
+
+What:		/sys/class/leds/<led>/linear
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the brightness-mapping mode (0, 1), where
+
+		0 - exponential mode
+		1 - linear mode
+
+What:		/sys/class/leds/<led>/max_current
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the full-scale current I_{LED_FULLSCALE} (0..31), where
+
+		I_{LED_FULLSCALE} = 5mA + max_current * 0.8mA
+
+What:		/sys/class/leds/<led>/pwm
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the PWM-input control mask (5 bits), where
+
+		bit 5 - PWM-input enabled in Zone 4
+		bit 4 - PWM-input enabled in Zone 3
+		bit 3 - PWM-input enabled in Zone 2
+		bit 2 - PWM-input enabled in Zone 1
+		bit 1 - PWM-input enabled in Zone 0
+		bit 0 - PWM-input enabled
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index ff4b8cf..19bd829 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -50,6 +50,19 @@ config LEDS_LM3530
 	  controlled manually or using PWM input or using ambient
 	  light automatically.
 
+config LEDS_LM3533
+	tristate "LED support for LM3533"
+	depends on LEDS_CLASS
+	depends on MFD_LM3533
+	help
+	  This option enables support for the LEDs on National Semiconductor /
+	  TI LM3533 Lighting Power chips.
+
+	  The LEDs can be controlled directly, through PWM input, or by the
+	  ambient-light-sensor interface. The chip supports
+	  hardware-accelerated blinking with maximum on and off periods of 9.8
+	  and 77 seconds respectively.
+
 config LEDS_LOCOMO
 	tristate "LED Support for Locomo device"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 890481c..f39a526 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_ATMEL_PWM)		+= leds-atmel-pwm.o
 obj-$(CONFIG_LEDS_BD2802)		+= leds-bd2802.o
 obj-$(CONFIG_LEDS_LOCOMO)		+= leds-locomo.o
 obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
+obj-$(CONFIG_LEDS_LM3533)		+= leds-lm3533.o
 obj-$(CONFIG_LEDS_MIKROTIK_RB532)	+= leds-rb532.o
 obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
 obj-$(CONFIG_LEDS_NET48XX)		+= leds-net48xx.o
diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c
new file mode 100644
index 0000000..7d02f4b
--- /dev/null
+++ b/drivers/leds/leds-lm3533.c
@@ -0,0 +1,741 @@
+/*
+ * leds-lm3533.c -- LM3533 LED driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/mfd/core.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_LVCTRLBANK_MIN		2
+#define LM3533_LVCTRLBANK_MAX		5
+#define LM3533_LVCTRLBANK_COUNT		4
+#define LM3533_RISEFALLTIME_MAX		7
+#define LM3533_ALS_LV_MIN		2
+#define LM3533_ALS_LV_MAX		3
+
+#define LM3533_REG_CTRLBANK_BCONF_BASE		0x1b
+#define LM3533_REG_PATTERN_ENABLE		0x28
+#define LM3533_REG_PATTERN_LOW_TIME_BASE	0x71
+#define LM3533_REG_PATTERN_HIGH_TIME_BASE	0x72
+#define LM3533_REG_PATTERN_RISETIME_BASE	0x74
+#define LM3533_REG_PATTERN_FALLTIME_BASE	0x75
+
+#define LM3533_REG_PATTERN_STEP			0x10
+
+#define LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK	0x04
+#define LM3533_REG_CTRLBANK_BCONF_ALS_MASK	0x03
+
+#define LM3533_LED_FLAG_PATTERN_ENABLE		1
+
+
+struct lm3533_led {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct led_classdev cdev;
+	int id;
+
+	struct mutex mutex;
+	unsigned long flags;
+
+	struct work_struct work;
+	u8 new_brightness;
+};
+
+#define to_lm3533_led(_cdev) \
+	container_of(_cdev, struct lm3533_led, cdev)
+
+
+static inline int lm3533_led_get_ctrlbank_id(struct lm3533_led *led)
+{
+	return led->id + 2;
+}
+
+static inline u8 lm3533_led_get_lv_reg(struct lm3533_led *led, u8 base)
+{
+	return base + led->id;
+}
+
+static inline u8 lm3533_led_get_pattern(struct lm3533_led *led)
+{
+	return led->id;
+}
+
+static inline u8 lm3533_led_get_pattern_reg(struct lm3533_led *led,
+								u8 base)
+{
+	return base + lm3533_led_get_pattern(led) * LM3533_REG_PATTERN_STEP;
+}
+
+static int lm3533_led_pattern_enable(struct lm3533_led *led, int enable)
+{
+	u8 mask;
+	u8 val;
+	int pattern;
+	int state;
+	int ret = 0;
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, enable);
+
+	mutex_lock(&led->mutex);
+
+	state = test_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+	if ((enable && state) || (!enable && !state))
+		goto out;
+
+	pattern = lm3533_led_get_pattern(led);
+	mask = 1 << (2 * pattern);
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, LM3533_REG_PATTERN_ENABLE, val, mask);
+	if (ret) {
+		dev_err(led->cdev.dev, "failed to enable pattern %d (%d)\n",
+							pattern, enable);
+		goto out;
+	}
+
+	__change_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+out:
+	mutex_unlock(&led->mutex);
+
+	return ret;
+}
+
+static void lm3533_led_work(struct work_struct *work)
+{
+	struct lm3533_led *led = container_of(work, struct lm3533_led, work);
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, led->new_brightness);
+
+	if (led->new_brightness == 0)
+		lm3533_led_pattern_enable(led, 0);	/* disable blink */
+
+	lm3533_ctrlbank_set_brightness(&led->cb, led->new_brightness);
+}
+
+static void lm3533_led_set(struct led_classdev *cdev,
+						enum led_brightness value)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, value);
+
+	led->new_brightness = value;
+	schedule_work(&led->work);
+}
+
+static enum led_brightness lm3533_led_get(struct led_classdev *cdev)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&led->cb, &val);
+	if (ret)
+		return ret;
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, val);
+
+	return val;
+}
+
+/* Pattern generator defines -- delays in us */
+#define LM3533_LED_DELAY_GROUP1_BASE	0x00
+#define LM3533_LED_DELAY_GROUP2_BASE	0x3d
+#define LM3533_LED_DELAY_GROUP3_BASE	0x80
+#define LM3533_LED_DELAY_MAX		0xff
+
+#define LM3533_LED_DELAY_GROUP1_STEP	16384
+#define LM3533_LED_DELAY_GROUP2_STEP	131072
+#define LM3533_LED_DELAY_GROUP3_STEP	524288
+#define LM3533_LED_DELAY_GROUP1_MIN	16384
+#define LM3533_LED_DELAY_GROUP2_MIN	1130496
+#define LM3533_LED_DELAY_GROUP3_MIN	10305536
+#define LM3533_LED_DELAY_GROUP1_MAX	999424
+#define LM3533_LED_DELAY_GROUP2_MAX	9781248
+#define LM3533_LED_DELAY_GROUP3_MAX	76890112
+
+/* Delay limits in ms */
+#define LM3533_LED_DELAY_ON_MAX		9845
+#define LM3533_LED_DELAY_OFF_MAX	77140
+
+static int time_to_val(long *t, long t_min, long t_max, long t_step,
+							int v_min, int v_max)
+{
+	int val;
+
+	*t += t_step / 2;
+	val = (*t - t_min) / t_step + v_min;
+	val = clamp(val, v_min, v_max);
+	*t = t_step * (val - v_min) + t_min;
+
+	return val;
+}
+
+static int lm3533_led_get_delay(long *delay)
+{
+	int val;
+
+	*delay *= 1000;
+
+	if (*delay >= LM3533_LED_DELAY_GROUP3_MIN -
+					LM3533_LED_DELAY_GROUP3_STEP / 2) {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP3_MIN,
+					LM3533_LED_DELAY_GROUP3_MAX,
+					LM3533_LED_DELAY_GROUP3_STEP,
+					LM3533_LED_DELAY_GROUP3_BASE,
+					0xff);
+	} else if (*delay >= LM3533_LED_DELAY_GROUP2_MIN -
+					LM3533_LED_DELAY_GROUP2_STEP / 2) {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP2_MIN,
+					LM3533_LED_DELAY_GROUP2_MAX,
+					LM3533_LED_DELAY_GROUP2_STEP,
+					LM3533_LED_DELAY_GROUP2_BASE,
+					LM3533_LED_DELAY_GROUP3_BASE - 1);
+	} else {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP1_MIN,
+					LM3533_LED_DELAY_GROUP1_MAX,
+					LM3533_LED_DELAY_GROUP1_STEP,
+					LM3533_LED_DELAY_GROUP1_BASE,
+					LM3533_LED_DELAY_GROUP2_BASE - 1);
+	}
+
+	*delay /= 1000;
+
+	return val;
+}
+
+static int lm3533_led_delay_set(struct lm3533_led *led, u8 base,
+							unsigned long *delay)
+{
+	u8 val;
+	u8 reg;
+	long t;
+	int ret;
+
+	t = *delay;
+	val = lm3533_led_get_delay(&t);
+
+	dev_dbg(led->cdev.dev, "%s - %lu: %ld (0x%02x)\n", __func__,
+							*delay, t, val);
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
+
+	*delay = t;
+
+	return ret;
+}
+
+static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
+{
+	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP2_MAX / 1000);
+
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
+}
+
+static int lm3533_led_delay_off_set(struct lm3533_led *led, unsigned long *t)
+{
+	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP3_MAX / 1000);
+
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_LOW_TIME_BASE, t);
+}
+
+static int lm3533_led_blink_set(struct led_classdev *cdev,
+				unsigned long *delay_on,
+				unsigned long *delay_off)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	int ret;
+
+	dev_dbg(led->cdev.dev, "%s - on = %lu, off = %lu\n", __func__,
+							*delay_on, *delay_off);
+
+	if (*delay_on > LM3533_LED_DELAY_ON_MAX ||
+					*delay_off > LM3533_LED_DELAY_OFF_MAX)
+		return -EINVAL;
+
+	if (*delay_on == 0 && *delay_off == 0) {
+		*delay_on = 500;
+		*delay_off = 500;
+	}
+
+	ret = lm3533_led_delay_on_set(led, delay_on);
+	if (ret)
+		return ret;
+
+	ret = lm3533_led_delay_off_set(led, delay_off);
+	if (ret)
+		return ret;
+
+	return lm3533_led_pattern_enable(led, 1);
+}
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", led->id);
+}
+
+/*
+ * Pattern generator rise/fall times:
+ *
+ *   0 - 2048 us (default)
+ *   1 - 262 ms
+ *   2 - 524 ms
+ *   3 - 1.049 s
+ *   4 - 2.097 s
+ *   5 - 4.194 s
+ *   6 - 8.389 s
+ *   7 - 16.78 s
+ */
+static ssize_t show_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					char *buf, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	ssize_t ret;
+	u8 reg;
+	u8 val;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", val);
+}
+
+static ssize_t show_risetime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t show_falltime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+static ssize_t store_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	u8 reg;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > LM3533_RISEFALLTIME_MAX)
+		return -EINVAL;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t store_risetime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t store_falltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+/*
+ * ALS-control setting:
+ *
+ *   0 - ALS disabled
+ *   2 - ALS-mapper 2
+ *   3 - ALS-mapper 3
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int als;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	als = val & LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 als;
+	u8 reg;
+	u8 mask;
+	int ret;
+
+	if (kstrtou8(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	ret = lm3533_update(led->lm3533, reg, als, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int linear;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	if (val & LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	unsigned long linear;
+	u8 reg;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK;
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, reg, val, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_max_current(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_max_current(&led->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_max_current(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_max_current(&led->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_pwm(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_pwm(&led->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_pwm(&led->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RW(falltime);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(max_current);
+static LM3533_ATTR_RW(pwm);
+static LM3533_ATTR_RW(risetime);
+
+static struct attribute *lm3533_led_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_falltime.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_max_current.attr,
+	&dev_attr_pwm.attr,
+	&dev_attr_risetime.attr,
+	NULL,
+};
+
+static mode_t lm3533_led_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	mode_t mode = attr->mode;
+
+	if (attr == &dev_attr_als.attr) {
+		if (!led->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_led_attribute_group = {
+	.is_visible	= lm3533_led_attr_is_visible,
+	.attrs		= lm3533_led_attributes
+};
+
+static int __devinit lm3533_led_setup(struct lm3533_led *led,
+					struct lm3533_led_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&led->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&led->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_led_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_led_platform_data *pdata;
+	struct lm3533_led *led;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_LVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal LED id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	led = kzalloc(sizeof(*led), GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	led->lm3533 = lm3533;
+	led->cdev.name = pdata->name;
+	led->cdev.default_trigger = pdata->default_trigger;
+	led->cdev.brightness_set = lm3533_led_set;
+	led->cdev.brightness_get = lm3533_led_get;
+	led->cdev.blink_set = lm3533_led_blink_set;
+	led->cdev.brightness = LED_OFF;
+	led->id = pdev->id;
+
+	mutex_init(&led->mutex);
+	INIT_WORK(&led->work, lm3533_led_work);
+
+	/* The class framework makes a callback to get brightness during
+	 * registration so use parent device (for error reporting) until
+	 * registered.
+	 */
+	led->cb.lm3533 = lm3533;
+	led->cb.id = lm3533_led_get_ctrlbank_id(led);
+	led->cb.dev = lm3533->dev;
+
+	platform_set_drvdata(pdev, led);
+
+	ret = led_classdev_register(pdev->dev.parent, &led->cdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id);
+		goto err_free;
+	}
+
+	led->cb.dev = led->cdev.dev;
+
+	ret = sysfs_create_group(&led->cdev.dev->kobj,
+						&lm3533_led_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	ret = lm3533_led_setup(led, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&led->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+err_unregister:
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+err_free:
+	kfree(led);
+
+	return ret;
+}
+
+static int __devexit lm3533_led_remove(struct platform_device *pdev)
+{
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+	kfree(led);
+
+	return 0;
+}
+
+static void lm3533_led_shutdown(struct platform_device *pdev)
+{
+
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	lm3533_led_set(&led->cdev, LED_OFF);		/* disable blink */
+	flush_work_sync(&led->work);
+}
+
+static struct platform_driver lm3533_led_driver = {
+	.driver = {
+		.name = "lm3533-leds",
+		.owner = THIS_MODULE,
+	},
+	.probe		= lm3533_led_probe,
+	.remove		= __devexit_p(lm3533_led_remove),
+	.shutdown	= lm3533_led_shutdown,
+};
+module_platform_driver(lm3533_led_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-leds");
-- 
1.7.8.5


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

* [PATCH v2 3/4] leds: add LM3533 LED driver
@ 2012-05-03 10:26     ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:26 UTC (permalink / raw)
  To: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat
  Cc: Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev, Johan Hovold

Add sub-driver for the LEDs on National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels, hardware accelerated blinking
as well as ambient-light-sensor and pwm input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2:
 - add sysfs-ABI documentation
 - open code max_current/pwm macros


 .../ABI/testing/sysfs-class-led-driver-lm3533      |   67 ++
 drivers/leds/Kconfig                               |   13 +
 drivers/leds/Makefile                              |    1 +
 drivers/leds/leds-lm3533.c                         |  741 ++++++++++++++++++++
 4 files changed, 822 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-led-driver-lm3533
 create mode 100644 drivers/leds/leds-lm3533.c

diff --git a/Documentation/ABI/testing/sysfs-class-led-driver-lm3533 b/Documentation/ABI/testing/sysfs-class-led-driver-lm3533
new file mode 100644
index 0000000..fc1ee04
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-led-driver-lm3533
@@ -0,0 +1,67 @@
+What:		/sys/class/leds/<led>/als
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS-control mode (0, 2, 3), where
+
+		0 - disabled
+		2 - ALS-mapper 2
+		3 - ALS-mapper 3
+
+What:		/sys/class/leds/<led>/falltime
+What:		/sys/class/leds/<led>/risetime
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the pattern generator fall and rise times (0..7), where
+
+		0 - 2048 us
+		1 - 262 ms
+		2 - 524 ms
+		3 - 1.049 s
+		4 - 2.097 s
+		5 - 4.194 s
+		6 - 8.389 s
+		7 - 16.78 s
+
+What:		/sys/class/leds/<led>/id
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the id of this led (0..3).
+
+What:		/sys/class/leds/<led>/linear
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the brightness-mapping mode (0, 1), where
+
+		0 - exponential mode
+		1 - linear mode
+
+What:		/sys/class/leds/<led>/max_current
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the full-scale current I_{LED_FULLSCALE} (0..31), where
+
+		I_{LED_FULLSCALE} = 5mA + max_current * 0.8mA
+
+What:		/sys/class/leds/<led>/pwm
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the PWM-input control mask (5 bits), where
+
+		bit 5 - PWM-input enabled in Zone 4
+		bit 4 - PWM-input enabled in Zone 3
+		bit 3 - PWM-input enabled in Zone 2
+		bit 2 - PWM-input enabled in Zone 1
+		bit 1 - PWM-input enabled in Zone 0
+		bit 0 - PWM-input enabled
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index ff4b8cf..19bd829 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -50,6 +50,19 @@ config LEDS_LM3530
 	  controlled manually or using PWM input or using ambient
 	  light automatically.
 
+config LEDS_LM3533
+	tristate "LED support for LM3533"
+	depends on LEDS_CLASS
+	depends on MFD_LM3533
+	help
+	  This option enables support for the LEDs on National Semiconductor /
+	  TI LM3533 Lighting Power chips.
+
+	  The LEDs can be controlled directly, through PWM input, or by the
+	  ambient-light-sensor interface. The chip supports
+	  hardware-accelerated blinking with maximum on and off periods of 9.8
+	  and 77 seconds respectively.
+
 config LEDS_LOCOMO
 	tristate "LED Support for Locomo device"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 890481c..f39a526 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_ATMEL_PWM)		+= leds-atmel-pwm.o
 obj-$(CONFIG_LEDS_BD2802)		+= leds-bd2802.o
 obj-$(CONFIG_LEDS_LOCOMO)		+= leds-locomo.o
 obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
+obj-$(CONFIG_LEDS_LM3533)		+= leds-lm3533.o
 obj-$(CONFIG_LEDS_MIKROTIK_RB532)	+= leds-rb532.o
 obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
 obj-$(CONFIG_LEDS_NET48XX)		+= leds-net48xx.o
diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c
new file mode 100644
index 0000000..7d02f4b
--- /dev/null
+++ b/drivers/leds/leds-lm3533.c
@@ -0,0 +1,741 @@
+/*
+ * leds-lm3533.c -- LM3533 LED driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/mfd/core.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_LVCTRLBANK_MIN		2
+#define LM3533_LVCTRLBANK_MAX		5
+#define LM3533_LVCTRLBANK_COUNT		4
+#define LM3533_RISEFALLTIME_MAX		7
+#define LM3533_ALS_LV_MIN		2
+#define LM3533_ALS_LV_MAX		3
+
+#define LM3533_REG_CTRLBANK_BCONF_BASE		0x1b
+#define LM3533_REG_PATTERN_ENABLE		0x28
+#define LM3533_REG_PATTERN_LOW_TIME_BASE	0x71
+#define LM3533_REG_PATTERN_HIGH_TIME_BASE	0x72
+#define LM3533_REG_PATTERN_RISETIME_BASE	0x74
+#define LM3533_REG_PATTERN_FALLTIME_BASE	0x75
+
+#define LM3533_REG_PATTERN_STEP			0x10
+
+#define LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK	0x04
+#define LM3533_REG_CTRLBANK_BCONF_ALS_MASK	0x03
+
+#define LM3533_LED_FLAG_PATTERN_ENABLE		1
+
+
+struct lm3533_led {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct led_classdev cdev;
+	int id;
+
+	struct mutex mutex;
+	unsigned long flags;
+
+	struct work_struct work;
+	u8 new_brightness;
+};
+
+#define to_lm3533_led(_cdev) \
+	container_of(_cdev, struct lm3533_led, cdev)
+
+
+static inline int lm3533_led_get_ctrlbank_id(struct lm3533_led *led)
+{
+	return led->id + 2;
+}
+
+static inline u8 lm3533_led_get_lv_reg(struct lm3533_led *led, u8 base)
+{
+	return base + led->id;
+}
+
+static inline u8 lm3533_led_get_pattern(struct lm3533_led *led)
+{
+	return led->id;
+}
+
+static inline u8 lm3533_led_get_pattern_reg(struct lm3533_led *led,
+								u8 base)
+{
+	return base + lm3533_led_get_pattern(led) * LM3533_REG_PATTERN_STEP;
+}
+
+static int lm3533_led_pattern_enable(struct lm3533_led *led, int enable)
+{
+	u8 mask;
+	u8 val;
+	int pattern;
+	int state;
+	int ret = 0;
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, enable);
+
+	mutex_lock(&led->mutex);
+
+	state = test_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+	if ((enable && state) || (!enable && !state))
+		goto out;
+
+	pattern = lm3533_led_get_pattern(led);
+	mask = 1 << (2 * pattern);
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, LM3533_REG_PATTERN_ENABLE, val, mask);
+	if (ret) {
+		dev_err(led->cdev.dev, "failed to enable pattern %d (%d)\n",
+							pattern, enable);
+		goto out;
+	}
+
+	__change_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+out:
+	mutex_unlock(&led->mutex);
+
+	return ret;
+}
+
+static void lm3533_led_work(struct work_struct *work)
+{
+	struct lm3533_led *led = container_of(work, struct lm3533_led, work);
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, led->new_brightness);
+
+	if (led->new_brightness = 0)
+		lm3533_led_pattern_enable(led, 0);	/* disable blink */
+
+	lm3533_ctrlbank_set_brightness(&led->cb, led->new_brightness);
+}
+
+static void lm3533_led_set(struct led_classdev *cdev,
+						enum led_brightness value)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, value);
+
+	led->new_brightness = value;
+	schedule_work(&led->work);
+}
+
+static enum led_brightness lm3533_led_get(struct led_classdev *cdev)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&led->cb, &val);
+	if (ret)
+		return ret;
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, val);
+
+	return val;
+}
+
+/* Pattern generator defines -- delays in us */
+#define LM3533_LED_DELAY_GROUP1_BASE	0x00
+#define LM3533_LED_DELAY_GROUP2_BASE	0x3d
+#define LM3533_LED_DELAY_GROUP3_BASE	0x80
+#define LM3533_LED_DELAY_MAX		0xff
+
+#define LM3533_LED_DELAY_GROUP1_STEP	16384
+#define LM3533_LED_DELAY_GROUP2_STEP	131072
+#define LM3533_LED_DELAY_GROUP3_STEP	524288
+#define LM3533_LED_DELAY_GROUP1_MIN	16384
+#define LM3533_LED_DELAY_GROUP2_MIN	1130496
+#define LM3533_LED_DELAY_GROUP3_MIN	10305536
+#define LM3533_LED_DELAY_GROUP1_MAX	999424
+#define LM3533_LED_DELAY_GROUP2_MAX	9781248
+#define LM3533_LED_DELAY_GROUP3_MAX	76890112
+
+/* Delay limits in ms */
+#define LM3533_LED_DELAY_ON_MAX		9845
+#define LM3533_LED_DELAY_OFF_MAX	77140
+
+static int time_to_val(long *t, long t_min, long t_max, long t_step,
+							int v_min, int v_max)
+{
+	int val;
+
+	*t += t_step / 2;
+	val = (*t - t_min) / t_step + v_min;
+	val = clamp(val, v_min, v_max);
+	*t = t_step * (val - v_min) + t_min;
+
+	return val;
+}
+
+static int lm3533_led_get_delay(long *delay)
+{
+	int val;
+
+	*delay *= 1000;
+
+	if (*delay >= LM3533_LED_DELAY_GROUP3_MIN -
+					LM3533_LED_DELAY_GROUP3_STEP / 2) {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP3_MIN,
+					LM3533_LED_DELAY_GROUP3_MAX,
+					LM3533_LED_DELAY_GROUP3_STEP,
+					LM3533_LED_DELAY_GROUP3_BASE,
+					0xff);
+	} else if (*delay >= LM3533_LED_DELAY_GROUP2_MIN -
+					LM3533_LED_DELAY_GROUP2_STEP / 2) {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP2_MIN,
+					LM3533_LED_DELAY_GROUP2_MAX,
+					LM3533_LED_DELAY_GROUP2_STEP,
+					LM3533_LED_DELAY_GROUP2_BASE,
+					LM3533_LED_DELAY_GROUP3_BASE - 1);
+	} else {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP1_MIN,
+					LM3533_LED_DELAY_GROUP1_MAX,
+					LM3533_LED_DELAY_GROUP1_STEP,
+					LM3533_LED_DELAY_GROUP1_BASE,
+					LM3533_LED_DELAY_GROUP2_BASE - 1);
+	}
+
+	*delay /= 1000;
+
+	return val;
+}
+
+static int lm3533_led_delay_set(struct lm3533_led *led, u8 base,
+							unsigned long *delay)
+{
+	u8 val;
+	u8 reg;
+	long t;
+	int ret;
+
+	t = *delay;
+	val = lm3533_led_get_delay(&t);
+
+	dev_dbg(led->cdev.dev, "%s - %lu: %ld (0x%02x)\n", __func__,
+							*delay, t, val);
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
+
+	*delay = t;
+
+	return ret;
+}
+
+static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
+{
+	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP2_MAX / 1000);
+
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
+}
+
+static int lm3533_led_delay_off_set(struct lm3533_led *led, unsigned long *t)
+{
+	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP3_MAX / 1000);
+
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_LOW_TIME_BASE, t);
+}
+
+static int lm3533_led_blink_set(struct led_classdev *cdev,
+				unsigned long *delay_on,
+				unsigned long *delay_off)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	int ret;
+
+	dev_dbg(led->cdev.dev, "%s - on = %lu, off = %lu\n", __func__,
+							*delay_on, *delay_off);
+
+	if (*delay_on > LM3533_LED_DELAY_ON_MAX ||
+					*delay_off > LM3533_LED_DELAY_OFF_MAX)
+		return -EINVAL;
+
+	if (*delay_on = 0 && *delay_off = 0) {
+		*delay_on = 500;
+		*delay_off = 500;
+	}
+
+	ret = lm3533_led_delay_on_set(led, delay_on);
+	if (ret)
+		return ret;
+
+	ret = lm3533_led_delay_off_set(led, delay_off);
+	if (ret)
+		return ret;
+
+	return lm3533_led_pattern_enable(led, 1);
+}
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", led->id);
+}
+
+/*
+ * Pattern generator rise/fall times:
+ *
+ *   0 - 2048 us (default)
+ *   1 - 262 ms
+ *   2 - 524 ms
+ *   3 - 1.049 s
+ *   4 - 2.097 s
+ *   5 - 4.194 s
+ *   6 - 8.389 s
+ *   7 - 16.78 s
+ */
+static ssize_t show_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					char *buf, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	ssize_t ret;
+	u8 reg;
+	u8 val;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", val);
+}
+
+static ssize_t show_risetime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t show_falltime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+static ssize_t store_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	u8 reg;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > LM3533_RISEFALLTIME_MAX)
+		return -EINVAL;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t store_risetime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t store_falltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+/*
+ * ALS-control setting:
+ *
+ *   0 - ALS disabled
+ *   2 - ALS-mapper 2
+ *   3 - ALS-mapper 3
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int als;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	als = val & LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 als;
+	u8 reg;
+	u8 mask;
+	int ret;
+
+	if (kstrtou8(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	ret = lm3533_update(led->lm3533, reg, als, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int linear;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	if (val & LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	unsigned long linear;
+	u8 reg;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK;
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, reg, val, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_max_current(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_max_current(&led->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_max_current(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_max_current(&led->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_pwm(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_pwm(&led->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_pwm(&led->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RW(falltime);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(max_current);
+static LM3533_ATTR_RW(pwm);
+static LM3533_ATTR_RW(risetime);
+
+static struct attribute *lm3533_led_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_falltime.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_max_current.attr,
+	&dev_attr_pwm.attr,
+	&dev_attr_risetime.attr,
+	NULL,
+};
+
+static mode_t lm3533_led_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	mode_t mode = attr->mode;
+
+	if (attr = &dev_attr_als.attr) {
+		if (!led->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_led_attribute_group = {
+	.is_visible	= lm3533_led_attr_is_visible,
+	.attrs		= lm3533_led_attributes
+};
+
+static int __devinit lm3533_led_setup(struct lm3533_led *led,
+					struct lm3533_led_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&led->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&led->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_led_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_led_platform_data *pdata;
+	struct lm3533_led *led;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_LVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal LED id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	led = kzalloc(sizeof(*led), GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	led->lm3533 = lm3533;
+	led->cdev.name = pdata->name;
+	led->cdev.default_trigger = pdata->default_trigger;
+	led->cdev.brightness_set = lm3533_led_set;
+	led->cdev.brightness_get = lm3533_led_get;
+	led->cdev.blink_set = lm3533_led_blink_set;
+	led->cdev.brightness = LED_OFF;
+	led->id = pdev->id;
+
+	mutex_init(&led->mutex);
+	INIT_WORK(&led->work, lm3533_led_work);
+
+	/* The class framework makes a callback to get brightness during
+	 * registration so use parent device (for error reporting) until
+	 * registered.
+	 */
+	led->cb.lm3533 = lm3533;
+	led->cb.id = lm3533_led_get_ctrlbank_id(led);
+	led->cb.dev = lm3533->dev;
+
+	platform_set_drvdata(pdev, led);
+
+	ret = led_classdev_register(pdev->dev.parent, &led->cdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id);
+		goto err_free;
+	}
+
+	led->cb.dev = led->cdev.dev;
+
+	ret = sysfs_create_group(&led->cdev.dev->kobj,
+						&lm3533_led_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	ret = lm3533_led_setup(led, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&led->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+err_unregister:
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+err_free:
+	kfree(led);
+
+	return ret;
+}
+
+static int __devexit lm3533_led_remove(struct platform_device *pdev)
+{
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+	kfree(led);
+
+	return 0;
+}
+
+static void lm3533_led_shutdown(struct platform_device *pdev)
+{
+
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	lm3533_led_set(&led->cdev, LED_OFF);		/* disable blink */
+	flush_work_sync(&led->work);
+}
+
+static struct platform_driver lm3533_led_driver = {
+	.driver = {
+		.name = "lm3533-leds",
+		.owner = THIS_MODULE,
+	},
+	.probe		= lm3533_led_probe,
+	.remove		= __devexit_p(lm3533_led_remove),
+	.shutdown	= lm3533_led_shutdown,
+};
+module_platform_driver(lm3533_led_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-leds");
-- 
1.7.8.5


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

* [PATCH v2 4/4] backlight: add LM3533 backlight driver
  2012-05-03 10:26   ` Johan Hovold
@ 2012-05-03 10:26     ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:26 UTC (permalink / raw)
  To: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat
  Cc: Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev, Johan Hovold

Add sub-driver for the backlights on National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels and ambient-light-sensor and pwm
input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2:
 - add sysfs-ABI documentation
 - open code max_current/pwm macros


 .../testing/sysfs-class-backlight-driver-lm3533    |   50 +++
 drivers/video/backlight/Kconfig                    |   12 +
 drivers/video/backlight/Makefile                   |    1 +
 drivers/video/backlight/lm3533_bl.c                |  458 ++++++++++++++++++++
 4 files changed, 521 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
 create mode 100644 drivers/video/backlight/lm3533_bl.c

diff --git a/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533 b/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
new file mode 100644
index 0000000..866fd3e
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
@@ -0,0 +1,50 @@
+What:		/sys/class/backlight/<backlight>/als
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS-control mode (0,..2), where
+
+		0 - disabled
+		1 - ALS-mapper 1 (backlight 0)
+		2 - ALS-mapper 2 (backlight 1)
+
+What:		/sys/class/backlight/<backlight>/id
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the id of this backlight (0, 1).
+
+What:		/sys/class/backlight/<backlight>/linear
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the brightness-mapping mode (0, 1), where
+
+		0 - exponential mode
+		1 - linear mode
+
+What:		/sys/class/backlight/<backlight>/max_current
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the full-scale current I_{LED_FULLSCALE} (0..31), where
+
+		I_{LED_FULLSCALE} = 5mA + max_current * 0.8mA
+
+What:		/sys/class/backlight/<backlight>/pwm
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the PWM-input control mask (5 bits), where
+
+		bit 5 - PWM-input enabled in Zone 4
+		bit 4 - PWM-input enabled in Zone 3
+		bit 3 - PWM-input enabled in Zone 2
+		bit 2 - PWM-input enabled in Zone 1
+		bit 1 - PWM-input enabled in Zone 0
+		bit 0 - PWM-input enabled
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index af16884..fa2b037 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -184,6 +184,18 @@ config BACKLIGHT_GENERIC
 	  known as the Corgi backlight driver. If you have a Sharp Zaurus
 	  SL-C7xx, SL-Cxx00 or SL-6000x say y.
 
+config BACKLIGHT_LM3533
+	tristate "Backlight Driver for LM3533"
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on MFD_LM3533
+	help
+	  Say Y to enable the backlight driver for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  The backlights can be controlled directly, through PWM input, or by
+	  the ambient-light-sensor interface. The chip supports 256 brightness
+	  levels.
+
 config BACKLIGHT_LOCOMO
 	tristate "Sharp LOCOMO LCD/Backlight Driver"
 	depends on SHARP_LOCOMO
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 36855ae..a2ac9cf 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_BACKLIGHT_EP93XX)	+= ep93xx_bl.o
 obj-$(CONFIG_BACKLIGHT_GENERIC)	+= generic_bl.o
 obj-$(CONFIG_BACKLIGHT_HP700)	+= jornada720_bl.o
 obj-$(CONFIG_BACKLIGHT_HP680)	+= hp680_bl.o
+obj-$(CONFIG_BACKLIGHT_LM3533)	+= lm3533_bl.o
 obj-$(CONFIG_BACKLIGHT_LOCOMO)	+= locomolcd.o
 obj-$(CONFIG_BACKLIGHT_LP855X)	+= lp855x_bl.o
 obj-$(CONFIG_BACKLIGHT_OMAP1)	+= omap1_bl.o
diff --git a/drivers/video/backlight/lm3533_bl.c b/drivers/video/backlight/lm3533_bl.c
new file mode 100644
index 0000000..4100c7a
--- /dev/null
+++ b/drivers/video/backlight/lm3533_bl.c
@@ -0,0 +1,458 @@
+/*
+ * lm3533-bl.c -- LM3533 Backlight driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_HVCTRLBANK_COUNT		2
+#define LM3533_BL_MAX_BRIGHTNESS	255
+
+#define LM3533_REG_CTRLBANK_AB_BCONF	0x1a
+
+
+struct lm3533_bl {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct backlight_device *bd;
+	int id;
+};
+
+
+static inline int lm3533_bl_get_ctrlbank_id(struct lm3533_bl *bl)
+{
+	return bl->id;
+}
+
+static int lm3533_bl_update_status(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	int brightness = bd->props.brightness;
+
+	if (bd->props.power != FB_BLANK_UNBLANK)
+		brightness = 0;
+	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	return lm3533_ctrlbank_set_brightness(&bl->cb, (u8)brightness);
+}
+
+static int lm3533_bl_get_brightness(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return val;
+}
+
+static const struct backlight_ops lm3533_bl_ops = {
+	.get_brightness	= lm3533_bl_get_brightness,
+	.update_status	= lm3533_bl_update_status,
+};
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", bl->id);
+}
+
+/*
+ * ALS-control setting:
+ *
+ *   0 - ALS disabled
+ *   1 - ALS-mapper 1 (backlight 0)
+ *   2 - ALS-mapper 2 (backlight 1)
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	u8 val;
+	u8 mask;
+	int als;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 2 * ctrlbank;
+	als = val & mask;
+	if (als)
+		als = ctrlbank + 1;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	int als;
+	u8 val;
+	u8 mask;
+	int ret;
+
+	if (kstrtoint(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als != ctrlbank + 1))
+		return -EINVAL;
+
+	mask = 1 << (2 * ctrlbank);
+
+	if (als)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	u8 mask;
+	int linear;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (val & mask)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	unsigned long linear;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_max_current(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_max_current(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_max_current(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_max_current(&bl->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_pwm(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_pwm(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_pwm(&bl->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(max_current);
+static LM3533_ATTR_RW(pwm);
+
+static struct attribute *lm3533_bl_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_max_current.attr,
+	&dev_attr_pwm.attr,
+	NULL,
+};
+
+static mode_t lm3533_bl_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	mode_t mode = attr->mode;
+
+	if (attr == &dev_attr_als.attr) {
+		if (!bl->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_bl_attribute_group = {
+	.is_visible	= lm3533_bl_attr_is_visible,
+	.attrs		= lm3533_bl_attributes
+};
+
+static int __devinit lm3533_bl_setup(struct lm3533_bl *bl,
+					struct lm3533_bl_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&bl->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&bl->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_bl_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_bl_platform_data *pdata;
+	struct lm3533_bl *bl;
+	struct backlight_device *bd;
+	struct backlight_properties props;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_HVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal backlight id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	bl = kzalloc(sizeof(*bl), GFP_KERNEL);
+	if (!bl) {
+		dev_err(&pdev->dev,
+				"failed to allocate memory for backlight\n");
+		return -ENOMEM;
+	}
+
+	bl->lm3533 = lm3533;
+	bl->id = pdev->id;
+
+	bl->cb.lm3533 = lm3533;
+	bl->cb.id = lm3533_bl_get_ctrlbank_id(bl);
+	bl->cb.dev = NULL;			/* until registered */
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = LM3533_BL_MAX_BRIGHTNESS;
+	props.brightness = pdata->default_brightness;
+	bd = backlight_device_register(pdata->name, pdev->dev.parent, bl,
+						&lm3533_bl_ops, &props);
+	if (IS_ERR(bd)) {
+		dev_err(&pdev->dev, "failed to register backlight device\n");
+		ret = PTR_ERR(bd);
+		goto err_free;
+	}
+
+	bl->bd = bd;
+	bl->cb.dev = &bl->bd->dev;
+
+	platform_set_drvdata(pdev, bl);
+
+	ret = sysfs_create_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	backlight_update_status(bd);
+
+	ret = lm3533_bl_setup(bl, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&bl->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+err_unregister:
+	backlight_device_unregister(bd);
+err_free:
+	kfree(bl);
+
+	return ret;
+}
+
+static int __devexit lm3533_bl_remove(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+	struct backlight_device *bd = bl->bd;
+
+	dev_dbg(&bd->dev, "%s\n", __func__);
+
+	bd->props.power = FB_BLANK_POWERDOWN;
+	bd->props.brightness = 0;
+
+	lm3533_ctrlbank_disable(&bl->cb);
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	backlight_device_unregister(bd);
+	kfree(bl);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int lm3533_bl_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static int lm3533_bl_resume(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_enable(&bl->cb);
+}
+#else
+#define lm3533_bl_suspend	NULL
+#define lm3533_bl_resume	NULL
+#endif
+
+static void lm3533_bl_shutdown(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static struct platform_driver lm3533_bl_driver = {
+	.driver = {
+		.name	= "lm3533-backlight",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= lm3533_bl_probe,
+	.remove		= __devexit_p(lm3533_bl_remove),
+	.shutdown	= lm3533_bl_shutdown,
+	.suspend	= lm3533_bl_suspend,
+	.resume		= lm3533_bl_resume,
+};
+module_platform_driver(lm3533_bl_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Backlight driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-backlight");
-- 
1.7.8.5


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

* [PATCH v2 4/4] backlight: add LM3533 backlight driver
@ 2012-05-03 10:26     ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 10:26 UTC (permalink / raw)
  To: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat
  Cc: Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev, Johan Hovold

Add sub-driver for the backlights on National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels and ambient-light-sensor and pwm
input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2:
 - add sysfs-ABI documentation
 - open code max_current/pwm macros


 .../testing/sysfs-class-backlight-driver-lm3533    |   50 +++
 drivers/video/backlight/Kconfig                    |   12 +
 drivers/video/backlight/Makefile                   |    1 +
 drivers/video/backlight/lm3533_bl.c                |  458 ++++++++++++++++++++
 4 files changed, 521 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
 create mode 100644 drivers/video/backlight/lm3533_bl.c

diff --git a/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533 b/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
new file mode 100644
index 0000000..866fd3e
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
@@ -0,0 +1,50 @@
+What:		/sys/class/backlight/<backlight>/als
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS-control mode (0,..2), where
+
+		0 - disabled
+		1 - ALS-mapper 1 (backlight 0)
+		2 - ALS-mapper 2 (backlight 1)
+
+What:		/sys/class/backlight/<backlight>/id
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the id of this backlight (0, 1).
+
+What:		/sys/class/backlight/<backlight>/linear
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the brightness-mapping mode (0, 1), where
+
+		0 - exponential mode
+		1 - linear mode
+
+What:		/sys/class/backlight/<backlight>/max_current
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the full-scale current I_{LED_FULLSCALE} (0..31), where
+
+		I_{LED_FULLSCALE} = 5mA + max_current * 0.8mA
+
+What:		/sys/class/backlight/<backlight>/pwm
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the PWM-input control mask (5 bits), where
+
+		bit 5 - PWM-input enabled in Zone 4
+		bit 4 - PWM-input enabled in Zone 3
+		bit 3 - PWM-input enabled in Zone 2
+		bit 2 - PWM-input enabled in Zone 1
+		bit 1 - PWM-input enabled in Zone 0
+		bit 0 - PWM-input enabled
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index af16884..fa2b037 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -184,6 +184,18 @@ config BACKLIGHT_GENERIC
 	  known as the Corgi backlight driver. If you have a Sharp Zaurus
 	  SL-C7xx, SL-Cxx00 or SL-6000x say y.
 
+config BACKLIGHT_LM3533
+	tristate "Backlight Driver for LM3533"
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on MFD_LM3533
+	help
+	  Say Y to enable the backlight driver for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  The backlights can be controlled directly, through PWM input, or by
+	  the ambient-light-sensor interface. The chip supports 256 brightness
+	  levels.
+
 config BACKLIGHT_LOCOMO
 	tristate "Sharp LOCOMO LCD/Backlight Driver"
 	depends on SHARP_LOCOMO
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 36855ae..a2ac9cf 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_BACKLIGHT_EP93XX)	+= ep93xx_bl.o
 obj-$(CONFIG_BACKLIGHT_GENERIC)	+= generic_bl.o
 obj-$(CONFIG_BACKLIGHT_HP700)	+= jornada720_bl.o
 obj-$(CONFIG_BACKLIGHT_HP680)	+= hp680_bl.o
+obj-$(CONFIG_BACKLIGHT_LM3533)	+= lm3533_bl.o
 obj-$(CONFIG_BACKLIGHT_LOCOMO)	+= locomolcd.o
 obj-$(CONFIG_BACKLIGHT_LP855X)	+= lp855x_bl.o
 obj-$(CONFIG_BACKLIGHT_OMAP1)	+= omap1_bl.o
diff --git a/drivers/video/backlight/lm3533_bl.c b/drivers/video/backlight/lm3533_bl.c
new file mode 100644
index 0000000..4100c7a
--- /dev/null
+++ b/drivers/video/backlight/lm3533_bl.c
@@ -0,0 +1,458 @@
+/*
+ * lm3533-bl.c -- LM3533 Backlight driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_HVCTRLBANK_COUNT		2
+#define LM3533_BL_MAX_BRIGHTNESS	255
+
+#define LM3533_REG_CTRLBANK_AB_BCONF	0x1a
+
+
+struct lm3533_bl {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct backlight_device *bd;
+	int id;
+};
+
+
+static inline int lm3533_bl_get_ctrlbank_id(struct lm3533_bl *bl)
+{
+	return bl->id;
+}
+
+static int lm3533_bl_update_status(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	int brightness = bd->props.brightness;
+
+	if (bd->props.power != FB_BLANK_UNBLANK)
+		brightness = 0;
+	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	return lm3533_ctrlbank_set_brightness(&bl->cb, (u8)brightness);
+}
+
+static int lm3533_bl_get_brightness(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return val;
+}
+
+static const struct backlight_ops lm3533_bl_ops = {
+	.get_brightness	= lm3533_bl_get_brightness,
+	.update_status	= lm3533_bl_update_status,
+};
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", bl->id);
+}
+
+/*
+ * ALS-control setting:
+ *
+ *   0 - ALS disabled
+ *   1 - ALS-mapper 1 (backlight 0)
+ *   2 - ALS-mapper 2 (backlight 1)
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	u8 val;
+	u8 mask;
+	int als;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 2 * ctrlbank;
+	als = val & mask;
+	if (als)
+		als = ctrlbank + 1;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	int als;
+	u8 val;
+	u8 mask;
+	int ret;
+
+	if (kstrtoint(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als != ctrlbank + 1))
+		return -EINVAL;
+
+	mask = 1 << (2 * ctrlbank);
+
+	if (als)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	u8 mask;
+	int linear;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (val & mask)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	unsigned long linear;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_max_current(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_max_current(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_max_current(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_max_current(&bl->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_pwm(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_pwm(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_pwm(&bl->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(max_current);
+static LM3533_ATTR_RW(pwm);
+
+static struct attribute *lm3533_bl_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_max_current.attr,
+	&dev_attr_pwm.attr,
+	NULL,
+};
+
+static mode_t lm3533_bl_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	mode_t mode = attr->mode;
+
+	if (attr = &dev_attr_als.attr) {
+		if (!bl->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_bl_attribute_group = {
+	.is_visible	= lm3533_bl_attr_is_visible,
+	.attrs		= lm3533_bl_attributes
+};
+
+static int __devinit lm3533_bl_setup(struct lm3533_bl *bl,
+					struct lm3533_bl_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&bl->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&bl->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_bl_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_bl_platform_data *pdata;
+	struct lm3533_bl *bl;
+	struct backlight_device *bd;
+	struct backlight_properties props;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_HVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal backlight id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	bl = kzalloc(sizeof(*bl), GFP_KERNEL);
+	if (!bl) {
+		dev_err(&pdev->dev,
+				"failed to allocate memory for backlight\n");
+		return -ENOMEM;
+	}
+
+	bl->lm3533 = lm3533;
+	bl->id = pdev->id;
+
+	bl->cb.lm3533 = lm3533;
+	bl->cb.id = lm3533_bl_get_ctrlbank_id(bl);
+	bl->cb.dev = NULL;			/* until registered */
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = LM3533_BL_MAX_BRIGHTNESS;
+	props.brightness = pdata->default_brightness;
+	bd = backlight_device_register(pdata->name, pdev->dev.parent, bl,
+						&lm3533_bl_ops, &props);
+	if (IS_ERR(bd)) {
+		dev_err(&pdev->dev, "failed to register backlight device\n");
+		ret = PTR_ERR(bd);
+		goto err_free;
+	}
+
+	bl->bd = bd;
+	bl->cb.dev = &bl->bd->dev;
+
+	platform_set_drvdata(pdev, bl);
+
+	ret = sysfs_create_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	backlight_update_status(bd);
+
+	ret = lm3533_bl_setup(bl, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&bl->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+err_unregister:
+	backlight_device_unregister(bd);
+err_free:
+	kfree(bl);
+
+	return ret;
+}
+
+static int __devexit lm3533_bl_remove(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+	struct backlight_device *bd = bl->bd;
+
+	dev_dbg(&bd->dev, "%s\n", __func__);
+
+	bd->props.power = FB_BLANK_POWERDOWN;
+	bd->props.brightness = 0;
+
+	lm3533_ctrlbank_disable(&bl->cb);
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	backlight_device_unregister(bd);
+	kfree(bl);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int lm3533_bl_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static int lm3533_bl_resume(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_enable(&bl->cb);
+}
+#else
+#define lm3533_bl_suspend	NULL
+#define lm3533_bl_resume	NULL
+#endif
+
+static void lm3533_bl_shutdown(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static struct platform_driver lm3533_bl_driver = {
+	.driver = {
+		.name	= "lm3533-backlight",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= lm3533_bl_probe,
+	.remove		= __devexit_p(lm3533_bl_remove),
+	.shutdown	= lm3533_bl_shutdown,
+	.suspend	= lm3533_bl_suspend,
+	.resume		= lm3533_bl_resume,
+};
+module_platform_driver(lm3533_bl_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Backlight driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-backlight");
-- 
1.7.8.5


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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-03 10:26     ` Johan Hovold
@ 2012-05-03 10:38       ` Mark Brown
  -1 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 10:38 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 745 bytes --]

On Thu, May 03, 2012 at 12:26:36PM +0200, Johan Hovold wrote:
> Add support for National Semiconductor / TI LM3533 lighting power chips.
> 
> This is the core driver which provides register access over I2C and
> registers the ambient-light-sensor, LED and backlight sub-drivers.

Reviwed-by: Mark Brown <broonie@opensource.wolfsonmicro.com>

though

> +	dev_dbg(lm3533->dev, "read [%02x]: %02x\n", reg, *val);

I'd expect you can drop these log messages, if there's stuff like this
missing we should add it to regmap.  At the minute the regmap logging is
via trace points rather than debug logs as you can leave them enabled
all the time.

Might also be worth moving some of the sysfs stuff to live with the
relevant drivers.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-03 10:38       ` Mark Brown
  0 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 10:38 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 745 bytes --]

On Thu, May 03, 2012 at 12:26:36PM +0200, Johan Hovold wrote:
> Add support for National Semiconductor / TI LM3533 lighting power chips.
> 
> This is the core driver which provides register access over I2C and
> registers the ambient-light-sensor, LED and backlight sub-drivers.

Reviwed-by: Mark Brown <broonie@opensource.wolfsonmicro.com>

though

> +	dev_dbg(lm3533->dev, "read [%02x]: %02x\n", reg, *val);

I'd expect you can drop these log messages, if there's stuff like this
missing we should add it to regmap.  At the minute the regmap logging is
via trace points rather than debug logs as you can leave them enabled
all the time.

Might also be worth moving some of the sysfs stuff to live with the
relevant drivers.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 3/4] leds: add LM3533 LED driver
  2012-05-03 10:26     ` Johan Hovold
@ 2012-05-03 10:43       ` Mark Brown
  -1 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 10:43 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 1272 bytes --]

On Thu, May 03, 2012 at 12:26:38PM +0200, Johan Hovold wrote:

> +What:		/sys/class/leds/<led>/risetime
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold <jhovold@gmail.com>
> +Description:
> +		Set the pattern generator fall and rise times (0..7), where
> +
> +		0 - 2048 us
> +		1 - 262 ms
> +		2 - 524 ms
> +		3 - 1.049 s
> +		4 - 2.097 s
> +		5 - 4.194 s
> +		6 - 8.389 s
> +		7 - 16.78 s
> +

Shouldn't these be controlled by led_blink_set() rather than a custom
ABI?

> +What:		/sys/class/leds/<led>/id
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold <jhovold@gmail.com>
> +Description:
> +		Get the id of this led (0..3).
> +

This should just be a generic LED subsystem thing?

> +What:		/sys/class/leds/<led>/max_current
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold <jhovold@gmail.com>
> +Description:
> +		Set the full-scale current I_{LED_FULLSCALE} (0..31), where
> +
> +		I_{LED_FULLSCALE} = 5mA + max_current * 0.8mA
> +

Shouldn't this be set by platform data, the maximum current you can push
through the LEDs seems like a board dependant thing which won't change
dynamically at runtime.  The brightness can already be varied.

It'd also be nicer if the kernel did the calculation for the user.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 3/4] leds: add LM3533 LED driver
@ 2012-05-03 10:43       ` Mark Brown
  0 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 10:43 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 1272 bytes --]

On Thu, May 03, 2012 at 12:26:38PM +0200, Johan Hovold wrote:

> +What:		/sys/class/leds/<led>/risetime
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold <jhovold@gmail.com>
> +Description:
> +		Set the pattern generator fall and rise times (0..7), where
> +
> +		0 - 2048 us
> +		1 - 262 ms
> +		2 - 524 ms
> +		3 - 1.049 s
> +		4 - 2.097 s
> +		5 - 4.194 s
> +		6 - 8.389 s
> +		7 - 16.78 s
> +

Shouldn't these be controlled by led_blink_set() rather than a custom
ABI?

> +What:		/sys/class/leds/<led>/id
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold <jhovold@gmail.com>
> +Description:
> +		Get the id of this led (0..3).
> +

This should just be a generic LED subsystem thing?

> +What:		/sys/class/leds/<led>/max_current
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold <jhovold@gmail.com>
> +Description:
> +		Set the full-scale current I_{LED_FULLSCALE} (0..31), where
> +
> +		I_{LED_FULLSCALE} = 5mA + max_current * 0.8mA
> +

Shouldn't this be set by platform data, the maximum current you can push
through the LEDs seems like a board dependant thing which won't change
dynamically at runtime.  The brightness can already be varied.

It'd also be nicer if the kernel did the calculation for the user.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-03 10:38       ` Mark Brown
@ 2012-05-03 11:28         ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 11:28 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 11:38:48AM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 12:26:36PM +0200, Johan Hovold wrote:
> > Add support for National Semiconductor / TI LM3533 lighting power chips.
> > 
> > This is the core driver which provides register access over I2C and
> > registers the ambient-light-sensor, LED and backlight sub-drivers.
> 
> Reviwed-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
> 
> though
> 
> > +	dev_dbg(lm3533->dev, "read [%02x]: %02x\n", reg, *val);
> 
> I'd expect you can drop these log messages, if there's stuff like this
> missing we should add it to regmap.  At the minute the regmap logging is
> via trace points rather than debug logs as you can leave them enabled
> all the time.

If such debugging is added to regmap we still need a way to enable them
per driver (or rather regmap) to not clutter the logs.

These three dev_dbg statements are extremely useful during debugging /
development especially in combination with the other dynamic printks in
these drivers.

I'd actually prefer just keeping them for now.
 
> Might also be worth moving some of the sysfs stuff to live with the
> relevant drivers.

Which attributes do you have in mind?

The boost_freq and boost_ovp affect both backlight devices (i.e. cannot
be set separately) and as such belong in the parent driver IMO.

Same with the output_hvled{1..2}, output_lvled{1..5} attributes. The
chip has four logical LEDs ("control banks") but five low-voltage output
sinks. The five output_lvled attributes determine the mapping and as
such belong in the parent driver. The two logical backlight devices can
likewise be used to control either or both high-voltage outputs and
belong in the parent driver for the same reasons.

Thanks,
Johan

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-03 11:28         ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 11:28 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 11:38:48AM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 12:26:36PM +0200, Johan Hovold wrote:
> > Add support for National Semiconductor / TI LM3533 lighting power chips.
> > 
> > This is the core driver which provides register access over I2C and
> > registers the ambient-light-sensor, LED and backlight sub-drivers.
> 
> Reviwed-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
> 
> though
> 
> > +	dev_dbg(lm3533->dev, "read [%02x]: %02x\n", reg, *val);
> 
> I'd expect you can drop these log messages, if there's stuff like this
> missing we should add it to regmap.  At the minute the regmap logging is
> via trace points rather than debug logs as you can leave them enabled
> all the time.

If such debugging is added to regmap we still need a way to enable them
per driver (or rather regmap) to not clutter the logs.

These three dev_dbg statements are extremely useful during debugging /
development especially in combination with the other dynamic printks in
these drivers.

I'd actually prefer just keeping them for now.
 
> Might also be worth moving some of the sysfs stuff to live with the
> relevant drivers.

Which attributes do you have in mind?

The boost_freq and boost_ovp affect both backlight devices (i.e. cannot
be set separately) and as such belong in the parent driver IMO.

Same with the output_hvled{1..2}, output_lvled{1..5} attributes. The
chip has four logical LEDs ("control banks") but five low-voltage output
sinks. The five output_lvled attributes determine the mapping and as
such belong in the parent driver. The two logical backlight devices can
likewise be used to control either or both high-voltage outputs and
belong in the parent driver for the same reasons.

Thanks,
Johan

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-03 11:28         ` Johan Hovold
@ 2012-05-03 11:38           ` Mark Brown
  -1 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 11:38 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 2134 bytes --]

On Thu, May 03, 2012 at 01:28:03PM +0200, Johan Hovold wrote:
> On Thu, May 03, 2012 at 11:38:48AM +0100, Mark Brown wrote:

> > I'd expect you can drop these log messages, if there's stuff like this
> > missing we should add it to regmap.  At the minute the regmap logging is
> > via trace points rather than debug logs as you can leave them enabled
> > all the time.

> If such debugging is added to regmap we still need a way to enable them
> per driver (or rather regmap) to not clutter the logs.

This is one of the reasons why we currently use tracepoints (they just
don't have this issue as they're trivial to filter), though
adding some sort of infrastructure for it ought not to be too difficult
even if it's just at the regmap level.

> These three dev_dbg statements are extremely useful during debugging /
> development especially in combination with the other dynamic printks in
> these drivers.

> I'd actually prefer just keeping them for now.

OTOH the whole point in having stuff like this is to factor out repeated
code like this so if the infrastructure isn't working we should fix
that.

> > Might also be worth moving some of the sysfs stuff to live with the
> > relevant drivers.

> Which attributes do you have in mind?

Pretty much all of those on the MFD.

> The boost_freq and boost_ovp affect both backlight devices (i.e. cannot
> be set separately) and as such belong in the parent driver IMO.

> Same with the output_hvled{1..2}, output_lvled{1..5} attributes. The
> chip has four logical LEDs ("control banks") but five low-voltage output
> sinks. The five output_lvled attributes determine the mapping and as
> such belong in the parent driver. The two logical backlight devices can
> likewise be used to control either or both high-voltage outputs and
> belong in the parent driver for the same reasons.

Actually, the other question I had but forgot to ask (or I think punted
on for your response) was why these are in sysfs at all - things like
which things are connected to the backlight are going to be a property
of the board design so should be defined by the machine not tweaked from
userspace.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-03 11:38           ` Mark Brown
  0 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 11:38 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 2134 bytes --]

On Thu, May 03, 2012 at 01:28:03PM +0200, Johan Hovold wrote:
> On Thu, May 03, 2012 at 11:38:48AM +0100, Mark Brown wrote:

> > I'd expect you can drop these log messages, if there's stuff like this
> > missing we should add it to regmap.  At the minute the regmap logging is
> > via trace points rather than debug logs as you can leave them enabled
> > all the time.

> If such debugging is added to regmap we still need a way to enable them
> per driver (or rather regmap) to not clutter the logs.

This is one of the reasons why we currently use tracepoints (they just
don't have this issue as they're trivial to filter), though
adding some sort of infrastructure for it ought not to be too difficult
even if it's just at the regmap level.

> These three dev_dbg statements are extremely useful during debugging /
> development especially in combination with the other dynamic printks in
> these drivers.

> I'd actually prefer just keeping them for now.

OTOH the whole point in having stuff like this is to factor out repeated
code like this so if the infrastructure isn't working we should fix
that.

> > Might also be worth moving some of the sysfs stuff to live with the
> > relevant drivers.

> Which attributes do you have in mind?

Pretty much all of those on the MFD.

> The boost_freq and boost_ovp affect both backlight devices (i.e. cannot
> be set separately) and as such belong in the parent driver IMO.

> Same with the output_hvled{1..2}, output_lvled{1..5} attributes. The
> chip has four logical LEDs ("control banks") but five low-voltage output
> sinks. The five output_lvled attributes determine the mapping and as
> such belong in the parent driver. The two logical backlight devices can
> likewise be used to control either or both high-voltage outputs and
> belong in the parent driver for the same reasons.

Actually, the other question I had but forgot to ask (or I think punted
on for your response) was why these are in sysfs at all - things like
which things are connected to the backlight are going to be a property
of the board design so should be defined by the machine not tweaked from
userspace.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
  2012-05-03 10:26     ` Johan Hovold
@ 2012-05-03 11:40       ` Jonathan Cameron
  -1 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-03 11:40 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Arnd Bergmann, Andrew Morton,
	Mark Brown, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On 5/3/2012 11:26 AM, Johan Hovold wrote:
> Add sub-driver for the ambient light sensor interface on National
> Semiconductor / TI LM3533 lighting power chips.
>
> The sensor interface can be used to control the LEDs and backlights of
> the chip through defining five light zones and three sets of
> corresponding brightness target levels.
>
> The driver provides raw and mean adc readings along with the current
> light zone through sysfs. A threshold event can be generated on zone
> changes.
Code is fine.  Pretty much all my comments are to do with the interface.
>
> Signed-off-by: Johan Hovold<jhovold@gmail.com>
> ---
>
> v2:
>   - reimplement using iio
>   - add sysfs-ABI documentation
>
>
>   .../Documentation/sysfs-bus-iio-light-lm3533-als   |   62 ++
>   drivers/staging/iio/light/Kconfig                  |   16 +
>   drivers/staging/iio/light/Makefile                 |    1 +
>   drivers/staging/iio/light/lm3533-als.c             |  617 ++++++++++++++++++++
>   4 files changed, 696 insertions(+), 0 deletions(-)
>   create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>   create mode 100644 drivers/staging/iio/light/lm3533-als.c
>
> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> new file mode 100644
> index 0000000..9849d14
> --- /dev/null
> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> @@ -0,0 +1,62 @@
> +What:		/sys/bus/iio/devices/iio:deviceX/gain
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Set the ALS gain-resistor setting (0..127) for analog input
> +		mode, where
> +
> +		0000000 - ALS input is high impedance
> +		0000001 - 200kOhm (10uA at 2V full-scale)
> +		0000010 - 100kOhm (20uA at 2V full-scale)
> +		...
> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
> +
> +		R_als = 2V / (10uA * gain)	(gain>  0)
Firstly, no magic numbers.  These are definitely magic.  Secondly see
in_illuminance0_scale for a suitable existing attribute.
> +
> +What:		/sys/bus/iio/devices/iio:deviceX/illuminance_zone
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Get the current light zone (0..4) as defined by the
> +		in_illuminance_thresh[n]_{falling,rising} thresholds.
Hmm.. definitely have an in prefix, beyond that I'm not sure what the 
cleanest
interface will be for this.   Could extend the event codes to deal with the
zone index.  Slightly tricky as the channel could already be modified so
chan2 isn't necesarily available.
> +
> +What:		/sys/.../events/in_illuminance_thresh_either_en
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Event generated when channel passes one of the four threshold
> +		in either direction (rising|falling) and a zone change occurs.
> +		The corresponding light zone can be read from
> +		illuminance_zone.
> +
> +What:		/sys/.../events/illuminance_thresh0_falling_value
hmm.. every time you think you are making progress a new and exciting 
device comes
along requiring the abi to be extended.
in_illuminanceX_threshY_rising_value
in_illuminanceX_threshY_falling_value
should do with appropriate description.
> +What:		/sys/.../events/illuminance_thresh0_raising_value
> +What:		/sys/.../events/illuminance_thresh1_falling_value
> +What:		/sys/.../events/illuminance_thresh1_raising_value
> +What:		/sys/.../events/illuminance_thresh2_falling_value
> +What:		/sys/.../events/illuminance_thresh2_raising_value
> +What:		/sys/.../events/illuminance_thresh3_falling_value
> +What:		/sys/.../events/illuminance_thresh3_raising_value
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Specifies the value of threshold that the device is comparing
> +		against for the events enabled by
> +		in_illuminance_thresh_either_en, and defines the
> +		the five light zones.
> +
> +		These thresholds correspond to the eight zone-boundary
> +		registers (boundary[n]_{low,high}).
> +
This interface is going to take some thought.  We have 
in_illuminance0_target at the
moment, so I guess we can add a zoning concept to that...
> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Set the target brightness for ALS-mapper m in light zone n
> +		(0..255), where m in 1..3 and n in 0..4.
Don't suppose you could do a quick summary of what these zones are and 
why there
are 3 ALS-mappers?  I'm not getting terribly far on a quick look at the 
datasheet!
> diff --git a/drivers/staging/iio/light/Kconfig b/drivers/staging/iio/light/Kconfig
> index e7e9159..263e44a 100644
> --- a/drivers/staging/iio/light/Kconfig
> +++ b/drivers/staging/iio/light/Kconfig
> @@ -24,6 +24,22 @@ config SENSORS_TSL2563
>   	 This driver can also be built as a module.  If so, the module
>   	 will be called tsl2563.
>
> +config SENSORS_LM3533
> +	tristate "LM3533 ambient light sensor"
> +	depends on MFD_LM3533
> +	help
> +	  If you say yes here you get support for the ambient light sensor
> +	  interface on National Semiconductor / TI LM3533 Lighting Power
> +	  chips.
> +
> +	  The sensor interface can be used to control the LEDs and backlights
> +	  of the chip through defining five light zones and three sets of
> +	  corresponding brightness target levels.
> +
> +	  The driver provides raw and mean adc readings along with the current
> +	  light zone through sysfs. A threshold event can be generated on zone
> +	  changes.
> +
>   config TSL2583
>   	tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters"
>   	depends on I2C
> diff --git a/drivers/staging/iio/light/Makefile b/drivers/staging/iio/light/Makefile
> index 3011fbf..16a60a2 100644
> --- a/drivers/staging/iio/light/Makefile
> +++ b/drivers/staging/iio/light/Makefile
> @@ -4,4 +4,5 @@
>
>   obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o
>   obj-$(CONFIG_SENSORS_ISL29018)	+= isl29018.o
> +obj-$(CONFIG_SENSORS_LM3533)	+= lm3533-als.o
>   obj-$(CONFIG_TSL2583)	+= tsl2583.o
> diff --git a/drivers/staging/iio/light/lm3533-als.c b/drivers/staging/iio/light/lm3533-als.c
> new file mode 100644
> index 0000000..e2c9be6
> --- /dev/null
> +++ b/drivers/staging/iio/light/lm3533-als.c
> @@ -0,0 +1,617 @@
> +/*
> + * lm3533-als.c -- LM3533 Ambient Light Sensor driver
> + *
> + * Copyright (C) 2011-2012 Texas Instruments
> + *
> + * Author: Johan Hovold<jhovold@gmail.com>
> + *
> + * 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.
> + */
> +
> +#include<linux/atomic.h>
> +#include<linux/fs.h>
> +#include<linux/interrupt.h>
> +#include<linux/io.h>
> +#include<linux/module.h>
> +#include<linux/mfd/core.h>
> +#include<linux/platform_device.h>
> +#include<linux/slab.h>
> +#include<linux/uaccess.h>
> +
> +#include<linux/mfd/lm3533.h>
> +
> +#include "../events.h"
> +#include "../iio.h"
This will need to go through the staging-next tree.  In there these
headers have moved.
> +
> +
> +#define LM3533_ALS_RESISTOR_MAX			0x7f
> +#define LM3533_ALS_ADC_MAX			0xff
> +#define LM3533_ALS_BOUNDARY_MAX			LM3533_ALS_ADC_MAX
> +#define LM3533_ALS_TARGET_MAX			LM3533_ALS_ADC_MAX
> +#define LM3533_ALS_ZONE_MAX			4
> +
> +#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
> +#define LM3533_REG_ALS_CONF			0x31
> +#define LM3533_REG_ALS_ZONE_INFO		0x34
> +#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x37
> +#define LM3533_REG_ALS_READ_ADC_RAW		0x38
> +#define LM3533_REG_ALS_BOUNDARY0_HIGH		0x50
> +#define LM3533_REG_ALS_BOUNDARY0_LOW		0x51
> +#define LM3533_REG_ALS_BOUNDARY1_HIGH		0x52
> +#define LM3533_REG_ALS_BOUNDARY1_LOW		0x53
> +#define LM3533_REG_ALS_BOUNDARY2_HIGH		0x54
> +#define LM3533_REG_ALS_BOUNDARY2_LOW		0x55
> +#define LM3533_REG_ALS_BOUNDARY3_HIGH		0x56
> +#define LM3533_REG_ALS_BOUNDARY3_LOW		0x57
> +#define LM3533_REG_ALS_M1_TARGET_0		0x60
> +#define LM3533_REG_ALS_M1_TARGET_1		0x61
> +#define LM3533_REG_ALS_M1_TARGET_2		0x62
> +#define LM3533_REG_ALS_M1_TARGET_3		0x63
> +#define LM3533_REG_ALS_M1_TARGET_4		0x64
> +#define LM3533_REG_ALS_M2_TARGET_0		0x65
> +#define LM3533_REG_ALS_M2_TARGET_1		0x66
> +#define LM3533_REG_ALS_M2_TARGET_2		0x67
> +#define LM3533_REG_ALS_M2_TARGET_3		0x68
> +#define LM3533_REG_ALS_M2_TARGET_4		0x69
> +#define LM3533_REG_ALS_M3_TARGET_0		0x6a
> +#define LM3533_REG_ALS_M3_TARGET_1		0x6b
> +#define LM3533_REG_ALS_M3_TARGET_2		0x6c
> +#define LM3533_REG_ALS_M3_TARGET_3		0x6d
> +#define LM3533_REG_ALS_M3_TARGET_4		0x6e
> +
> +#define LM3533_ALS_ENABLE_MASK			0x01
> +#define LM3533_ALS_INPUT_MODE_MASK		0x02
> +#define LM3533_ALS_INT_ENABLE_MASK		0x01
> +
> +#define LM3533_ALS_ZONE_SHIFT			2
> +#define LM3533_ALS_ZONE_MASK			0x1c
> +
> +#define LM3533_ALS_FLAG_INT_ENABLED		1
> +
> +
> +struct lm3533_als {
> +	struct lm3533 *lm3533;
> +
> +	unsigned long flags;
> +	int irq;
> +
> +	atomic_t zone;
> +};
> +
> +
> +static int lm3533_als_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val1, int *val2, long mask)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 reg;
> +	u8 val;
> +	int ret;
> +
> +	switch (mask) {
> +	case 0:
> +		reg = LM3533_REG_ALS_READ_ADC_RAW;
> +		break;
> +	case IIO_CHAN_INFO_AVERAGE_RAW:
> +		reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	ret = lm3533_read(als->lm3533, reg,&val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to read adc\n");
> +		return ret;
> +	}
> +
> +	*val1 = val;
> +
> +	return IIO_VAL_INT;
> +}
> +
> +static const struct iio_chan_spec lm3533_als_channels[] = {
> +	{
> +		.type = IIO_LIGHT,
> +		.info_mask = IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT,
> +		.channel = 0,
channel doesn't get used unless you also set indexed = 1.
> +	}
> +};
> +
> +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 val;
> +	int ret;
> +
> +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO,&val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to read zone\n");
> +		return ret;
> +	}
> +
> +	val = (val&  LM3533_ALS_ZONE_MASK)>>  LM3533_ALS_ZONE_SHIFT;
> +	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
> +
> +	return 0;
> +}
> +
> +static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
> +{
> +
> +	struct iio_dev *indio_dev = dev_id;
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 zone;
> +	int ret;
> +
> +	/* Clear interrupt by reading the ALS zone register. */
> +	ret = lm3533_als_get_zone(indio_dev,&zone);
> +	if (ret)
> +		goto out;
> +
> +	atomic_set(&als->zone, zone);
> +
> +	iio_push_event(indio_dev,
> +		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
> +					    0,
> +					    IIO_EV_TYPE_THRESH,
> +					    IIO_EV_DIR_EITHER),
> +		       iio_get_time_ns());
> +out:
> +	return IRQ_HANDLED;
> +}
> +
> +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> +	u8 val;
> +	int ret;
> +
> +	if (enable)
> +		val = mask;
> +	else
> +		val = 0;
> +
> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
> +								enable);
extra brackets.
> +	}
> +
> +	return ret;
> +}
> +
> +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> +	u8 val;
> +	int ret;
> +
> +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO,&val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to get int mode\n");
> +		return ret;
> +	}
> +
> +	*enable = !!(val&  mask);
> +
> +	return 0;
> +}
> +
> +static int show_thresh_either_en(struct device *dev,
> +					struct device_attribute *attr,
> +					char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	int enable;
> +	int ret;
> +
> +	if (als->irq) {
> +		ret = lm3533_als_get_int_mode(indio_dev,&enable);
> +		if (ret)
> +			return ret;
> +	} else {
> +		enable = 0;
> +	}
> +
> +	return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
> +}
> +
> +static int store_thresh_either_en(struct device *dev,
> +					struct device_attribute *attr,
> +					const char *buf, size_t len)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	unsigned long enable;
> +	bool int_enabled;
> +	u8 zone;
> +	int ret;
> +
> +	if (!als->irq)
> +		return -EBUSY;
> +
> +	if (kstrtoul(buf, 0,&enable))
> +		return -EINVAL;
> +
> +	int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +
> +	if (enable&&  !int_enabled) {
> +		ret = lm3533_als_get_zone(indio_dev,&zone);
> +		if (ret)
> +			return ret;
> +
> +		atomic_set(&als->zone, zone);
> +
> +		set_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +	}
> +
> +	ret = lm3533_als_set_int_mode(indio_dev, enable);
> +	if (ret) {
> +		if (!int_enabled)
> +			clear_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +
> +		return ret;
> +	}
> +
> +	if (!enable)
> +		clear_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +
> +	return len;
> +}
> +
> +static ssize_t show_zone(struct device *dev,
> +				struct device_attribute *attr, char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 zone;
> +	int ret;
> +
> +	if (test_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags)) {
> +		zone = atomic_read(&als->zone);
> +	} else {
> +		ret = lm3533_als_get_zone(indio_dev,&zone);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
> +}
> +
> +struct lm3533_device_attribute {
> +	struct device_attribute dev_attr;
> +	u8 reg;
> +	u8 max;
> +};
> +
> +#define to_lm3533_dev_attr(_dev_attr) \
> +	container_of(_dev_attr, struct lm3533_device_attribute, dev_attr)
> +
> +static ssize_t show_lm3533_als_reg(struct device *dev,
> +					struct device_attribute *attr,
> +					char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
> +	u8 val;
> +	int ret;
> +
> +	ret = lm3533_read(als->lm3533, lm3533_attr->reg,&val);
> +	if (ret)
> +		return ret;
> +
> +	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
> +}
> +
> +static ssize_t store_lm3533_als_reg(struct device *dev,
> +					struct device_attribute *attr,
> +					const char *buf, size_t len)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
> +	u8 val;
> +	int ret;
> +
> +	if (kstrtou8(buf, 0,&val) || val>  lm3533_attr->max)
> +		return -EINVAL;
> +
> +	ret = lm3533_write(als->lm3533, lm3533_attr->reg, val);
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +#define REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
> +	{ .dev_attr = __ATTR(_name, _mode, _show, _store), \
> +	  .reg = _reg, \
> +	  .max = _max }
> +
> +#define LM3533_REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
> +	struct lm3533_device_attribute lm3533_dev_attr_##_name \
> +		= REG_ATTR(_name, _mode, _show, _store, _reg, _max)
> +
> +#define LM3533_REG_ATTR_RW(_name, _reg, _max) \
> +	LM3533_REG_ATTR(_name, S_IRUGO | S_IWUSR, show_lm3533_als_reg, \
> +					store_lm3533_als_reg, _reg, _max)
> +
> +#define ALS_THRESH_FALLING_ATTR_RW(_nr) \
> +	LM3533_REG_ATTR_RW(in_illuminance_thresh##_nr##_falling_value, \
> +		LM3533_REG_ALS_BOUNDARY##_nr##_LOW, LM3533_ALS_BOUNDARY_MAX)
> +
> +#define ALS_THRESH_RAISING_ATTR_RW(_nr) \
> +	LM3533_REG_ATTR_RW(in_illuminance_thresh##_nr##_raising_value, \
> +		LM3533_REG_ALS_BOUNDARY##_nr##_HIGH, LM3533_ALS_BOUNDARY_MAX)
> +
> +/* ALS Zone thresholds (boundaries)
> + *
> + * in_illuminance_thresh[0-3]_falling_value	0-255
> + * in_illuminance_thresh[0-3]_raising_value	0-255
> + */
> +static ALS_THRESH_FALLING_ATTR_RW(0);
> +static ALS_THRESH_FALLING_ATTR_RW(1);
> +static ALS_THRESH_FALLING_ATTR_RW(2);
> +static ALS_THRESH_FALLING_ATTR_RW(3);
> +
> +static ALS_THRESH_RAISING_ATTR_RW(0);
> +static ALS_THRESH_RAISING_ATTR_RW(1);
> +static ALS_THRESH_RAISING_ATTR_RW(2);
> +static ALS_THRESH_RAISING_ATTR_RW(3);
> +
> +#define LM3533_ALS_ATTR_RO(_name) \
> +	DEVICE_ATTR(in_illuminance_##_name, S_IRUGO, show_##_name, NULL)
> +#define LM3533_ALS_ATTR_RW(_name) \
> +	DEVICE_ATTR(in_illuminance_##_name, S_IRUGO | S_IWUSR , \
> +						show_##_name, store_##_name)
> +
> +/* ALS Zone threshold-event enable
> + *
> + * in_illuminance_thresh_either_en		0,1
> + */
> +static LM3533_ALS_ATTR_RW(thresh_either_en);
> +
> +/* ALS Current Zone
> + *
> + * in_illuminance_zone		0-4
> + */
> +static LM3533_ALS_ATTR_RO(zone);
> +
> +#define ALS_TARGET_ATTR_RW(_mapper, _nr) \
> +	LM3533_REG_ATTR_RW(target##_mapper##_##_nr, \
> +		LM3533_REG_ALS_M##_mapper##_TARGET_##_nr, LM3533_ALS_TARGET_MAX)
> +
> +/* ALS Mapper targets
> + *
> + * target[1-3]_[0-4]		0-255
> + */
> +static ALS_TARGET_ATTR_RW(1, 0);
> +static ALS_TARGET_ATTR_RW(1, 1);
> +static ALS_TARGET_ATTR_RW(1, 2);
> +static ALS_TARGET_ATTR_RW(1, 3);
> +static ALS_TARGET_ATTR_RW(1, 4);
> +
> +static ALS_TARGET_ATTR_RW(2, 0);
> +static ALS_TARGET_ATTR_RW(2, 1);
> +static ALS_TARGET_ATTR_RW(2, 2);
> +static ALS_TARGET_ATTR_RW(2, 3);
> +static ALS_TARGET_ATTR_RW(2, 4);
> +
> +static ALS_TARGET_ATTR_RW(3, 0);
> +static ALS_TARGET_ATTR_RW(3, 1);
> +static ALS_TARGET_ATTR_RW(3, 2);
> +static ALS_TARGET_ATTR_RW(3, 3);
> +static ALS_TARGET_ATTR_RW(3, 4);
> +
> +/* ALS Gain resistor setting
> + *
> + * gain		0-127
> + */
> +static LM3533_REG_ATTR_RW(gain, LM3533_REG_ALS_RESISTOR_SELECT,
> +						LM3533_ALS_RESISTOR_MAX);
> +
> +static struct attribute *lm3533_als_event_attributes[] = {
> +	&dev_attr_in_illuminance_thresh_either_en.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh0_falling_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh0_raising_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh1_falling_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh1_raising_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh2_falling_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh2_raising_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh3_falling_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh3_raising_value.dev_attr.attr,
> +	NULL
> +};
> +
> +static struct attribute_group lm3533_als_event_attribute_group = {
> +	.attrs = lm3533_als_event_attributes
> +};
> +
> +static struct attribute *lm3533_als_attributes[] = {
> +	&lm3533_dev_attr_target1_0.dev_attr.attr,
> +	&lm3533_dev_attr_target1_1.dev_attr.attr,
> +	&lm3533_dev_attr_target1_2.dev_attr.attr,
> +	&lm3533_dev_attr_target1_3.dev_attr.attr,
> +	&lm3533_dev_attr_target1_4.dev_attr.attr,
> +	&lm3533_dev_attr_target2_0.dev_attr.attr,
> +	&lm3533_dev_attr_target2_1.dev_attr.attr,
> +	&lm3533_dev_attr_target2_2.dev_attr.attr,
> +	&lm3533_dev_attr_target2_3.dev_attr.attr,
> +	&lm3533_dev_attr_target2_4.dev_attr.attr,
> +	&lm3533_dev_attr_target3_0.dev_attr.attr,
> +	&lm3533_dev_attr_target3_1.dev_attr.attr,
> +	&lm3533_dev_attr_target3_2.dev_attr.attr,
> +	&lm3533_dev_attr_target3_3.dev_attr.attr,
> +	&lm3533_dev_attr_target3_4.dev_attr.attr,
> +	&lm3533_dev_attr_gain.dev_attr.attr,
> +	&dev_attr_in_illuminance_zone.attr,
> +	NULL
> +};
> +
> +static struct attribute_group lm3533_als_attribute_group = {
> +	.attrs = lm3533_als_attributes
> +};
> +
> +static int __devinit lm3533_als_set_input_mode(struct lm3533 *lm3533,
> +								int pwm_mode)
> +{
> +	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
> +	u8 val;
> +	int ret;
> +
> +	if (pwm_mode)
> +		val = mask;	/* pwm input */
> +	else
> +		val = 0;	/* analog input */
> +
> +	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
> +	if (ret) {
> +		dev_err(lm3533->dev,
> +				"failed to set input mode %d\n", pwm_mode);
> +	}
> +
> +	return ret;
> +}
> +
> +static int __devinit lm3533_als_enable(struct lm3533 *lm3533)
> +{
> +	u8 mask = LM3533_ALS_ENABLE_MASK;
> +	int ret;
> +
> +	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
> +	if (ret)
> +		dev_err(lm3533->dev, "failed to enable ALS\n");
> +
> +	return ret;
> +}
> +
> +static int lm3533_als_disable(struct lm3533 *lm3533)
> +{
> +	u8 mask = LM3533_ALS_ENABLE_MASK;
> +	int ret;
> +
> +	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, 0, mask);
> +	if (ret)
> +		dev_err(lm3533->dev, "failed to disable ALS\n");
> +
> +	return ret;
> +}
> +
> +static const struct iio_info lm3533_als_info = {
> +	.attrs		=&lm3533_als_attribute_group,
> +	.event_attrs	=&lm3533_als_event_attribute_group,
> +	.driver_module	= THIS_MODULE,
> +	.read_raw	=&lm3533_als_read_raw,
> +};
> +
> +static int __devinit lm3533_als_probe(struct platform_device *pdev)
> +{
> +	struct lm3533 *lm3533;
> +	struct lm3533_als_platform_data *pdata;
> +	struct lm3533_als *als;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +
> +	dev_dbg(&pdev->dev, "%s\n", __func__);
> +
> +	lm3533 = dev_get_drvdata(pdev->dev.parent);
> +	if (!lm3533)
> +		return -EINVAL;
> +
> +	pdata = pdev->dev.platform_data;
> +	if (!pdata) {
> +		dev_err(&pdev->dev, "no platform data\n");
> +		return -EINVAL;
> +	}
> +
> +	indio_dev = iio_allocate_device(sizeof(*als));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	indio_dev->info =&lm3533_als_info;
> +	indio_dev->channels = lm3533_als_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
> +	indio_dev->name = "lm3533-als";
> +	indio_dev->dev.parent = pdev->dev.parent;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	als = iio_priv(indio_dev);
> +	als->lm3533 = lm3533;
> +	als->irq = lm3533->irq;
> +	atomic_set(&als->zone, 0);
> +
> +	if (als->irq) {
> +		ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
> +					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> +					indio_dev->name, indio_dev);
> +		if (ret) {
> +			dev_err(&indio_dev->dev, "failed to request irq %d\n",
> +								lm3533->irq);
> +			goto err_free;
> +		}
> +	}
> +
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	ret = lm3533_als_set_input_mode(lm3533, pdata->pwm_mode);
> +	if (ret)
> +		goto err_free;
> +
> +	ret = lm3533_als_enable(lm3533);
> +	if (ret)
> +		goto err_free;
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to register ALS\n");
> +		goto err_disable;
> +	}
> +
> +	return 0;
> +
> +err_disable:
> +	lm3533_als_disable(lm3533);
> +err_free:
> +	iio_free_device(indio_dev);
> +
> +	return ret;
> +}
> +
> +static int __devexit lm3533_als_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +
> +	dev_dbg(&pdev->dev, "%s\n", __func__);
> +
> +	iio_device_unregister(indio_dev);
> +	lm3533_als_disable(als->lm3533);
> +	if (als->irq)
> +		free_irq(als->irq, indio_dev);
> +	iio_free_device(indio_dev);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver lm3533_als_driver = {
> +	.driver = {
> +		.name = "lm3533-als",
> +		.owner = THIS_MODULE,
> +	},
> +	.probe		= lm3533_als_probe,
> +	.remove		= __devexit_p(lm3533_als_remove),
> +};
> +module_platform_driver(lm3533_als_driver);
> +
> +MODULE_AUTHOR("Johan Hovold<jhovold@gmail.com>");
> +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:lm3533-als");


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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
@ 2012-05-03 11:40       ` Jonathan Cameron
  0 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-03 11:40 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Arnd Bergmann, Andrew Morton,
	Mark Brown, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On 5/3/2012 11:26 AM, Johan Hovold wrote:
> Add sub-driver for the ambient light sensor interface on National
> Semiconductor / TI LM3533 lighting power chips.
>
> The sensor interface can be used to control the LEDs and backlights of
> the chip through defining five light zones and three sets of
> corresponding brightness target levels.
>
> The driver provides raw and mean adc readings along with the current
> light zone through sysfs. A threshold event can be generated on zone
> changes.
Code is fine.  Pretty much all my comments are to do with the interface.
>
> Signed-off-by: Johan Hovold<jhovold@gmail.com>
> ---
>
> v2:
>   - reimplement using iio
>   - add sysfs-ABI documentation
>
>
>   .../Documentation/sysfs-bus-iio-light-lm3533-als   |   62 ++
>   drivers/staging/iio/light/Kconfig                  |   16 +
>   drivers/staging/iio/light/Makefile                 |    1 +
>   drivers/staging/iio/light/lm3533-als.c             |  617 ++++++++++++++++++++
>   4 files changed, 696 insertions(+), 0 deletions(-)
>   create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>   create mode 100644 drivers/staging/iio/light/lm3533-als.c
>
> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> new file mode 100644
> index 0000000..9849d14
> --- /dev/null
> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> @@ -0,0 +1,62 @@
> +What:		/sys/bus/iio/devices/iio:deviceX/gain
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Set the ALS gain-resistor setting (0..127) for analog input
> +		mode, where
> +
> +		0000000 - ALS input is high impedance
> +		0000001 - 200kOhm (10uA at 2V full-scale)
> +		0000010 - 100kOhm (20uA at 2V full-scale)
> +		...
> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
> +
> +		R_als = 2V / (10uA * gain)	(gain>  0)
Firstly, no magic numbers.  These are definitely magic.  Secondly see
in_illuminance0_scale for a suitable existing attribute.
> +
> +What:		/sys/bus/iio/devices/iio:deviceX/illuminance_zone
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Get the current light zone (0..4) as defined by the
> +		in_illuminance_thresh[n]_{falling,rising} thresholds.
Hmm.. definitely have an in prefix, beyond that I'm not sure what the 
cleanest
interface will be for this.   Could extend the event codes to deal with the
zone index.  Slightly tricky as the channel could already be modified so
chan2 isn't necesarily available.
> +
> +What:		/sys/.../events/in_illuminance_thresh_either_en
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Event generated when channel passes one of the four threshold
> +		in either direction (rising|falling) and a zone change occurs.
> +		The corresponding light zone can be read from
> +		illuminance_zone.
> +
> +What:		/sys/.../events/illuminance_thresh0_falling_value
hmm.. every time you think you are making progress a new and exciting 
device comes
along requiring the abi to be extended.
in_illuminanceX_threshY_rising_value
in_illuminanceX_threshY_falling_value
should do with appropriate description.
> +What:		/sys/.../events/illuminance_thresh0_raising_value
> +What:		/sys/.../events/illuminance_thresh1_falling_value
> +What:		/sys/.../events/illuminance_thresh1_raising_value
> +What:		/sys/.../events/illuminance_thresh2_falling_value
> +What:		/sys/.../events/illuminance_thresh2_raising_value
> +What:		/sys/.../events/illuminance_thresh3_falling_value
> +What:		/sys/.../events/illuminance_thresh3_raising_value
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Specifies the value of threshold that the device is comparing
> +		against for the events enabled by
> +		in_illuminance_thresh_either_en, and defines the
> +		the five light zones.
> +
> +		These thresholds correspond to the eight zone-boundary
> +		registers (boundary[n]_{low,high}).
> +
This interface is going to take some thought.  We have 
in_illuminance0_target at the
moment, so I guess we can add a zoning concept to that...
> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Set the target brightness for ALS-mapper m in light zone n
> +		(0..255), where m in 1..3 and n in 0..4.
Don't suppose you could do a quick summary of what these zones are and 
why there
are 3 ALS-mappers?  I'm not getting terribly far on a quick look at the 
datasheet!
> diff --git a/drivers/staging/iio/light/Kconfig b/drivers/staging/iio/light/Kconfig
> index e7e9159..263e44a 100644
> --- a/drivers/staging/iio/light/Kconfig
> +++ b/drivers/staging/iio/light/Kconfig
> @@ -24,6 +24,22 @@ config SENSORS_TSL2563
>   	 This driver can also be built as a module.  If so, the module
>   	 will be called tsl2563.
>
> +config SENSORS_LM3533
> +	tristate "LM3533 ambient light sensor"
> +	depends on MFD_LM3533
> +	help
> +	  If you say yes here you get support for the ambient light sensor
> +	  interface on National Semiconductor / TI LM3533 Lighting Power
> +	  chips.
> +
> +	  The sensor interface can be used to control the LEDs and backlights
> +	  of the chip through defining five light zones and three sets of
> +	  corresponding brightness target levels.
> +
> +	  The driver provides raw and mean adc readings along with the current
> +	  light zone through sysfs. A threshold event can be generated on zone
> +	  changes.
> +
>   config TSL2583
>   	tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters"
>   	depends on I2C
> diff --git a/drivers/staging/iio/light/Makefile b/drivers/staging/iio/light/Makefile
> index 3011fbf..16a60a2 100644
> --- a/drivers/staging/iio/light/Makefile
> +++ b/drivers/staging/iio/light/Makefile
> @@ -4,4 +4,5 @@
>
>   obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o
>   obj-$(CONFIG_SENSORS_ISL29018)	+= isl29018.o
> +obj-$(CONFIG_SENSORS_LM3533)	+= lm3533-als.o
>   obj-$(CONFIG_TSL2583)	+= tsl2583.o
> diff --git a/drivers/staging/iio/light/lm3533-als.c b/drivers/staging/iio/light/lm3533-als.c
> new file mode 100644
> index 0000000..e2c9be6
> --- /dev/null
> +++ b/drivers/staging/iio/light/lm3533-als.c
> @@ -0,0 +1,617 @@
> +/*
> + * lm3533-als.c -- LM3533 Ambient Light Sensor driver
> + *
> + * Copyright (C) 2011-2012 Texas Instruments
> + *
> + * Author: Johan Hovold<jhovold@gmail.com>
> + *
> + * 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.
> + */
> +
> +#include<linux/atomic.h>
> +#include<linux/fs.h>
> +#include<linux/interrupt.h>
> +#include<linux/io.h>
> +#include<linux/module.h>
> +#include<linux/mfd/core.h>
> +#include<linux/platform_device.h>
> +#include<linux/slab.h>
> +#include<linux/uaccess.h>
> +
> +#include<linux/mfd/lm3533.h>
> +
> +#include "../events.h"
> +#include "../iio.h"
This will need to go through the staging-next tree.  In there these
headers have moved.
> +
> +
> +#define LM3533_ALS_RESISTOR_MAX			0x7f
> +#define LM3533_ALS_ADC_MAX			0xff
> +#define LM3533_ALS_BOUNDARY_MAX			LM3533_ALS_ADC_MAX
> +#define LM3533_ALS_TARGET_MAX			LM3533_ALS_ADC_MAX
> +#define LM3533_ALS_ZONE_MAX			4
> +
> +#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
> +#define LM3533_REG_ALS_CONF			0x31
> +#define LM3533_REG_ALS_ZONE_INFO		0x34
> +#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x37
> +#define LM3533_REG_ALS_READ_ADC_RAW		0x38
> +#define LM3533_REG_ALS_BOUNDARY0_HIGH		0x50
> +#define LM3533_REG_ALS_BOUNDARY0_LOW		0x51
> +#define LM3533_REG_ALS_BOUNDARY1_HIGH		0x52
> +#define LM3533_REG_ALS_BOUNDARY1_LOW		0x53
> +#define LM3533_REG_ALS_BOUNDARY2_HIGH		0x54
> +#define LM3533_REG_ALS_BOUNDARY2_LOW		0x55
> +#define LM3533_REG_ALS_BOUNDARY3_HIGH		0x56
> +#define LM3533_REG_ALS_BOUNDARY3_LOW		0x57
> +#define LM3533_REG_ALS_M1_TARGET_0		0x60
> +#define LM3533_REG_ALS_M1_TARGET_1		0x61
> +#define LM3533_REG_ALS_M1_TARGET_2		0x62
> +#define LM3533_REG_ALS_M1_TARGET_3		0x63
> +#define LM3533_REG_ALS_M1_TARGET_4		0x64
> +#define LM3533_REG_ALS_M2_TARGET_0		0x65
> +#define LM3533_REG_ALS_M2_TARGET_1		0x66
> +#define LM3533_REG_ALS_M2_TARGET_2		0x67
> +#define LM3533_REG_ALS_M2_TARGET_3		0x68
> +#define LM3533_REG_ALS_M2_TARGET_4		0x69
> +#define LM3533_REG_ALS_M3_TARGET_0		0x6a
> +#define LM3533_REG_ALS_M3_TARGET_1		0x6b
> +#define LM3533_REG_ALS_M3_TARGET_2		0x6c
> +#define LM3533_REG_ALS_M3_TARGET_3		0x6d
> +#define LM3533_REG_ALS_M3_TARGET_4		0x6e
> +
> +#define LM3533_ALS_ENABLE_MASK			0x01
> +#define LM3533_ALS_INPUT_MODE_MASK		0x02
> +#define LM3533_ALS_INT_ENABLE_MASK		0x01
> +
> +#define LM3533_ALS_ZONE_SHIFT			2
> +#define LM3533_ALS_ZONE_MASK			0x1c
> +
> +#define LM3533_ALS_FLAG_INT_ENABLED		1
> +
> +
> +struct lm3533_als {
> +	struct lm3533 *lm3533;
> +
> +	unsigned long flags;
> +	int irq;
> +
> +	atomic_t zone;
> +};
> +
> +
> +static int lm3533_als_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val1, int *val2, long mask)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 reg;
> +	u8 val;
> +	int ret;
> +
> +	switch (mask) {
> +	case 0:
> +		reg = LM3533_REG_ALS_READ_ADC_RAW;
> +		break;
> +	case IIO_CHAN_INFO_AVERAGE_RAW:
> +		reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	ret = lm3533_read(als->lm3533, reg,&val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to read adc\n");
> +		return ret;
> +	}
> +
> +	*val1 = val;
> +
> +	return IIO_VAL_INT;
> +}
> +
> +static const struct iio_chan_spec lm3533_als_channels[] = {
> +	{
> +		.type = IIO_LIGHT,
> +		.info_mask = IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT,
> +		.channel = 0,
channel doesn't get used unless you also set indexed = 1.
> +	}
> +};
> +
> +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 val;
> +	int ret;
> +
> +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO,&val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to read zone\n");
> +		return ret;
> +	}
> +
> +	val = (val&  LM3533_ALS_ZONE_MASK)>>  LM3533_ALS_ZONE_SHIFT;
> +	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
> +
> +	return 0;
> +}
> +
> +static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
> +{
> +
> +	struct iio_dev *indio_dev = dev_id;
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 zone;
> +	int ret;
> +
> +	/* Clear interrupt by reading the ALS zone register. */
> +	ret = lm3533_als_get_zone(indio_dev,&zone);
> +	if (ret)
> +		goto out;
> +
> +	atomic_set(&als->zone, zone);
> +
> +	iio_push_event(indio_dev,
> +		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
> +					    0,
> +					    IIO_EV_TYPE_THRESH,
> +					    IIO_EV_DIR_EITHER),
> +		       iio_get_time_ns());
> +out:
> +	return IRQ_HANDLED;
> +}
> +
> +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> +	u8 val;
> +	int ret;
> +
> +	if (enable)
> +		val = mask;
> +	else
> +		val = 0;
> +
> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
> +								enable);
extra brackets.
> +	}
> +
> +	return ret;
> +}
> +
> +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> +	u8 val;
> +	int ret;
> +
> +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO,&val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to get int mode\n");
> +		return ret;
> +	}
> +
> +	*enable = !!(val&  mask);
> +
> +	return 0;
> +}
> +
> +static int show_thresh_either_en(struct device *dev,
> +					struct device_attribute *attr,
> +					char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	int enable;
> +	int ret;
> +
> +	if (als->irq) {
> +		ret = lm3533_als_get_int_mode(indio_dev,&enable);
> +		if (ret)
> +			return ret;
> +	} else {
> +		enable = 0;
> +	}
> +
> +	return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
> +}
> +
> +static int store_thresh_either_en(struct device *dev,
> +					struct device_attribute *attr,
> +					const char *buf, size_t len)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	unsigned long enable;
> +	bool int_enabled;
> +	u8 zone;
> +	int ret;
> +
> +	if (!als->irq)
> +		return -EBUSY;
> +
> +	if (kstrtoul(buf, 0,&enable))
> +		return -EINVAL;
> +
> +	int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +
> +	if (enable&&  !int_enabled) {
> +		ret = lm3533_als_get_zone(indio_dev,&zone);
> +		if (ret)
> +			return ret;
> +
> +		atomic_set(&als->zone, zone);
> +
> +		set_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +	}
> +
> +	ret = lm3533_als_set_int_mode(indio_dev, enable);
> +	if (ret) {
> +		if (!int_enabled)
> +			clear_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +
> +		return ret;
> +	}
> +
> +	if (!enable)
> +		clear_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +
> +	return len;
> +}
> +
> +static ssize_t show_zone(struct device *dev,
> +				struct device_attribute *attr, char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 zone;
> +	int ret;
> +
> +	if (test_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags)) {
> +		zone = atomic_read(&als->zone);
> +	} else {
> +		ret = lm3533_als_get_zone(indio_dev,&zone);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
> +}
> +
> +struct lm3533_device_attribute {
> +	struct device_attribute dev_attr;
> +	u8 reg;
> +	u8 max;
> +};
> +
> +#define to_lm3533_dev_attr(_dev_attr) \
> +	container_of(_dev_attr, struct lm3533_device_attribute, dev_attr)
> +
> +static ssize_t show_lm3533_als_reg(struct device *dev,
> +					struct device_attribute *attr,
> +					char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
> +	u8 val;
> +	int ret;
> +
> +	ret = lm3533_read(als->lm3533, lm3533_attr->reg,&val);
> +	if (ret)
> +		return ret;
> +
> +	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
> +}
> +
> +static ssize_t store_lm3533_als_reg(struct device *dev,
> +					struct device_attribute *attr,
> +					const char *buf, size_t len)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
> +	u8 val;
> +	int ret;
> +
> +	if (kstrtou8(buf, 0,&val) || val>  lm3533_attr->max)
> +		return -EINVAL;
> +
> +	ret = lm3533_write(als->lm3533, lm3533_attr->reg, val);
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +#define REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
> +	{ .dev_attr = __ATTR(_name, _mode, _show, _store), \
> +	  .reg = _reg, \
> +	  .max = _max }
> +
> +#define LM3533_REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
> +	struct lm3533_device_attribute lm3533_dev_attr_##_name \
> +		= REG_ATTR(_name, _mode, _show, _store, _reg, _max)
> +
> +#define LM3533_REG_ATTR_RW(_name, _reg, _max) \
> +	LM3533_REG_ATTR(_name, S_IRUGO | S_IWUSR, show_lm3533_als_reg, \
> +					store_lm3533_als_reg, _reg, _max)
> +
> +#define ALS_THRESH_FALLING_ATTR_RW(_nr) \
> +	LM3533_REG_ATTR_RW(in_illuminance_thresh##_nr##_falling_value, \
> +		LM3533_REG_ALS_BOUNDARY##_nr##_LOW, LM3533_ALS_BOUNDARY_MAX)
> +
> +#define ALS_THRESH_RAISING_ATTR_RW(_nr) \
> +	LM3533_REG_ATTR_RW(in_illuminance_thresh##_nr##_raising_value, \
> +		LM3533_REG_ALS_BOUNDARY##_nr##_HIGH, LM3533_ALS_BOUNDARY_MAX)
> +
> +/* ALS Zone thresholds (boundaries)
> + *
> + * in_illuminance_thresh[0-3]_falling_value	0-255
> + * in_illuminance_thresh[0-3]_raising_value	0-255
> + */
> +static ALS_THRESH_FALLING_ATTR_RW(0);
> +static ALS_THRESH_FALLING_ATTR_RW(1);
> +static ALS_THRESH_FALLING_ATTR_RW(2);
> +static ALS_THRESH_FALLING_ATTR_RW(3);
> +
> +static ALS_THRESH_RAISING_ATTR_RW(0);
> +static ALS_THRESH_RAISING_ATTR_RW(1);
> +static ALS_THRESH_RAISING_ATTR_RW(2);
> +static ALS_THRESH_RAISING_ATTR_RW(3);
> +
> +#define LM3533_ALS_ATTR_RO(_name) \
> +	DEVICE_ATTR(in_illuminance_##_name, S_IRUGO, show_##_name, NULL)
> +#define LM3533_ALS_ATTR_RW(_name) \
> +	DEVICE_ATTR(in_illuminance_##_name, S_IRUGO | S_IWUSR , \
> +						show_##_name, store_##_name)
> +
> +/* ALS Zone threshold-event enable
> + *
> + * in_illuminance_thresh_either_en		0,1
> + */
> +static LM3533_ALS_ATTR_RW(thresh_either_en);
> +
> +/* ALS Current Zone
> + *
> + * in_illuminance_zone		0-4
> + */
> +static LM3533_ALS_ATTR_RO(zone);
> +
> +#define ALS_TARGET_ATTR_RW(_mapper, _nr) \
> +	LM3533_REG_ATTR_RW(target##_mapper##_##_nr, \
> +		LM3533_REG_ALS_M##_mapper##_TARGET_##_nr, LM3533_ALS_TARGET_MAX)
> +
> +/* ALS Mapper targets
> + *
> + * target[1-3]_[0-4]		0-255
> + */
> +static ALS_TARGET_ATTR_RW(1, 0);
> +static ALS_TARGET_ATTR_RW(1, 1);
> +static ALS_TARGET_ATTR_RW(1, 2);
> +static ALS_TARGET_ATTR_RW(1, 3);
> +static ALS_TARGET_ATTR_RW(1, 4);
> +
> +static ALS_TARGET_ATTR_RW(2, 0);
> +static ALS_TARGET_ATTR_RW(2, 1);
> +static ALS_TARGET_ATTR_RW(2, 2);
> +static ALS_TARGET_ATTR_RW(2, 3);
> +static ALS_TARGET_ATTR_RW(2, 4);
> +
> +static ALS_TARGET_ATTR_RW(3, 0);
> +static ALS_TARGET_ATTR_RW(3, 1);
> +static ALS_TARGET_ATTR_RW(3, 2);
> +static ALS_TARGET_ATTR_RW(3, 3);
> +static ALS_TARGET_ATTR_RW(3, 4);
> +
> +/* ALS Gain resistor setting
> + *
> + * gain		0-127
> + */
> +static LM3533_REG_ATTR_RW(gain, LM3533_REG_ALS_RESISTOR_SELECT,
> +						LM3533_ALS_RESISTOR_MAX);
> +
> +static struct attribute *lm3533_als_event_attributes[] = {
> +	&dev_attr_in_illuminance_thresh_either_en.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh0_falling_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh0_raising_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh1_falling_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh1_raising_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh2_falling_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh2_raising_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh3_falling_value.dev_attr.attr,
> +	&lm3533_dev_attr_in_illuminance_thresh3_raising_value.dev_attr.attr,
> +	NULL
> +};
> +
> +static struct attribute_group lm3533_als_event_attribute_group = {
> +	.attrs = lm3533_als_event_attributes
> +};
> +
> +static struct attribute *lm3533_als_attributes[] = {
> +	&lm3533_dev_attr_target1_0.dev_attr.attr,
> +	&lm3533_dev_attr_target1_1.dev_attr.attr,
> +	&lm3533_dev_attr_target1_2.dev_attr.attr,
> +	&lm3533_dev_attr_target1_3.dev_attr.attr,
> +	&lm3533_dev_attr_target1_4.dev_attr.attr,
> +	&lm3533_dev_attr_target2_0.dev_attr.attr,
> +	&lm3533_dev_attr_target2_1.dev_attr.attr,
> +	&lm3533_dev_attr_target2_2.dev_attr.attr,
> +	&lm3533_dev_attr_target2_3.dev_attr.attr,
> +	&lm3533_dev_attr_target2_4.dev_attr.attr,
> +	&lm3533_dev_attr_target3_0.dev_attr.attr,
> +	&lm3533_dev_attr_target3_1.dev_attr.attr,
> +	&lm3533_dev_attr_target3_2.dev_attr.attr,
> +	&lm3533_dev_attr_target3_3.dev_attr.attr,
> +	&lm3533_dev_attr_target3_4.dev_attr.attr,
> +	&lm3533_dev_attr_gain.dev_attr.attr,
> +	&dev_attr_in_illuminance_zone.attr,
> +	NULL
> +};
> +
> +static struct attribute_group lm3533_als_attribute_group = {
> +	.attrs = lm3533_als_attributes
> +};
> +
> +static int __devinit lm3533_als_set_input_mode(struct lm3533 *lm3533,
> +								int pwm_mode)
> +{
> +	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
> +	u8 val;
> +	int ret;
> +
> +	if (pwm_mode)
> +		val = mask;	/* pwm input */
> +	else
> +		val = 0;	/* analog input */
> +
> +	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
> +	if (ret) {
> +		dev_err(lm3533->dev,
> +				"failed to set input mode %d\n", pwm_mode);
> +	}
> +
> +	return ret;
> +}
> +
> +static int __devinit lm3533_als_enable(struct lm3533 *lm3533)
> +{
> +	u8 mask = LM3533_ALS_ENABLE_MASK;
> +	int ret;
> +
> +	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, mask, mask);
> +	if (ret)
> +		dev_err(lm3533->dev, "failed to enable ALS\n");
> +
> +	return ret;
> +}
> +
> +static int lm3533_als_disable(struct lm3533 *lm3533)
> +{
> +	u8 mask = LM3533_ALS_ENABLE_MASK;
> +	int ret;
> +
> +	ret = lm3533_update(lm3533, LM3533_REG_ALS_CONF, 0, mask);
> +	if (ret)
> +		dev_err(lm3533->dev, "failed to disable ALS\n");
> +
> +	return ret;
> +}
> +
> +static const struct iio_info lm3533_als_info = {
> +	.attrs		=&lm3533_als_attribute_group,
> +	.event_attrs	=&lm3533_als_event_attribute_group,
> +	.driver_module	= THIS_MODULE,
> +	.read_raw	=&lm3533_als_read_raw,
> +};
> +
> +static int __devinit lm3533_als_probe(struct platform_device *pdev)
> +{
> +	struct lm3533 *lm3533;
> +	struct lm3533_als_platform_data *pdata;
> +	struct lm3533_als *als;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +
> +	dev_dbg(&pdev->dev, "%s\n", __func__);
> +
> +	lm3533 = dev_get_drvdata(pdev->dev.parent);
> +	if (!lm3533)
> +		return -EINVAL;
> +
> +	pdata = pdev->dev.platform_data;
> +	if (!pdata) {
> +		dev_err(&pdev->dev, "no platform data\n");
> +		return -EINVAL;
> +	}
> +
> +	indio_dev = iio_allocate_device(sizeof(*als));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	indio_dev->info =&lm3533_als_info;
> +	indio_dev->channels = lm3533_als_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
> +	indio_dev->name = "lm3533-als";
> +	indio_dev->dev.parent = pdev->dev.parent;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	als = iio_priv(indio_dev);
> +	als->lm3533 = lm3533;
> +	als->irq = lm3533->irq;
> +	atomic_set(&als->zone, 0);
> +
> +	if (als->irq) {
> +		ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
> +					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> +					indio_dev->name, indio_dev);
> +		if (ret) {
> +			dev_err(&indio_dev->dev, "failed to request irq %d\n",
> +								lm3533->irq);
> +			goto err_free;
> +		}
> +	}
> +
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	ret = lm3533_als_set_input_mode(lm3533, pdata->pwm_mode);
> +	if (ret)
> +		goto err_free;
> +
> +	ret = lm3533_als_enable(lm3533);
> +	if (ret)
> +		goto err_free;
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to register ALS\n");
> +		goto err_disable;
> +	}
> +
> +	return 0;
> +
> +err_disable:
> +	lm3533_als_disable(lm3533);
> +err_free:
> +	iio_free_device(indio_dev);
> +
> +	return ret;
> +}
> +
> +static int __devexit lm3533_als_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +
> +	dev_dbg(&pdev->dev, "%s\n", __func__);
> +
> +	iio_device_unregister(indio_dev);
> +	lm3533_als_disable(als->lm3533);
> +	if (als->irq)
> +		free_irq(als->irq, indio_dev);
> +	iio_free_device(indio_dev);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver lm3533_als_driver = {
> +	.driver = {
> +		.name = "lm3533-als",
> +		.owner = THIS_MODULE,
> +	},
> +	.probe		= lm3533_als_probe,
> +	.remove		= __devexit_p(lm3533_als_remove),
> +};
> +module_platform_driver(lm3533_als_driver);
> +
> +MODULE_AUTHOR("Johan Hovold<jhovold@gmail.com>");
> +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:lm3533-als");


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

* Re: [PATCH v2 3/4] leds: add LM3533 LED driver
  2012-05-03 10:43       ` Mark Brown
@ 2012-05-03 11:50         ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 11:50 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 11:43:44AM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 12:26:38PM +0200, Johan Hovold wrote:
> 
> > +What:		/sys/class/leds/<led>/risetime
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Set the pattern generator fall and rise times (0..7), where
> > +
> > +		0 - 2048 us
> > +		1 - 262 ms
> > +		2 - 524 ms
> > +		3 - 1.049 s
> > +		4 - 2.097 s
> > +		5 - 4.194 s
> > +		6 - 8.389 s
> > +		7 - 16.78 s
> > +
> 
> Shouldn't these be controlled by led_blink_set() rather than a custom
> ABI?

led_blink_set controls the on/off times, but the LM3533 has the two
additional rise and fall-time settings which determine the transition
time between these states.

> > +What:		/sys/class/leds/<led>/id
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Get the id of this led (0..3).
> > +
> 
> This should just be a generic LED subsystem thing?

It's related to the output mapping discussed in my previous mail. The
four logical LEDs (0..3) can be used to control either (or all) of the
five low-voltage output. This attribute provides the identity of the
class devices (logical LEDs) which can then be used in the output
mapping (done in the parent device). These id's have been chosen to
correspond to the MFD-id's, but are really device specific.
 
> > +What:		/sys/class/leds/<led>/max_current
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Set the full-scale current I_{LED_FULLSCALE} (0..31), where
> > +
> > +		I_{LED_FULLSCALE} = 5mA + max_current * 0.8mA
> > +
> 
> Shouldn't this be set by platform data, the maximum current you can push
> through the LEDs seems like a board dependant thing which won't change
> dynamically at runtime.  The brightness can already be varied.

I fully agree and it is possible to set via the platform data for that
reason. The end-customer, however, insisted that even this setting be
available through sysfs to facilitate their integration and testing.

I'd be willing drop this attribute if requested, as it would only be used
during integration and could easily be added back by the end-customer if
needed.

> It'd also be nicer if the kernel did the calculation for the user.

If it was something that was going to be changed a lot, then yes,
perhaps. But as you point out above, this is generally a fixed setting
set using platform data once by integrators that will have access to
the datasheet and it's current table.

Thanks,
Johan

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

* Re: [PATCH v2 3/4] leds: add LM3533 LED driver
@ 2012-05-03 11:50         ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 11:50 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 11:43:44AM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 12:26:38PM +0200, Johan Hovold wrote:
> 
> > +What:		/sys/class/leds/<led>/risetime
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Set the pattern generator fall and rise times (0..7), where
> > +
> > +		0 - 2048 us
> > +		1 - 262 ms
> > +		2 - 524 ms
> > +		3 - 1.049 s
> > +		4 - 2.097 s
> > +		5 - 4.194 s
> > +		6 - 8.389 s
> > +		7 - 16.78 s
> > +
> 
> Shouldn't these be controlled by led_blink_set() rather than a custom
> ABI?

led_blink_set controls the on/off times, but the LM3533 has the two
additional rise and fall-time settings which determine the transition
time between these states.

> > +What:		/sys/class/leds/<led>/id
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Get the id of this led (0..3).
> > +
> 
> This should just be a generic LED subsystem thing?

It's related to the output mapping discussed in my previous mail. The
four logical LEDs (0..3) can be used to control either (or all) of the
five low-voltage output. This attribute provides the identity of the
class devices (logical LEDs) which can then be used in the output
mapping (done in the parent device). These id's have been chosen to
correspond to the MFD-id's, but are really device specific.
 
> > +What:		/sys/class/leds/<led>/max_current
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Set the full-scale current I_{LED_FULLSCALE} (0..31), where
> > +
> > +		I_{LED_FULLSCALE} = 5mA + max_current * 0.8mA
> > +
> 
> Shouldn't this be set by platform data, the maximum current you can push
> through the LEDs seems like a board dependant thing which won't change
> dynamically at runtime.  The brightness can already be varied.

I fully agree and it is possible to set via the platform data for that
reason. The end-customer, however, insisted that even this setting be
available through sysfs to facilitate their integration and testing.

I'd be willing drop this attribute if requested, as it would only be used
during integration and could easily be added back by the end-customer if
needed.

> It'd also be nicer if the kernel did the calculation for the user.

If it was something that was going to be changed a lot, then yes,
perhaps. But as you point out above, this is generally a fixed setting
set using platform data once by integrators that will have access to
the datasheet and it's current table.

Thanks,
Johan

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

* Re: [PATCH v2 3/4] leds: add LM3533 LED driver
  2012-05-03 11:50         ` Johan Hovold
@ 2012-05-03 14:51           ` Mark Brown
  -1 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 14:51 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 1560 bytes --]

On Thu, May 03, 2012 at 01:50:59PM +0200, Johan Hovold wrote:
> On Thu, May 03, 2012 at 11:43:44AM +0100, Mark Brown wrote:

> > > +		5 - 4.194 s
> > > +		6 - 8.389 s
> > > +		7 - 16.78 s

> > Shouldn't these be controlled by led_blink_set() rather than a custom
> > ABI?

> led_blink_set controls the on/off times, but the LM3533 has the two
> additional rise and fall-time settings which determine the transition
> time between these states.

Hrm.  In that case these rise times are very large - I'd expect them to
cause issues with led_set_blink() users?  Though actually I suspect the
solution here is to pull these out into the framework later; we can
probably simulate reasonably in software with a lot of brightness
variable LEDs.

> > > +What:		/sys/class/leds/<led>/max_current

> > Shouldn't this be set by platform data, the maximum current you can push
> > through the LEDs seems like a board dependant thing which won't change
> > dynamically at runtime.  The brightness can already be varied.

> I fully agree and it is possible to set via the platform data for that
> reason. The end-customer, however, insisted that even this setting be
> available through sysfs to facilitate their integration and testing.

> I'd be willing drop this attribute if requested, as it would only be used
> during integration and could easily be added back by the end-customer if
> needed.

I'd strongly suggest removing this for mainline.  If it's present it
should at least be limited to the maximum specified in platform data
(just for safety if nothing else).

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 3/4] leds: add LM3533 LED driver
@ 2012-05-03 14:51           ` Mark Brown
  0 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 14:51 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 1560 bytes --]

On Thu, May 03, 2012 at 01:50:59PM +0200, Johan Hovold wrote:
> On Thu, May 03, 2012 at 11:43:44AM +0100, Mark Brown wrote:

> > > +		5 - 4.194 s
> > > +		6 - 8.389 s
> > > +		7 - 16.78 s

> > Shouldn't these be controlled by led_blink_set() rather than a custom
> > ABI?

> led_blink_set controls the on/off times, but the LM3533 has the two
> additional rise and fall-time settings which determine the transition
> time between these states.

Hrm.  In that case these rise times are very large - I'd expect them to
cause issues with led_set_blink() users?  Though actually I suspect the
solution here is to pull these out into the framework later; we can
probably simulate reasonably in software with a lot of brightness
variable LEDs.

> > > +What:		/sys/class/leds/<led>/max_current

> > Shouldn't this be set by platform data, the maximum current you can push
> > through the LEDs seems like a board dependant thing which won't change
> > dynamically at runtime.  The brightness can already be varied.

> I fully agree and it is possible to set via the platform data for that
> reason. The end-customer, however, insisted that even this setting be
> available through sysfs to facilitate their integration and testing.

> I'd be willing drop this attribute if requested, as it would only be used
> during integration and could easily be added back by the end-customer if
> needed.

I'd strongly suggest removing this for mainline.  If it's present it
should at least be limited to the maximum specified in platform data
(just for safety if nothing else).

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-03 11:38           ` Mark Brown
@ 2012-05-03 15:00             ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 15:00 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 12:38:02PM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 01:28:03PM +0200, Johan Hovold wrote:
> > On Thu, May 03, 2012 at 11:38:48AM +0100, Mark Brown wrote:
> 
> > > I'd expect you can drop these log messages, if there's stuff like this
> > > missing we should add it to regmap.  At the minute the regmap logging is
> > > via trace points rather than debug logs as you can leave them enabled
> > > all the time.
> 
> > If such debugging is added to regmap we still need a way to enable them
> > per driver (or rather regmap) to not clutter the logs.
> 
> This is one of the reasons why we currently use tracepoints (they just
> don't have this issue as they're trivial to filter), though
> adding some sort of infrastructure for it ought not to be too difficult
> even if it's just at the regmap level.

So a /sys/kernel/debug/regmap/<device>/io_printk attribute (with a
better name) to enable debug printks in io paths
(regmap*{read,write,update} outside of mutex) in regmap.c would be
acceptable?

> > These three dev_dbg statements are extremely useful during debugging /
> > development especially in combination with the other dynamic printks in
> > these drivers.
> 
> > I'd actually prefer just keeping them for now.
> 
> OTOH the whole point in having stuff like this is to factor out repeated
> code like this so if the infrastructure isn't working we should fix
> that.

Ok, I'll drop them if you will consider a regmap patch to enable debug
printks to trace reg/val/mask.

> > > Might also be worth moving some of the sysfs stuff to live with the
> > > relevant drivers.
> 
> > Which attributes do you have in mind?
> 
> Pretty much all of those on the MFD.
> 
> > The boost_freq and boost_ovp affect both backlight devices (i.e. cannot
> > be set separately) and as such belong in the parent driver IMO.
> 
> > Same with the output_hvled{1..2}, output_lvled{1..5} attributes. The
> > chip has four logical LEDs ("control banks") but five low-voltage output
> > sinks. The five output_lvled attributes determine the mapping and as
> > such belong in the parent driver. The two logical backlight devices can
> > likewise be used to control either or both high-voltage outputs and
> > belong in the parent driver for the same reasons.
> 
> Actually, the other question I had but forgot to ask (or I think punted
> on for your response) was why these are in sysfs at all - things like
> which things are connected to the backlight are going to be a property
> of the board design so should be defined by the machine not tweaked from
> userspace.

I agree with you and the reason is the same as for the max_current
attribute (discussed in the other thread) -- it was an explicit request
from the end customer.

I could replace the boost attributes with a platform_data entry where it
really belongs.

Regarding the output configuration, the chip defaults are probably what
will be used in most cases (i.e. one-one map of logical backlights/leds
and hvled/lvled outputs except for the last led which controls two
outputs). The plan was to add this to the platform data later.

There is a use case (beyond testing/integration) for keeping the (lvled)
outputs configurable from userspace, in that it provides a way to
synchronise LED activity such as blinking. So I still want to keep those,
at least for the lvleds.

Thanks,
Johan

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-03 15:00             ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 15:00 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 12:38:02PM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 01:28:03PM +0200, Johan Hovold wrote:
> > On Thu, May 03, 2012 at 11:38:48AM +0100, Mark Brown wrote:
> 
> > > I'd expect you can drop these log messages, if there's stuff like this
> > > missing we should add it to regmap.  At the minute the regmap logging is
> > > via trace points rather than debug logs as you can leave them enabled
> > > all the time.
> 
> > If such debugging is added to regmap we still need a way to enable them
> > per driver (or rather regmap) to not clutter the logs.
> 
> This is one of the reasons why we currently use tracepoints (they just
> don't have this issue as they're trivial to filter), though
> adding some sort of infrastructure for it ought not to be too difficult
> even if it's just at the regmap level.

So a /sys/kernel/debug/regmap/<device>/io_printk attribute (with a
better name) to enable debug printks in io paths
(regmap*{read,write,update} outside of mutex) in regmap.c would be
acceptable?

> > These three dev_dbg statements are extremely useful during debugging /
> > development especially in combination with the other dynamic printks in
> > these drivers.
> 
> > I'd actually prefer just keeping them for now.
> 
> OTOH the whole point in having stuff like this is to factor out repeated
> code like this so if the infrastructure isn't working we should fix
> that.

Ok, I'll drop them if you will consider a regmap patch to enable debug
printks to trace reg/val/mask.

> > > Might also be worth moving some of the sysfs stuff to live with the
> > > relevant drivers.
> 
> > Which attributes do you have in mind?
> 
> Pretty much all of those on the MFD.
> 
> > The boost_freq and boost_ovp affect both backlight devices (i.e. cannot
> > be set separately) and as such belong in the parent driver IMO.
> 
> > Same with the output_hvled{1..2}, output_lvled{1..5} attributes. The
> > chip has four logical LEDs ("control banks") but five low-voltage output
> > sinks. The five output_lvled attributes determine the mapping and as
> > such belong in the parent driver. The two logical backlight devices can
> > likewise be used to control either or both high-voltage outputs and
> > belong in the parent driver for the same reasons.
> 
> Actually, the other question I had but forgot to ask (or I think punted
> on for your response) was why these are in sysfs at all - things like
> which things are connected to the backlight are going to be a property
> of the board design so should be defined by the machine not tweaked from
> userspace.

I agree with you and the reason is the same as for the max_current
attribute (discussed in the other thread) -- it was an explicit request
from the end customer.

I could replace the boost attributes with a platform_data entry where it
really belongs.

Regarding the output configuration, the chip defaults are probably what
will be used in most cases (i.e. one-one map of logical backlights/leds
and hvled/lvled outputs except for the last led which controls two
outputs). The plan was to add this to the platform data later.

There is a use case (beyond testing/integration) for keeping the (lvled)
outputs configurable from userspace, in that it provides a way to
synchronise LED activity such as blinking. So I still want to keep those,
at least for the lvleds.

Thanks,
Johan

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-03 15:00             ` Johan Hovold
@ 2012-05-03 15:24               ` Mark Brown
  -1 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 15:24 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 1645 bytes --]

On Thu, May 03, 2012 at 05:00:40PM +0200, Johan Hovold wrote:
> On Thu, May 03, 2012 at 12:38:02PM +0100, Mark Brown wrote:

> > This is one of the reasons why we currently use tracepoints (they just
> > don't have this issue as they're trivial to filter), though
> > adding some sort of infrastructure for it ought not to be too difficult
> > even if it's just at the regmap level.

> So a /sys/kernel/debug/regmap/<device>/io_printk attribute (with a
> better name) to enable debug printks in io paths
> (regmap*{read,write,update} outside of mutex) in regmap.c would be
> acceptable?

Yes, that'd be totally fine for me - it's debugfs so we can always drop
it later if someone comes up with a better idea or something.

> > Actually, the other question I had but forgot to ask (or I think punted
> > on for your response) was why these are in sysfs at all - things like
> > which things are connected to the backlight are going to be a property
> > of the board design so should be defined by the machine not tweaked from
> > userspace.

> I agree with you and the reason is the same as for the max_current
> attribute (discussed in the other thread) -- it was an explicit request
> from the end customer.

> I could replace the boost attributes with a platform_data entry where it
> really belongs.

I really think this is much better for mainline.

> There is a use case (beyond testing/integration) for keeping the (lvled)
> outputs configurable from userspace, in that it provides a way to
> synchronise LED activity such as blinking. So I still want to keep those,
> at least for the lvleds.

I'm not sure exactly which control that is?

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-03 15:24               ` Mark Brown
  0 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 15:24 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 1645 bytes --]

On Thu, May 03, 2012 at 05:00:40PM +0200, Johan Hovold wrote:
> On Thu, May 03, 2012 at 12:38:02PM +0100, Mark Brown wrote:

> > This is one of the reasons why we currently use tracepoints (they just
> > don't have this issue as they're trivial to filter), though
> > adding some sort of infrastructure for it ought not to be too difficult
> > even if it's just at the regmap level.

> So a /sys/kernel/debug/regmap/<device>/io_printk attribute (with a
> better name) to enable debug printks in io paths
> (regmap*{read,write,update} outside of mutex) in regmap.c would be
> acceptable?

Yes, that'd be totally fine for me - it's debugfs so we can always drop
it later if someone comes up with a better idea or something.

> > Actually, the other question I had but forgot to ask (or I think punted
> > on for your response) was why these are in sysfs at all - things like
> > which things are connected to the backlight are going to be a property
> > of the board design so should be defined by the machine not tweaked from
> > userspace.

> I agree with you and the reason is the same as for the max_current
> attribute (discussed in the other thread) -- it was an explicit request
> from the end customer.

> I could replace the boost attributes with a platform_data entry where it
> really belongs.

I really think this is much better for mainline.

> There is a use case (beyond testing/integration) for keeping the (lvled)
> outputs configurable from userspace, in that it provides a way to
> synchronise LED activity such as blinking. So I still want to keep those,
> at least for the lvleds.

I'm not sure exactly which control that is?

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
  2012-05-03 11:40       ` Jonathan Cameron
@ 2012-05-03 16:36         ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 16:36 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Arnd Bergmann, Andrew Morton,
	Mark Brown, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
> On 5/3/2012 11:26 AM, Johan Hovold wrote:
> > Add sub-driver for the ambient light sensor interface on National
> > Semiconductor / TI LM3533 lighting power chips.
> >
> > The sensor interface can be used to control the LEDs and backlights of
> > the chip through defining five light zones and three sets of
> > corresponding brightness target levels.
> >
> > The driver provides raw and mean adc readings along with the current
> > light zone through sysfs. A threshold event can be generated on zone
> > changes.
> Code is fine.  Pretty much all my comments are to do with the interface.

[...]

> > diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> > new file mode 100644
> > index 0000000..9849d14
> > --- /dev/null
> > +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> > @@ -0,0 +1,62 @@
> > +What:		/sys/bus/iio/devices/iio:deviceX/gain
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Set the ALS gain-resistor setting (0..127) for analog input
> > +		mode, where
> > +
> > +		0000000 - ALS input is high impedance
> > +		0000001 - 200kOhm (10uA at 2V full-scale)
> > +		0000010 - 100kOhm (20uA at 2V full-scale)
> > +		...
> > +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
> > +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
> > +
> > +		R_als = 2V / (10uA * gain)	(gain>  0)
> Firstly, no magic numbers.  These are definitely magic.

Not that magic as they're clearly documented (in code and public
datasheets), right? What would you prefer instead?

> Secondly see
> in_illuminance0_scale for a suitable existing attribute.

I didn't consider scale to be appropriate given the following
documentation (e.g, for in_voltageY_scale):

"If known for a device, scale to be applied to <type>Y[_name]_raw post
addition of <type>[Y][_name]_offset in order to obtain the measured
value in <type> units as specified in <type>[Y][_name]_raw
documentation."

That is, the gain setting has nothing to do with scaling the raw adc
reading to SI-units or such, it's simply a setup dependent gain setting
(which affects the raw readings as well). [And as such, should probably
go into to the platform data eventually as well.]

> > +
> > +What:		/sys/bus/iio/devices/iio:deviceX/illuminance_zone
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Get the current light zone (0..4) as defined by the
> > +		in_illuminance_thresh[n]_{falling,rising} thresholds.
> Hmm.. definitely have an in prefix, beyond that I'm not sure what the 
> cleanest

Thanks for catching this, it's a typo in the sysfs document -- the in_
prefix is in the code.

> interface will be for this.   Could extend the event codes to deal with the
> zone index.  Slightly tricky as the channel could already be modified so
> chan2 isn't necesarily available.
>
> > +
> > +What:		/sys/.../events/in_illuminance_thresh_either_en
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Event generated when channel passes one of the four threshold
> > +		in either direction (rising|falling) and a zone change occurs.
> > +		The corresponding light zone can be read from
> > +		illuminance_zone.
> > +
> > +What:		/sys/.../events/illuminance_thresh0_falling_value
> hmm.. every time you think you are making progress a new and exciting 
> device comes
> along requiring the abi to be extended.

Exciting isn't it. :)

> in_illuminanceX_threshY_rising_value
> in_illuminanceX_threshY_falling_value
> should do with appropriate description.

Ok.

> > +What:		/sys/.../events/illuminance_thresh0_raising_value
> > +What:		/sys/.../events/illuminance_thresh1_falling_value
> > +What:		/sys/.../events/illuminance_thresh1_raising_value
> > +What:		/sys/.../events/illuminance_thresh2_falling_value
> > +What:		/sys/.../events/illuminance_thresh2_raising_value
> > +What:		/sys/.../events/illuminance_thresh3_falling_value
> > +What:		/sys/.../events/illuminance_thresh3_raising_value
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Specifies the value of threshold that the device is comparing
> > +		against for the events enabled by
> > +		in_illuminance_thresh_either_en, and defines the
> > +		the five light zones.
> > +
> > +		These thresholds correspond to the eight zone-boundary
> > +		registers (boundary[n]_{low,high}).
> > +
> This interface is going to take some thought.  We have 
> in_illuminance0_target at the
> moment, so I guess we can add a zoning concept to that...

But target isn't really related, as far as I understand. That's another
calibration setting right? While zone is derived from the average adc
readings. (More below.)

> > +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Set the target brightness for ALS-mapper m in light zone n
> > +		(0..255), where m in 1..3 and n in 0..4.
> Don't suppose you could do a quick summary of what these zones are and 
> why there
> are 3 ALS-mappers?  I'm not getting terribly far on a quick look at the 
> datasheet!

Of course. The average adc readings are mapped to five light zones using
eight zone boundary registers (4 boundaries with hysteresis) and a set
of rules.

To simplify somewhat (by ignoring some of the rules): If the average
adc input drops below boundary0_low, the zone register reads 0; if it
drops below boundary1_low, it reads 1, and so on. If the input it
increases over boundary3_high, the zone register return 4; if it
increases passed boundary2_high, it returns zone 3, etc.

That is, roughly something like (we get 8-bits of input from the ADC): 

	zone 0

boundary0_low	51
boundary0_high	53

	zone 1

boundary1_low	102
boundary1_high	106

	zone 2

boundary2_low	153
boundary2_high	161

	zone 3

boundary3_low	204
boundary3_high	220

	zone 4

[ Figure 6 on page 20 in the datasheets should make it clear. ]

The ALS interface and it's zone concept can then be used to control the
LEDs and backlights of the chip, by determining the target brightness for
each zone, e.g., set brightness to 52 when in zone 0.

To complicate things further (and it is complicated), there are three
such sets of target brightness values: ALSM1, ALSM2, ALSM3.

So for each LED or backlight you can set ALS-input control mode, by
saying that the device should get it's brightness levels from target set
1, 2, or 3.

[ And it gets even more complicated, as ALSM1 can only control
  backlight0, where as ALSM2 and ALSM3 can control any of the remaining
  devices, but that's irrelevant here. ]

Initially, I thought this interface to be too esoteric to be worth 
generalising, but it sort of fits with event thresholds so I gave it a
try. The biggest conceptual problem, I think, is that the zone
boundaries can be used to control the other devices, even when the event
is not enabled (or even an irq line not configured). That is, I find it
a bit awkward that the event thresholds also defines the zones (a sort of
discrete scaling factor). 

Perhaps simply keeping the attributes outside of events (e.g. named
boundary[n]_{low,high}) and having a custom event enabled (e.g.
in_illuminance_zone_change_en) is the best solution?

[...]

> > diff --git a/drivers/staging/iio/light/lm3533-als.c b/drivers/staging/iio/light/lm3533-als.c
> > new file mode 100644
> > index 0000000..e2c9be6
> > --- /dev/null
> > +++ b/drivers/staging/iio/light/lm3533-als.c
> > @@ -0,0 +1,617 @@
> > +/*
> > + * lm3533-als.c -- LM3533 Ambient Light Sensor driver
> > + *
> > + * Copyright (C) 2011-2012 Texas Instruments
> > + *
> > + * Author: Johan Hovold<jhovold@gmail.com>
> > + *
> > + * 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.
> > + */
> > +
> > +#include<linux/atomic.h>
> > +#include<linux/fs.h>
> > +#include<linux/interrupt.h>
> > +#include<linux/io.h>
> > +#include<linux/module.h>
> > +#include<linux/mfd/core.h>
> > +#include<linux/platform_device.h>
> > +#include<linux/slab.h>
> > +#include<linux/uaccess.h>
> > +
> > +#include<linux/mfd/lm3533.h>
> > +
> > +#include "../events.h"
> > +#include "../iio.h"
> This will need to go through the staging-next tree.  In there these
> headers have moved.

I'm aware of the move. Should the different sub-drivers go in through
each tree respectively? Right now the four patches are all against rc5.

[...]

> > +static const struct iio_chan_spec lm3533_als_channels[] = {
> > +	{
> > +		.type = IIO_LIGHT,
> > +		.info_mask = IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT,
> > +		.channel = 0,
> channel doesn't get used unless you also set indexed = 1.

So, you mean I could drop channel as well? Or should I add indexed, as I
use channel 0 when reporting the event?

[...]

> > +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> > +	u8 val;
> > +	int ret;
> > +
> > +	if (enable)
> > +		val = mask;
> > +	else
> > +		val = 0;
> > +
> > +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
> > +								enable);
> extra brackets.

I prefer the brackets for multi-line (single) statements even though
they are not required. (Especially if the single statement spans
several lines -- but I try to be consistent.) If you have a strong
opinion about this, I'll drop them.

> > +	}
> > +
> > +	return ret;
> > +}
> > +

Thanks,
Johan

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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
@ 2012-05-03 16:36         ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 16:36 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Arnd Bergmann, Andrew Morton,
	Mark Brown, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
> On 5/3/2012 11:26 AM, Johan Hovold wrote:
> > Add sub-driver for the ambient light sensor interface on National
> > Semiconductor / TI LM3533 lighting power chips.
> >
> > The sensor interface can be used to control the LEDs and backlights of
> > the chip through defining five light zones and three sets of
> > corresponding brightness target levels.
> >
> > The driver provides raw and mean adc readings along with the current
> > light zone through sysfs. A threshold event can be generated on zone
> > changes.
> Code is fine.  Pretty much all my comments are to do with the interface.

[...]

> > diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> > new file mode 100644
> > index 0000000..9849d14
> > --- /dev/null
> > +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> > @@ -0,0 +1,62 @@
> > +What:		/sys/bus/iio/devices/iio:deviceX/gain
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Set the ALS gain-resistor setting (0..127) for analog input
> > +		mode, where
> > +
> > +		0000000 - ALS input is high impedance
> > +		0000001 - 200kOhm (10uA at 2V full-scale)
> > +		0000010 - 100kOhm (20uA at 2V full-scale)
> > +		...
> > +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
> > +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
> > +
> > +		R_als = 2V / (10uA * gain)	(gain>  0)
> Firstly, no magic numbers.  These are definitely magic.

Not that magic as they're clearly documented (in code and public
datasheets), right? What would you prefer instead?

> Secondly see
> in_illuminance0_scale for a suitable existing attribute.

I didn't consider scale to be appropriate given the following
documentation (e.g, for in_voltageY_scale):

"If known for a device, scale to be applied to <type>Y[_name]_raw post
addition of <type>[Y][_name]_offset in order to obtain the measured
value in <type> units as specified in <type>[Y][_name]_raw
documentation."

That is, the gain setting has nothing to do with scaling the raw adc
reading to SI-units or such, it's simply a setup dependent gain setting
(which affects the raw readings as well). [And as such, should probably
go into to the platform data eventually as well.]

> > +
> > +What:		/sys/bus/iio/devices/iio:deviceX/illuminance_zone
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Get the current light zone (0..4) as defined by the
> > +		in_illuminance_thresh[n]_{falling,rising} thresholds.
> Hmm.. definitely have an in prefix, beyond that I'm not sure what the 
> cleanest

Thanks for catching this, it's a typo in the sysfs document -- the in_
prefix is in the code.

> interface will be for this.   Could extend the event codes to deal with the
> zone index.  Slightly tricky as the channel could already be modified so
> chan2 isn't necesarily available.
>
> > +
> > +What:		/sys/.../events/in_illuminance_thresh_either_en
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Event generated when channel passes one of the four threshold
> > +		in either direction (rising|falling) and a zone change occurs.
> > +		The corresponding light zone can be read from
> > +		illuminance_zone.
> > +
> > +What:		/sys/.../events/illuminance_thresh0_falling_value
> hmm.. every time you think you are making progress a new and exciting 
> device comes
> along requiring the abi to be extended.

Exciting isn't it. :)

> in_illuminanceX_threshY_rising_value
> in_illuminanceX_threshY_falling_value
> should do with appropriate description.

Ok.

> > +What:		/sys/.../events/illuminance_thresh0_raising_value
> > +What:		/sys/.../events/illuminance_thresh1_falling_value
> > +What:		/sys/.../events/illuminance_thresh1_raising_value
> > +What:		/sys/.../events/illuminance_thresh2_falling_value
> > +What:		/sys/.../events/illuminance_thresh2_raising_value
> > +What:		/sys/.../events/illuminance_thresh3_falling_value
> > +What:		/sys/.../events/illuminance_thresh3_raising_value
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Specifies the value of threshold that the device is comparing
> > +		against for the events enabled by
> > +		in_illuminance_thresh_either_en, and defines the
> > +		the five light zones.
> > +
> > +		These thresholds correspond to the eight zone-boundary
> > +		registers (boundary[n]_{low,high}).
> > +
> This interface is going to take some thought.  We have 
> in_illuminance0_target at the
> moment, so I guess we can add a zoning concept to that...

But target isn't really related, as far as I understand. That's another
calibration setting right? While zone is derived from the average adc
readings. (More below.)

> > +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Set the target brightness for ALS-mapper m in light zone n
> > +		(0..255), where m in 1..3 and n in 0..4.
> Don't suppose you could do a quick summary of what these zones are and 
> why there
> are 3 ALS-mappers?  I'm not getting terribly far on a quick look at the 
> datasheet!

Of course. The average adc readings are mapped to five light zones using
eight zone boundary registers (4 boundaries with hysteresis) and a set
of rules.

To simplify somewhat (by ignoring some of the rules): If the average
adc input drops below boundary0_low, the zone register reads 0; if it
drops below boundary1_low, it reads 1, and so on. If the input it
increases over boundary3_high, the zone register return 4; if it
increases passed boundary2_high, it returns zone 3, etc.

That is, roughly something like (we get 8-bits of input from the ADC): 

	zone 0

boundary0_low	51
boundary0_high	53

	zone 1

boundary1_low	102
boundary1_high	106

	zone 2

boundary2_low	153
boundary2_high	161

	zone 3

boundary3_low	204
boundary3_high	220

	zone 4

[ Figure 6 on page 20 in the datasheets should make it clear. ]

The ALS interface and it's zone concept can then be used to control the
LEDs and backlights of the chip, by determining the target brightness for
each zone, e.g., set brightness to 52 when in zone 0.

To complicate things further (and it is complicated), there are three
such sets of target brightness values: ALSM1, ALSM2, ALSM3.

So for each LED or backlight you can set ALS-input control mode, by
saying that the device should get it's brightness levels from target set
1, 2, or 3.

[ And it gets even more complicated, as ALSM1 can only control
  backlight0, where as ALSM2 and ALSM3 can control any of the remaining
  devices, but that's irrelevant here. ]

Initially, I thought this interface to be too esoteric to be worth 
generalising, but it sort of fits with event thresholds so I gave it a
try. The biggest conceptual problem, I think, is that the zone
boundaries can be used to control the other devices, even when the event
is not enabled (or even an irq line not configured). That is, I find it
a bit awkward that the event thresholds also defines the zones (a sort of
discrete scaling factor). 

Perhaps simply keeping the attributes outside of events (e.g. named
boundary[n]_{low,high}) and having a custom event enabled (e.g.
in_illuminance_zone_change_en) is the best solution?

[...]

> > diff --git a/drivers/staging/iio/light/lm3533-als.c b/drivers/staging/iio/light/lm3533-als.c
> > new file mode 100644
> > index 0000000..e2c9be6
> > --- /dev/null
> > +++ b/drivers/staging/iio/light/lm3533-als.c
> > @@ -0,0 +1,617 @@
> > +/*
> > + * lm3533-als.c -- LM3533 Ambient Light Sensor driver
> > + *
> > + * Copyright (C) 2011-2012 Texas Instruments
> > + *
> > + * Author: Johan Hovold<jhovold@gmail.com>
> > + *
> > + * 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.
> > + */
> > +
> > +#include<linux/atomic.h>
> > +#include<linux/fs.h>
> > +#include<linux/interrupt.h>
> > +#include<linux/io.h>
> > +#include<linux/module.h>
> > +#include<linux/mfd/core.h>
> > +#include<linux/platform_device.h>
> > +#include<linux/slab.h>
> > +#include<linux/uaccess.h>
> > +
> > +#include<linux/mfd/lm3533.h>
> > +
> > +#include "../events.h"
> > +#include "../iio.h"
> This will need to go through the staging-next tree.  In there these
> headers have moved.

I'm aware of the move. Should the different sub-drivers go in through
each tree respectively? Right now the four patches are all against rc5.

[...]

> > +static const struct iio_chan_spec lm3533_als_channels[] = {
> > +	{
> > +		.type = IIO_LIGHT,
> > +		.info_mask = IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT,
> > +		.channel = 0,
> channel doesn't get used unless you also set indexed = 1.

So, you mean I could drop channel as well? Or should I add indexed, as I
use channel 0 when reporting the event?

[...]

> > +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> > +	u8 val;
> > +	int ret;
> > +
> > +	if (enable)
> > +		val = mask;
> > +	else
> > +		val = 0;
> > +
> > +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
> > +								enable);
> extra brackets.

I prefer the brackets for multi-line (single) statements even though
they are not required. (Especially if the single statement spans
several lines -- but I try to be consistent.) If you have a strong
opinion about this, I'll drop them.

> > +	}
> > +
> > +	return ret;
> > +}
> > +

Thanks,
Johan

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

* Re: [PATCH v2 3/4] leds: add LM3533 LED driver
  2012-05-03 14:51           ` Mark Brown
@ 2012-05-03 16:46             ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 16:46 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 03:51:08PM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 01:50:59PM +0200, Johan Hovold wrote:
> > On Thu, May 03, 2012 at 11:43:44AM +0100, Mark Brown wrote:
> 
> > > > +		5 - 4.194 s
> > > > +		6 - 8.389 s
> > > > +		7 - 16.78 s
> 
> > > Shouldn't these be controlled by led_blink_set() rather than a custom
> > > ABI?
> 
> > led_blink_set controls the on/off times, but the LM3533 has the two
> > additional rise and fall-time settings which determine the transition
> > time between these states.
> 
> Hrm.  In that case these rise times are very large - I'd expect them to
> cause issues with led_set_blink() users?

They are. The default settings (as fast a transition as possible) will
probably what most people use, and if they start fiddling with the
transition times they probably know what they're doing.

> Though actually I suspect the
> solution here is to pull these out into the framework later; we can
> probably simulate reasonably in software with a lot of brightness
> variable LEDs.

Ok.

> > > > +What:		/sys/class/leds/<led>/max_current
> 
> > > Shouldn't this be set by platform data, the maximum current you can push
> > > through the LEDs seems like a board dependant thing which won't change
> > > dynamically at runtime.  The brightness can already be varied.
> 
> > I fully agree and it is possible to set via the platform data for that
> > reason. The end-customer, however, insisted that even this setting be
> > available through sysfs to facilitate their integration and testing.
> 
> > I'd be willing drop this attribute if requested, as it would only be used
> > during integration and could easily be added back by the end-customer if
> > needed.
> 
> I'd strongly suggest removing this for mainline.  If it's present it
> should at least be limited to the maximum specified in platform data
> (just for safety if nothing else).

Agreed.

Thanks,
Johan

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

* Re: [PATCH v2 3/4] leds: add LM3533 LED driver
@ 2012-05-03 16:46             ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 16:46 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 03:51:08PM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 01:50:59PM +0200, Johan Hovold wrote:
> > On Thu, May 03, 2012 at 11:43:44AM +0100, Mark Brown wrote:
> 
> > > > +		5 - 4.194 s
> > > > +		6 - 8.389 s
> > > > +		7 - 16.78 s
> 
> > > Shouldn't these be controlled by led_blink_set() rather than a custom
> > > ABI?
> 
> > led_blink_set controls the on/off times, but the LM3533 has the two
> > additional rise and fall-time settings which determine the transition
> > time between these states.
> 
> Hrm.  In that case these rise times are very large - I'd expect them to
> cause issues with led_set_blink() users?

They are. The default settings (as fast a transition as possible) will
probably what most people use, and if they start fiddling with the
transition times they probably know what they're doing.

> Though actually I suspect the
> solution here is to pull these out into the framework later; we can
> probably simulate reasonably in software with a lot of brightness
> variable LEDs.

Ok.

> > > > +What:		/sys/class/leds/<led>/max_current
> 
> > > Shouldn't this be set by platform data, the maximum current you can push
> > > through the LEDs seems like a board dependant thing which won't change
> > > dynamically at runtime.  The brightness can already be varied.
> 
> > I fully agree and it is possible to set via the platform data for that
> > reason. The end-customer, however, insisted that even this setting be
> > available through sysfs to facilitate their integration and testing.
> 
> > I'd be willing drop this attribute if requested, as it would only be used
> > during integration and could easily be added back by the end-customer if
> > needed.
> 
> I'd strongly suggest removing this for mainline.  If it's present it
> should at least be limited to the maximum specified in platform data
> (just for safety if nothing else).

Agreed.

Thanks,
Johan

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-03 15:24               ` Mark Brown
@ 2012-05-03 16:54                 ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 16:54 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 04:24:07PM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 05:00:40PM +0200, Johan Hovold wrote:
> > On Thu, May 03, 2012 at 12:38:02PM +0100, Mark Brown wrote:
> 
> > > This is one of the reasons why we currently use tracepoints (they just
> > > don't have this issue as they're trivial to filter), though
> > > adding some sort of infrastructure for it ought not to be too difficult
> > > even if it's just at the regmap level.
> 
> > So a /sys/kernel/debug/regmap/<device>/io_printk attribute (with a
> > better name) to enable debug printks in io paths
> > (regmap*{read,write,update} outside of mutex) in regmap.c would be
> > acceptable?
> 
> Yes, that'd be totally fine for me - it's debugfs so we can always drop
> it later if someone comes up with a better idea or something.

Ok. I'll have a look at this next week (will be on the road for a few
days), and drop the dev_dbg from the lm3533 io-functions for now.

> > > Actually, the other question I had but forgot to ask (or I think punted
> > > on for your response) was why these are in sysfs at all - things like
> > > which things are connected to the backlight are going to be a property
> > > of the board design so should be defined by the machine not tweaked from
> > > userspace.
> 
> > I agree with you and the reason is the same as for the max_current
> > attribute (discussed in the other thread) -- it was an explicit request
> > from the end customer.
> 
> > I could replace the boost attributes with a platform_data entry where it
> > really belongs.
> 
> I really think this is much better for mainline.

Agreed.

> > There is a use case (beyond testing/integration) for keeping the (lvled)
> > outputs configurable from userspace, in that it provides a way to
> > synchronise LED activity such as blinking. So I still want to keep those,
> > at least for the lvleds.
> 
> I'm not sure exactly which control that is?

That would be the output_lvled[n] (n = 1..5) attributes. For example, to
have all five low-voltage sinks blink synchronously, you could assign 0
to all these five attributes, and set a timer trigger for the led device
which has id 0.

Thanks,
Johan

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-03 16:54                 ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 16:54 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 04:24:07PM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 05:00:40PM +0200, Johan Hovold wrote:
> > On Thu, May 03, 2012 at 12:38:02PM +0100, Mark Brown wrote:
> 
> > > This is one of the reasons why we currently use tracepoints (they just
> > > don't have this issue as they're trivial to filter), though
> > > adding some sort of infrastructure for it ought not to be too difficult
> > > even if it's just at the regmap level.
> 
> > So a /sys/kernel/debug/regmap/<device>/io_printk attribute (with a
> > better name) to enable debug printks in io paths
> > (regmap*{read,write,update} outside of mutex) in regmap.c would be
> > acceptable?
> 
> Yes, that'd be totally fine for me - it's debugfs so we can always drop
> it later if someone comes up with a better idea or something.

Ok. I'll have a look at this next week (will be on the road for a few
days), and drop the dev_dbg from the lm3533 io-functions for now.

> > > Actually, the other question I had but forgot to ask (or I think punted
> > > on for your response) was why these are in sysfs at all - things like
> > > which things are connected to the backlight are going to be a property
> > > of the board design so should be defined by the machine not tweaked from
> > > userspace.
> 
> > I agree with you and the reason is the same as for the max_current
> > attribute (discussed in the other thread) -- it was an explicit request
> > from the end customer.
> 
> > I could replace the boost attributes with a platform_data entry where it
> > really belongs.
> 
> I really think this is much better for mainline.

Agreed.

> > There is a use case (beyond testing/integration) for keeping the (lvled)
> > outputs configurable from userspace, in that it provides a way to
> > synchronise LED activity such as blinking. So I still want to keep those,
> > at least for the lvleds.
> 
> I'm not sure exactly which control that is?

That would be the output_lvled[n] (n = 1..5) attributes. For example, to
have all five low-voltage sinks blink synchronously, you could assign 0
to all these five attributes, and set a timer trigger for the led device
which has id 0.

Thanks,
Johan

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-03 16:54                 ` Johan Hovold
@ 2012-05-03 16:57                   ` Mark Brown
  -1 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 16:57 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 473 bytes --]

On Thu, May 03, 2012 at 06:54:37PM +0200, Johan Hovold wrote:
> On Thu, May 03, 2012 at 04:24:07PM +0100, Mark Brown wrote:

> > I'm not sure exactly which control that is?

> That would be the output_lvled[n] (n = 1..5) attributes. For example, to
> have all five low-voltage sinks blink synchronously, you could assign 0
> to all these five attributes, and set a timer trigger for the led device
> which has id 0.

Sorry, I meant "what exactly does this do in hardware".

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-03 16:57                   ` Mark Brown
  0 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 16:57 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 473 bytes --]

On Thu, May 03, 2012 at 06:54:37PM +0200, Johan Hovold wrote:
> On Thu, May 03, 2012 at 04:24:07PM +0100, Mark Brown wrote:

> > I'm not sure exactly which control that is?

> That would be the output_lvled[n] (n = 1..5) attributes. For example, to
> have all five low-voltage sinks blink synchronously, you could assign 0
> to all these five attributes, and set a timer trigger for the led device
> which has id 0.

Sorry, I meant "what exactly does this do in hardware".

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-03 16:57                   ` Mark Brown
@ 2012-05-03 17:14                     ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 17:14 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 05:57:49PM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 06:54:37PM +0200, Johan Hovold wrote:
> > On Thu, May 03, 2012 at 04:24:07PM +0100, Mark Brown wrote:
> 
> > > I'm not sure exactly which control that is?
> 
> > That would be the output_lvled[n] (n = 1..5) attributes. For example, to
> > have all five low-voltage sinks blink synchronously, you could assign 0
> > to all these five attributes, and set a timer trigger for the led device
> > which has id 0.
> 
> Sorry, I meant "what exactly does this do in hardware".

>From the datasheet (page 14):

	"CONTROL BANK MAPPING
	 Control of the LM3533's current sinks is not done directly, but
	 through the programming of Control Banks. The current sinks are
	 then assigned to the programmed Control Bank. This allows for
	 a wide variety of current control possibilities where LEDs can
	 be grouped and controlled via specific Control Banks (see
	 Figure 3)."

It is the control banks that has a brightness settings or can be
programmed to blink, that is, they correspond to the logical LEDs and
backlights.

Assigning a current sink to a control bank corresponds, then, to
setting, for example, output_lvled3 to (led) 1.

[ Figure 3 on page 16 of the data sheet may be instructive. In the
  figure,  BANK A and B corresponds to the two backlight devices, and
  BANK C through F corresponds to the four led devices. Note that there
  are more outputs (current sinks) than control banks. ]

Thanks,
Johan

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-03 17:14                     ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 17:14 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 05:57:49PM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 06:54:37PM +0200, Johan Hovold wrote:
> > On Thu, May 03, 2012 at 04:24:07PM +0100, Mark Brown wrote:
> 
> > > I'm not sure exactly which control that is?
> 
> > That would be the output_lvled[n] (n = 1..5) attributes. For example, to
> > have all five low-voltage sinks blink synchronously, you could assign 0
> > to all these five attributes, and set a timer trigger for the led device
> > which has id 0.
> 
> Sorry, I meant "what exactly does this do in hardware".

From the datasheet (page 14):

	"CONTROL BANK MAPPING
	 Control of the LM3533's current sinks is not done directly, but
	 through the programming of Control Banks. The current sinks are
	 then assigned to the programmed Control Bank. This allows for
	 a wide variety of current control possibilities where LEDs can
	 be grouped and controlled via specific Control Banks (see
	 Figure 3)."

It is the control banks that has a brightness settings or can be
programmed to blink, that is, they correspond to the logical LEDs and
backlights.

Assigning a current sink to a control bank corresponds, then, to
setting, for example, output_lvled3 to (led) 1.

[ Figure 3 on page 16 of the data sheet may be instructive. In the
  figure,  BANK A and B corresponds to the two backlight devices, and
  BANK C through F corresponds to the four led devices. Note that there
  are more outputs (current sinks) than control banks. ]

Thanks,
Johan

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-03 17:14                     ` Johan Hovold
@ 2012-05-03 17:23                       ` Mark Brown
  -1 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 17:23 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 380 bytes --]

On Thu, May 03, 2012 at 07:14:02PM +0200, Johan Hovold wrote:

> Assigning a current sink to a control bank corresponds, then, to
> setting, for example, output_lvled3 to (led) 1.

This seems sensible enough, though it does feel like what we're offering
up to the LED subsystem is relly the LED control banks rather than the
LEDs themselves - or some mix of this and other stuff.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-03 17:23                       ` Mark Brown
  0 siblings, 0 replies; 131+ messages in thread
From: Mark Brown @ 2012-05-03 17:23 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

[-- Attachment #1: Type: text/plain, Size: 380 bytes --]

On Thu, May 03, 2012 at 07:14:02PM +0200, Johan Hovold wrote:

> Assigning a current sink to a control bank corresponds, then, to
> setting, for example, output_lvled3 to (led) 1.

This seems sensible enough, though it does feel like what we're offering
up to the LED subsystem is relly the LED control banks rather than the
LEDs themselves - or some mix of this and other stuff.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-03 17:23                       ` Mark Brown
@ 2012-05-03 17:31                         ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 17:31 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 06:23:10PM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 07:14:02PM +0200, Johan Hovold wrote:
> 
> > Assigning a current sink to a control bank corresponds, then, to
> > setting, for example, output_lvled3 to (led) 1.
> 
> This seems sensible enough, though it does feel like what we're offering
> up to the LED subsystem is relly the LED control banks rather than the
> LEDs themselves - or some mix of this and other stuff.

Exactly. That's why I've tried to refer to the led devices as "logical
leds" as they may be connected to more than one output (the physical
leds).

Johan

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-03 17:31                         ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-03 17:31 UTC (permalink / raw)
  To: Mark Brown
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On Thu, May 03, 2012 at 06:23:10PM +0100, Mark Brown wrote:
> On Thu, May 03, 2012 at 07:14:02PM +0200, Johan Hovold wrote:
> 
> > Assigning a current sink to a control bank corresponds, then, to
> > setting, for example, output_lvled3 to (led) 1.
> 
> This seems sensible enough, though it does feel like what we're offering
> up to the LED subsystem is relly the LED control banks rather than the
> LEDs themselves - or some mix of this and other stuff.

Exactly. That's why I've tried to refer to the led devices as "logical
leds" as they may be connected to more than one output (the physical
leds).

Johan

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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
  2012-05-03 16:36         ` Johan Hovold
@ 2012-05-08 13:47           ` Jonathan Cameron
  -1 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-08 13:47 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Arnd Bergmann, Andrew Morton,
	Mark Brown, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On 5/3/2012 5:36 PM, Johan Hovold wrote:
> On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
>> On 5/3/2012 11:26 AM, Johan Hovold wrote:
>>> Add sub-driver for the ambient light sensor interface on National
>>> Semiconductor / TI LM3533 lighting power chips.
>>>
>>> The sensor interface can be used to control the LEDs and backlights of
>>> the chip through defining five light zones and three sets of
>>> corresponding brightness target levels.
>>>
>>> The driver provides raw and mean adc readings along with the current
>>> light zone through sysfs. A threshold event can be generated on zone
>>> changes.
>> Code is fine.  Pretty much all my comments are to do with the interface.
> [...]
>
>>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>> new file mode 100644
>>> index 0000000..9849d14
>>> --- /dev/null
>>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>> @@ -0,0 +1,62 @@
>>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>> +Description:
>>> +		Set the ALS gain-resistor setting (0..127) for analog input
>>> +		mode, where
>>> +
>>> +		0000000 - ALS input is high impedance
>>> +		0000001 - 200kOhm (10uA at 2V full-scale)
>>> +		0000010 - 100kOhm (20uA at 2V full-scale)
>>> +		...
>>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
>>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
>>> +
>>> +		R_als = 2V / (10uA * gain)	(gain>   0)
>> Firstly, no magic numbers.  These are definitely magic.
> Not that magic as they're clearly documented (in code and public
> datasheets), right? What would you prefer instead?
The numbers on the right of the - look good to me though then this isn't
a gain.  (200kohm) and the infinite element is annoying.  Why not
compute the actual gains?
Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
a bit of fixed point maths in the driver but the advantage is you'll
have real values that are standardizable across multiple devices
and hence allow your device to be operated by generic userspace
code.  Welcome to standardising interfaces - my favourite occupation ;)
>
>> Secondly see
>> in_illuminance0_scale for a suitable existing attribute.
> I didn't consider scale to be appropriate given the following
> documentation (e.g, for in_voltageY_scale):
sorry  I just did this to someone else in another review (so I'm 
consistently
wrong!)

in_voltageY_calibscale is what I should have said.  That one applies a 
scaling
before the raw reading is generated (so in hardware).
>
> "If known for a device, scale to be applied to<type>Y[_name]_raw post
> addition of<type>[Y][_name]_offset in order to obtain the measured
> value in<type>  units as specified in<type>[Y][_name]_raw
> documentation."
>
> That is, the gain setting has nothing to do with scaling the raw adc
> reading to SI-units or such, it's simply a setup dependent gain setting
> (which affects the raw readings as well). [And as such, should probably
> go into to the platform data eventually as well.]
>
>>> +
>>> +What:		/sys/bus/iio/devices/iio:deviceX/illuminance_zone
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>> +Description:
>>> +		Get the current light zone (0..4) as defined by the
>>> +		in_illuminance_thresh[n]_{falling,rising} thresholds.
>> Hmm.. definitely have an in prefix, beyond that I'm not sure what the
>> cleanest
> Thanks for catching this, it's a typo in the sysfs document -- the in_
> prefix is in the code.
>
>> interface will be for this.   Could extend the event codes to deal with the
>> zone index.  Slightly tricky as the channel could already be modified so
>> chan2 isn't necesarily available.
>>
>>> +
>>> +What:		/sys/.../events/in_illuminance_thresh_either_en
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>> +Description:
>>> +		Event generated when channel passes one of the four threshold
>>> +		in either direction (rising|falling) and a zone change occurs.
>>> +		The corresponding light zone can be read from
>>> +		illuminance_zone.
>>> +
>>> +What:		/sys/.../events/illuminance_thresh0_falling_value
>> hmm.. every time you think you are making progress a new and exciting
>> device comes
>> along requiring the abi to be extended.
> Exciting isn't it. :)
humph.
>
>> in_illuminanceX_threshY_rising_value
>> in_illuminanceX_threshY_falling_value
>> should do with appropriate description.
> Ok.
>
>>> +What:		/sys/.../events/illuminance_thresh0_raising_value
>>> +What:		/sys/.../events/illuminance_thresh1_falling_value
>>> +What:		/sys/.../events/illuminance_thresh1_raising_value
>>> +What:		/sys/.../events/illuminance_thresh2_falling_value
>>> +What:		/sys/.../events/illuminance_thresh2_raising_value
>>> +What:		/sys/.../events/illuminance_thresh3_falling_value
>>> +What:		/sys/.../events/illuminance_thresh3_raising_value
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>> +Description:
>>> +		Specifies the value of threshold that the device is comparing
>>> +		against for the events enabled by
>>> +		in_illuminance_thresh_either_en, and defines the
>>> +		the five light zones.
>>> +
>>> +		These thresholds correspond to the eight zone-boundary
>>> +		registers (boundary[n]_{low,high}).
>>> +
>> This interface is going to take some thought.  We have
>> in_illuminance0_target at the
>> moment, so I guess we can add a zoning concept to that...
> But target isn't really related, as far as I understand. That's another
> calibration setting right? While zone is derived from the average adc
> readings. (More below.)
True enough. I'd missunderstood this.
>
>>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>> +Description:
>>> +		Set the target brightness for ALS-mapper m in light zone n
>>> +		(0..255), where m in 1..3 and n in 0..4.
>> Don't suppose you could do a quick summary of what these zones are and
>> why there
>> are 3 ALS-mappers?  I'm not getting terribly far on a quick look at the
>> datasheet!
> Of course. The average adc readings are mapped to five light zones using
> eight zone boundary registers (4 boundaries with hysteresis) and a set
> of rules.
This is going to be fun.  We'll need the boundaries and attached 
hysteresis attributes
to fully specify these (nothing would indicate that hysterisis is 
involved otherwise).
>
> To simplify somewhat (by ignoring some of the rules): If the average
> adc input drops below boundary0_low, the zone register reads 0; if it
> drops below boundary1_low, it reads 1, and so on. If the input it
> increases over boundary3_high, the zone register return 4; if it
> increases passed boundary2_high, it returns zone 3, etc.
>
> That is, roughly something like (we get 8-bits of input from the ADC):
>
> 	zone 0
>
> boundary0_low	51
> boundary0_high	53
>
> 	zone 1
>
> boundary1_low	102
> boundary1_high	106
>
> 	zone 2
>
> boundary2_low	153
> boundary2_high	161
>
> 	zone 3
>
> boundary3_low	204
> boundary3_high	220
>
> 	zone 4
>
> [ Figure 6 on page 20 in the datasheets should make it clear. ]
>
> The ALS interface and it's zone concept can then be used to control the
> LEDs and backlights of the chip, by determining the target brightness for
> each zone, e.g., set brightness to 52 when in zone 0.
>
> To complicate things further (and it is complicated), there are three
> such sets of target brightness values: ALSM1, ALSM2, ALSM3.
>
> So for each LED or backlight you can set ALS-input control mode, by
> saying that the device should get it's brightness levels from target set
> 1, 2, or 3.
>
> [ And it gets even more complicated, as ALSM1 can only control
>    backlight0, where as ALSM2 and ALSM3 can control any of the remaining
>    devices, but that's irrelevant here. ]
>
> Initially, I thought this interface to be too esoteric to be worth
> generalising, but it sort of fits with event thresholds so I gave it a
> try.
Glad you did and it pretty much fits, be it with a few extensions being 
necessary.
> The biggest conceptual problem, I think, is that the zone
> boundaries can be used to control the other devices, even when the event
> is not enabled (or even an irq line not configured). That is, I find it
> a bit awkward that the event thresholds also defines the zones (a sort of
> discrete scaling factor).
That is indeed awkward. I'm not sure how we handle this either.  If we 
need to control
these from the other devices (e.g. the back light driver) then we'll 
have to get them
into chan_spec and use the inkernel interfaces to do it.  Not infeasible but
I was hoping to avoid that until we have had a few months to see what 
similar
devices show up (on basis nothing in this world is a one off for long ;)
>
>
> Perhaps simply keeping the attributes outside of events (e.g. named
> boundary[n]_{low,high}) and having a custom event enabled (e.g.
> in_illuminance_zone_change_en) is the best solution?
Maybe, but it's ugly and as you have said, they do correspond pretty well to
thresholds so I'd rather you went with that.
The core stuff for registering events clearly needs a rethink.... For 
now doing
it as you describe above (with the addition fo hysteresis attributes) should
be fine.  Just document the 'quirks'.
>
> [...]
>
>>> diff --git a/drivers/staging/iio/light/lm3533-als.c b/drivers/staging/iio/light/lm3533-als.c
>>> new file mode 100644
>>> index 0000000..e2c9be6
>>> --- /dev/null
>>> +++ b/drivers/staging/iio/light/lm3533-als.c
>>> @@ -0,0 +1,617 @@
>>> +/*
>>> + * lm3533-als.c -- LM3533 Ambient Light Sensor driver
>>> + *
>>> + * Copyright (C) 2011-2012 Texas Instruments
>>> + *
>>> + * Author: Johan Hovold<jhovold@gmail.com>
>>> + *
>>> + * 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.
>>> + */
>>> +
>>> +#include<linux/atomic.h>
>>> +#include<linux/fs.h>
>>> +#include<linux/interrupt.h>
>>> +#include<linux/io.h>
>>> +#include<linux/module.h>
>>> +#include<linux/mfd/core.h>
>>> +#include<linux/platform_device.h>
>>> +#include<linux/slab.h>
>>> +#include<linux/uaccess.h>
>>> +
>>> +#include<linux/mfd/lm3533.h>
>>> +
>>> +#include "../events.h"
>>> +#include "../iio.h"
>> This will need to go through the staging-next tree.  In there these
>> headers have moved.
> I'm aware of the move. Should the different sub-drivers go in through
> each tree respectively? Right now the four patches are all against rc5.
They will probably have to.  Typically mfd bit goes in first, then the 
rest get added
in a random order after that.
>
> [...]
>
>>> +static const struct iio_chan_spec lm3533_als_channels[] = {
>>> +	{
>>> +		.type = IIO_LIGHT,
>>> +		.info_mask = IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT,
>>> +		.channel = 0,
>> channel doesn't get used unless you also set indexed = 1.
> So, you mean I could drop channel as well? Or should I add indexed, as I
> use channel 0 when reporting the event?
Either option is valid.  I personally tend to set indexed = 1 but we 
decided that
it didn't matter either way.  Userspace code that uses the abi right 
should allow
for either.
>
> [...]
>
>>> +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
>>> +	u8 val;
>>> +	int ret;
>>> +
>>> +	if (enable)
>>> +		val = mask;
>>> +	else
>>> +		val = 0;
>>> +
>>> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
>>> +	if (ret) {
>>> +		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
>>> +								enable);
>> extra brackets.
> I prefer the brackets for multi-line (single) statements even though
> they are not required. (Especially if the single statement spans
> several lines -- but I try to be consistent.) If you have a strong
> opinion about this, I'll drop them.
I don't care either way.
>
>>> +	}
>>> +
>>> +	return ret;
>>> +}
>>> +
> Thanks,
> Johan


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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
@ 2012-05-08 13:47           ` Jonathan Cameron
  0 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-08 13:47 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Arnd Bergmann, Andrew Morton,
	Mark Brown, linux-doc, linux-kernel, linux-iio, devel,
	linux-fbdev

On 5/3/2012 5:36 PM, Johan Hovold wrote:
> On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
>> On 5/3/2012 11:26 AM, Johan Hovold wrote:
>>> Add sub-driver for the ambient light sensor interface on National
>>> Semiconductor / TI LM3533 lighting power chips.
>>>
>>> The sensor interface can be used to control the LEDs and backlights of
>>> the chip through defining five light zones and three sets of
>>> corresponding brightness target levels.
>>>
>>> The driver provides raw and mean adc readings along with the current
>>> light zone through sysfs. A threshold event can be generated on zone
>>> changes.
>> Code is fine.  Pretty much all my comments are to do with the interface.
> [...]
>
>>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>> new file mode 100644
>>> index 0000000..9849d14
>>> --- /dev/null
>>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>> @@ -0,0 +1,62 @@
>>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>> +Description:
>>> +		Set the ALS gain-resistor setting (0..127) for analog input
>>> +		mode, where
>>> +
>>> +		0000000 - ALS input is high impedance
>>> +		0000001 - 200kOhm (10uA at 2V full-scale)
>>> +		0000010 - 100kOhm (20uA at 2V full-scale)
>>> +		...
>>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
>>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
>>> +
>>> +		R_als = 2V / (10uA * gain)	(gain>   0)
>> Firstly, no magic numbers.  These are definitely magic.
> Not that magic as they're clearly documented (in code and public
> datasheets), right? What would you prefer instead?
The numbers on the right of the - look good to me though then this isn't
a gain.  (200kohm) and the infinite element is annoying.  Why not
compute the actual gains?
Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
a bit of fixed point maths in the driver but the advantage is you'll
have real values that are standardizable across multiple devices
and hence allow your device to be operated by generic userspace
code.  Welcome to standardising interfaces - my favourite occupation ;)
>
>> Secondly see
>> in_illuminance0_scale for a suitable existing attribute.
> I didn't consider scale to be appropriate given the following
> documentation (e.g, for in_voltageY_scale):
sorry  I just did this to someone else in another review (so I'm 
consistently
wrong!)

in_voltageY_calibscale is what I should have said.  That one applies a 
scaling
before the raw reading is generated (so in hardware).
>
> "If known for a device, scale to be applied to<type>Y[_name]_raw post
> addition of<type>[Y][_name]_offset in order to obtain the measured
> value in<type>  units as specified in<type>[Y][_name]_raw
> documentation."
>
> That is, the gain setting has nothing to do with scaling the raw adc
> reading to SI-units or such, it's simply a setup dependent gain setting
> (which affects the raw readings as well). [And as such, should probably
> go into to the platform data eventually as well.]
>
>>> +
>>> +What:		/sys/bus/iio/devices/iio:deviceX/illuminance_zone
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>> +Description:
>>> +		Get the current light zone (0..4) as defined by the
>>> +		in_illuminance_thresh[n]_{falling,rising} thresholds.
>> Hmm.. definitely have an in prefix, beyond that I'm not sure what the
>> cleanest
> Thanks for catching this, it's a typo in the sysfs document -- the in_
> prefix is in the code.
>
>> interface will be for this.   Could extend the event codes to deal with the
>> zone index.  Slightly tricky as the channel could already be modified so
>> chan2 isn't necesarily available.
>>
>>> +
>>> +What:		/sys/.../events/in_illuminance_thresh_either_en
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>> +Description:
>>> +		Event generated when channel passes one of the four threshold
>>> +		in either direction (rising|falling) and a zone change occurs.
>>> +		The corresponding light zone can be read from
>>> +		illuminance_zone.
>>> +
>>> +What:		/sys/.../events/illuminance_thresh0_falling_value
>> hmm.. every time you think you are making progress a new and exciting
>> device comes
>> along requiring the abi to be extended.
> Exciting isn't it. :)
humph.
>
>> in_illuminanceX_threshY_rising_value
>> in_illuminanceX_threshY_falling_value
>> should do with appropriate description.
> Ok.
>
>>> +What:		/sys/.../events/illuminance_thresh0_raising_value
>>> +What:		/sys/.../events/illuminance_thresh1_falling_value
>>> +What:		/sys/.../events/illuminance_thresh1_raising_value
>>> +What:		/sys/.../events/illuminance_thresh2_falling_value
>>> +What:		/sys/.../events/illuminance_thresh2_raising_value
>>> +What:		/sys/.../events/illuminance_thresh3_falling_value
>>> +What:		/sys/.../events/illuminance_thresh3_raising_value
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>> +Description:
>>> +		Specifies the value of threshold that the device is comparing
>>> +		against for the events enabled by
>>> +		in_illuminance_thresh_either_en, and defines the
>>> +		the five light zones.
>>> +
>>> +		These thresholds correspond to the eight zone-boundary
>>> +		registers (boundary[n]_{low,high}).
>>> +
>> This interface is going to take some thought.  We have
>> in_illuminance0_target at the
>> moment, so I guess we can add a zoning concept to that...
> But target isn't really related, as far as I understand. That's another
> calibration setting right? While zone is derived from the average adc
> readings. (More below.)
True enough. I'd missunderstood this.
>
>>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>> +Description:
>>> +		Set the target brightness for ALS-mapper m in light zone n
>>> +		(0..255), where m in 1..3 and n in 0..4.
>> Don't suppose you could do a quick summary of what these zones are and
>> why there
>> are 3 ALS-mappers?  I'm not getting terribly far on a quick look at the
>> datasheet!
> Of course. The average adc readings are mapped to five light zones using
> eight zone boundary registers (4 boundaries with hysteresis) and a set
> of rules.
This is going to be fun.  We'll need the boundaries and attached 
hysteresis attributes
to fully specify these (nothing would indicate that hysterisis is 
involved otherwise).
>
> To simplify somewhat (by ignoring some of the rules): If the average
> adc input drops below boundary0_low, the zone register reads 0; if it
> drops below boundary1_low, it reads 1, and so on. If the input it
> increases over boundary3_high, the zone register return 4; if it
> increases passed boundary2_high, it returns zone 3, etc.
>
> That is, roughly something like (we get 8-bits of input from the ADC):
>
> 	zone 0
>
> boundary0_low	51
> boundary0_high	53
>
> 	zone 1
>
> boundary1_low	102
> boundary1_high	106
>
> 	zone 2
>
> boundary2_low	153
> boundary2_high	161
>
> 	zone 3
>
> boundary3_low	204
> boundary3_high	220
>
> 	zone 4
>
> [ Figure 6 on page 20 in the datasheets should make it clear. ]
>
> The ALS interface and it's zone concept can then be used to control the
> LEDs and backlights of the chip, by determining the target brightness for
> each zone, e.g., set brightness to 52 when in zone 0.
>
> To complicate things further (and it is complicated), there are three
> such sets of target brightness values: ALSM1, ALSM2, ALSM3.
>
> So for each LED or backlight you can set ALS-input control mode, by
> saying that the device should get it's brightness levels from target set
> 1, 2, or 3.
>
> [ And it gets even more complicated, as ALSM1 can only control
>    backlight0, where as ALSM2 and ALSM3 can control any of the remaining
>    devices, but that's irrelevant here. ]
>
> Initially, I thought this interface to be too esoteric to be worth
> generalising, but it sort of fits with event thresholds so I gave it a
> try.
Glad you did and it pretty much fits, be it with a few extensions being 
necessary.
> The biggest conceptual problem, I think, is that the zone
> boundaries can be used to control the other devices, even when the event
> is not enabled (or even an irq line not configured). That is, I find it
> a bit awkward that the event thresholds also defines the zones (a sort of
> discrete scaling factor).
That is indeed awkward. I'm not sure how we handle this either.  If we 
need to control
these from the other devices (e.g. the back light driver) then we'll 
have to get them
into chan_spec and use the inkernel interfaces to do it.  Not infeasible but
I was hoping to avoid that until we have had a few months to see what 
similar
devices show up (on basis nothing in this world is a one off for long ;)
>
>
> Perhaps simply keeping the attributes outside of events (e.g. named
> boundary[n]_{low,high}) and having a custom event enabled (e.g.
> in_illuminance_zone_change_en) is the best solution?
Maybe, but it's ugly and as you have said, they do correspond pretty well to
thresholds so I'd rather you went with that.
The core stuff for registering events clearly needs a rethink.... For 
now doing
it as you describe above (with the addition fo hysteresis attributes) should
be fine.  Just document the 'quirks'.
>
> [...]
>
>>> diff --git a/drivers/staging/iio/light/lm3533-als.c b/drivers/staging/iio/light/lm3533-als.c
>>> new file mode 100644
>>> index 0000000..e2c9be6
>>> --- /dev/null
>>> +++ b/drivers/staging/iio/light/lm3533-als.c
>>> @@ -0,0 +1,617 @@
>>> +/*
>>> + * lm3533-als.c -- LM3533 Ambient Light Sensor driver
>>> + *
>>> + * Copyright (C) 2011-2012 Texas Instruments
>>> + *
>>> + * Author: Johan Hovold<jhovold@gmail.com>
>>> + *
>>> + * 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.
>>> + */
>>> +
>>> +#include<linux/atomic.h>
>>> +#include<linux/fs.h>
>>> +#include<linux/interrupt.h>
>>> +#include<linux/io.h>
>>> +#include<linux/module.h>
>>> +#include<linux/mfd/core.h>
>>> +#include<linux/platform_device.h>
>>> +#include<linux/slab.h>
>>> +#include<linux/uaccess.h>
>>> +
>>> +#include<linux/mfd/lm3533.h>
>>> +
>>> +#include "../events.h"
>>> +#include "../iio.h"
>> This will need to go through the staging-next tree.  In there these
>> headers have moved.
> I'm aware of the move. Should the different sub-drivers go in through
> each tree respectively? Right now the four patches are all against rc5.
They will probably have to.  Typically mfd bit goes in first, then the 
rest get added
in a random order after that.
>
> [...]
>
>>> +static const struct iio_chan_spec lm3533_als_channels[] = {
>>> +	{
>>> +		.type = IIO_LIGHT,
>>> +		.info_mask = IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT,
>>> +		.channel = 0,
>> channel doesn't get used unless you also set indexed = 1.
> So, you mean I could drop channel as well? Or should I add indexed, as I
> use channel 0 when reporting the event?
Either option is valid.  I personally tend to set indexed = 1 but we 
decided that
it didn't matter either way.  Userspace code that uses the abi right 
should allow
for either.
>
> [...]
>
>>> +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
>>> +	u8 val;
>>> +	int ret;
>>> +
>>> +	if (enable)
>>> +		val = mask;
>>> +	else
>>> +		val = 0;
>>> +
>>> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
>>> +	if (ret) {
>>> +		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
>>> +								enable);
>> extra brackets.
> I prefer the brackets for multi-line (single) statements even though
> they are not required. (Especially if the single statement spans
> several lines -- but I try to be consistent.) If you have a strong
> opinion about this, I'll drop them.
I don't care either way.
>
>>> +	}
>>> +
>>> +	return ret;
>>> +}
>>> +
> Thanks,
> Johan


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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-03 10:26     ` Johan Hovold
@ 2012-05-09 14:42       ` Samuel Ortiz
  -1 siblings, 0 replies; 131+ messages in thread
From: Samuel Ortiz @ 2012-05-09 14:42 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

Hi Johan

On Thu, May 03, 2012 at 12:26:36PM +0200, Johan Hovold wrote:
> Add support for National Semiconductor / TI LM3533 lighting power chips.
> 
> This is the core driver which provides register access over I2C and
> registers the ambient-light-sensor, LED and backlight sub-drivers.
> 
> Signed-off-by: Johan Hovold <jhovold@gmail.com>
> ---
> 
> v2:
>  - add sysfs-ABI documentation
>  - merge i2c implementation with core
>  - use regmap and kill custom debugfs interface
> 
> 
>  .../ABI/testing/sysfs-bus-i2c-devices-lm3533       |   38 +
>  drivers/mfd/Kconfig                                |   13 +
>  drivers/mfd/Makefile                               |    1 +
>  drivers/mfd/lm3533-core.c                          |  717 ++++++++++++++++++++
>  drivers/mfd/lm3533-ctrlbank.c                      |  134 ++++
>  include/linux/mfd/lm3533.h                         |   89 +++
>  6 files changed, 992 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
>  create mode 100644 drivers/mfd/lm3533-core.c
>  create mode 100644 drivers/mfd/lm3533-ctrlbank.c
>  create mode 100644 include/linux/mfd/lm3533.h
Patch applied to my for-next branch, thanks.

Cheers,
Samuel.

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-09 14:42       ` Samuel Ortiz
  0 siblings, 0 replies; 131+ messages in thread
From: Samuel Ortiz @ 2012-05-09 14:42 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

Hi Johan

On Thu, May 03, 2012 at 12:26:36PM +0200, Johan Hovold wrote:
> Add support for National Semiconductor / TI LM3533 lighting power chips.
> 
> This is the core driver which provides register access over I2C and
> registers the ambient-light-sensor, LED and backlight sub-drivers.
> 
> Signed-off-by: Johan Hovold <jhovold@gmail.com>
> ---
> 
> v2:
>  - add sysfs-ABI documentation
>  - merge i2c implementation with core
>  - use regmap and kill custom debugfs interface
> 
> 
>  .../ABI/testing/sysfs-bus-i2c-devices-lm3533       |   38 +
>  drivers/mfd/Kconfig                                |   13 +
>  drivers/mfd/Makefile                               |    1 +
>  drivers/mfd/lm3533-core.c                          |  717 ++++++++++++++++++++
>  drivers/mfd/lm3533-ctrlbank.c                      |  134 ++++
>  include/linux/mfd/lm3533.h                         |   89 +++
>  6 files changed, 992 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
>  create mode 100644 drivers/mfd/lm3533-core.c
>  create mode 100644 drivers/mfd/lm3533-ctrlbank.c
>  create mode 100644 include/linux/mfd/lm3533.h
Patch applied to my for-next branch, thanks.

Cheers,
Samuel.

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-09 14:42       ` Samuel Ortiz
@ 2012-05-10 12:07         ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-10 12:07 UTC (permalink / raw)
  To: Samuel Ortiz
  Cc: Rob Landley, Richard Purdie, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

On Wed, May 09, 2012 at 04:42:18PM +0200, Samuel Ortiz wrote:
> Hi Johan
> 
> On Thu, May 03, 2012 at 12:26:36PM +0200, Johan Hovold wrote:
> > Add support for National Semiconductor / TI LM3533 lighting power chips.
> > 
> > This is the core driver which provides register access over I2C and
> > registers the ambient-light-sensor, LED and backlight sub-drivers.
> > 
> > Signed-off-by: Johan Hovold <jhovold@gmail.com>
> > ---
> > 
> > v2:
> >  - add sysfs-ABI documentation
> >  - merge i2c implementation with core
> >  - use regmap and kill custom debugfs interface
> > 
> > 
> >  .../ABI/testing/sysfs-bus-i2c-devices-lm3533       |   38 +
> >  drivers/mfd/Kconfig                                |   13 +
> >  drivers/mfd/Makefile                               |    1 +
> >  drivers/mfd/lm3533-core.c                          |  717 ++++++++++++++++++++
> >  drivers/mfd/lm3533-ctrlbank.c                      |  134 ++++
> >  include/linux/mfd/lm3533.h                         |   89 +++
> >  6 files changed, 992 insertions(+), 0 deletions(-)
> >  create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
> >  create mode 100644 drivers/mfd/lm3533-core.c
> >  create mode 100644 drivers/mfd/lm3533-ctrlbank.c
> >  create mode 100644 include/linux/mfd/lm3533.h
> Patch applied to my for-next branch, thanks.

I've been travelling for a few days and didn't have time to submit a
discussed change to move two attributes to the platform data before I
left.

Could you please apply the following two patches on top of this one?

 mfd: lm3533: add boost frequency and ovp to platform data
 mfd: lm3533: remove boost attributes

Thanks,
Johan

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-10 12:07         ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-10 12:07 UTC (permalink / raw)
  To: Samuel Ortiz
  Cc: Rob Landley, Richard Purdie, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

On Wed, May 09, 2012 at 04:42:18PM +0200, Samuel Ortiz wrote:
> Hi Johan
> 
> On Thu, May 03, 2012 at 12:26:36PM +0200, Johan Hovold wrote:
> > Add support for National Semiconductor / TI LM3533 lighting power chips.
> > 
> > This is the core driver which provides register access over I2C and
> > registers the ambient-light-sensor, LED and backlight sub-drivers.
> > 
> > Signed-off-by: Johan Hovold <jhovold@gmail.com>
> > ---
> > 
> > v2:
> >  - add sysfs-ABI documentation
> >  - merge i2c implementation with core
> >  - use regmap and kill custom debugfs interface
> > 
> > 
> >  .../ABI/testing/sysfs-bus-i2c-devices-lm3533       |   38 +
> >  drivers/mfd/Kconfig                                |   13 +
> >  drivers/mfd/Makefile                               |    1 +
> >  drivers/mfd/lm3533-core.c                          |  717 ++++++++++++++++++++
> >  drivers/mfd/lm3533-ctrlbank.c                      |  134 ++++
> >  include/linux/mfd/lm3533.h                         |   89 +++
> >  6 files changed, 992 insertions(+), 0 deletions(-)
> >  create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
> >  create mode 100644 drivers/mfd/lm3533-core.c
> >  create mode 100644 drivers/mfd/lm3533-ctrlbank.c
> >  create mode 100644 include/linux/mfd/lm3533.h
> Patch applied to my for-next branch, thanks.

I've been travelling for a few days and didn't have time to submit a
discussed change to move two attributes to the platform data before I
left.

Could you please apply the following two patches on top of this one?

 mfd: lm3533: add boost frequency and ovp to platform data
 mfd: lm3533: remove boost attributes

Thanks,
Johan

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

* [PATCH 1/2] mfd: lm3533: add boost frequency and ovp to platform data
  2012-05-10 12:07         ` Johan Hovold
  (?)
@ 2012-05-10 12:11         ` Johan Hovold
  2012-05-10 12:11           ` [PATCH 2/2] mfd: lm3533: remove boost attributes Johan Hovold
  -1 siblings, 1 reply; 131+ messages in thread
From: Johan Hovold @ 2012-05-10 12:11 UTC (permalink / raw)
  To: Samuel Ortiz
  Cc: Rob Landley, Richard Purdie, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, Johan Hovold

Add boost-frequency and over-voltage-protection settings to platform
data.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/mfd/lm3533-core.c  |   50 ++++++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/lm3533.h |   15 +++++++++++++
 2 files changed, 65 insertions(+), 0 deletions(-)

diff --git a/drivers/mfd/lm3533-core.c b/drivers/mfd/lm3533-core.c
index 75f4b7f..053438c 100644
--- a/drivers/mfd/lm3533-core.c
+++ b/drivers/mfd/lm3533-core.c
@@ -138,6 +138,35 @@ int lm3533_update(struct lm3533 *lm3533, u8 reg, u8 val, u8 mask)
 }
 EXPORT_SYMBOL_GPL(lm3533_update);
 
+static int lm3533_set_boost_freq(struct lm3533 *lm3533,
+						enum lm3533_boost_freq freq)
+{
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_BOOST_PWM,
+					freq << LM3533_BOOST_FREQ_SHIFT,
+					LM3533_BOOST_FREQ_MASK);
+	if (ret)
+		dev_err(lm3533->dev, "failed to set boost frequency\n");
+
+	return ret;
+}
+
+
+static int lm3533_set_boost_ovp(struct lm3533 *lm3533,
+						enum lm3533_boost_ovp ovp)
+{
+	int ret;
+
+	ret = lm3533_update(lm3533, LM3533_REG_BOOST_PWM,
+					ovp << LM3533_BOOST_OVP_SHIFT,
+					LM3533_BOOST_OVP_MASK);
+	if (ret)
+		dev_err(lm3533->dev, "failed to set boost ovp\n");
+
+	return ret;
+}
+
 /*
  * HVLED output config -- output hvled controlled by backlight bl
  */
@@ -521,6 +550,22 @@ static int __devinit lm3533_device_led_init(struct lm3533 *lm3533)
 	return 0;
 }
 
+static int __devinit lm3533_device_setup(struct lm3533 *lm3533,
+					struct lm3533_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_set_boost_freq(lm3533, pdata->boost_freq);
+	if (ret)
+		return ret;
+
+	ret = lm3533_set_boost_ovp(lm3533, pdata->boost_ovp);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
 static int __devinit lm3533_device_init(struct lm3533 *lm3533)
 {
 	struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
@@ -550,6 +595,10 @@ static int __devinit lm3533_device_init(struct lm3533 *lm3533)
 
 	lm3533_enable(lm3533);
 
+	ret = lm3533_device_setup(lm3533, pdata);
+	if (ret)
+		goto err_disable;
+
 	lm3533_device_als_init(lm3533);
 	lm3533_device_bl_init(lm3533);
 	lm3533_device_led_init(lm3533);
@@ -564,6 +613,7 @@ static int __devinit lm3533_device_init(struct lm3533 *lm3533)
 
 err_unregister:
 	mfd_remove_devices(lm3533->dev);
+err_disable:
 	lm3533_disable(lm3533);
 	if (gpio_is_valid(lm3533->gpio_hwen))
 		gpio_free(lm3533->gpio_hwen);
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
index 75f85f3..3361137 100644
--- a/include/linux/mfd/lm3533.h
+++ b/include/linux/mfd/lm3533.h
@@ -59,9 +59,24 @@ struct lm3533_led_platform_data {
 	u8 pwm;				/* 0 - 0x3f */
 };
 
+enum lm3533_boost_freq {
+	LM3533_BOOST_FREQ_500KHZ,
+	LM3533_BOOST_FREQ_1000KHZ,
+};
+
+enum lm3533_boost_ovp {
+	LM3533_BOOST_OVP_16V,
+	LM3533_BOOST_OVP_24V,
+	LM3533_BOOST_OVP_32V,
+	LM3533_BOOST_OVP_40V,
+};
+
 struct lm3533_platform_data {
 	int gpio_hwen;
 
+	enum lm3533_boost_ovp boost_ovp;
+	enum lm3533_boost_freq boost_freq;
+
 	struct lm3533_als_platform_data *als;
 
 	struct lm3533_bl_platform_data *backlights;
-- 
1.7.8.5


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

* [PATCH 2/2] mfd: lm3533: remove boost attributes
  2012-05-10 12:11         ` [PATCH 1/2] mfd: lm3533: add boost frequency and ovp to platform data Johan Hovold
@ 2012-05-10 12:11           ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-10 12:11 UTC (permalink / raw)
  To: Samuel Ortiz
  Cc: Rob Landley, Richard Purdie, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, Johan Hovold

Remove boost-frequency and ovp attributes, which can be set through
platform data, from sysfs.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 .../ABI/testing/sysfs-bus-i2c-devices-lm3533       |   23 -----
 drivers/mfd/lm3533-core.c                          |   88 --------------------
 2 files changed, 0 insertions(+), 111 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533 b/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
index 5700721..1b62230 100644
--- a/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
+++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
@@ -1,26 +1,3 @@
-What:		/sys/bus/i2c/devices/.../boost_freq
-Date:		April 2012
-KernelVersion:	3.5
-Contact:	Johan Hovold <jhovold@gmail.com>
-Description:
-		Set the boost converter switching frequency (0, 1), where
-
-		0 -  500Hz
-		1 - 1000Hz
-
-What:		/sys/bus/i2c/devices/.../boost_ovp
-Date:		April 2012
-KernelVersion:	3.5
-Contact:	Johan Hovold <jhovold@gmail.com>
-Description:
-		Set the boost converter over-voltage protection threshold
-		(0..3), where
-
-		0 - 16V
-		1 - 24V
-		2 - 32V
-		3 - 40V
-
 What:		/sys/bus/i2c/devices/.../output_hvled[n]
 Date:		April 2012
 KernelVersion:	3.5
diff --git a/drivers/mfd/lm3533-core.c b/drivers/mfd/lm3533-core.c
index 053438c..d670543 100644
--- a/drivers/mfd/lm3533-core.c
+++ b/drivers/mfd/lm3533-core.c
@@ -26,11 +26,9 @@
 #include <linux/mfd/lm3533.h>
 
 
-#define LM3533_BOOST_OVP_MAX		0x03
 #define LM3533_BOOST_OVP_MASK		0x06
 #define LM3533_BOOST_OVP_SHIFT		1
 
-#define LM3533_BOOST_FREQ_MAX		0x01
 #define LM3533_BOOST_FREQ_MASK		0x01
 #define LM3533_BOOST_FREQ_SHIFT		0
 
@@ -253,96 +251,12 @@ struct lm3533_device_attribute {
 		struct {
 			u8 id;
 		} output;
-		struct {
-			u8 reg;
-			u8 shift;
-			u8 mask;
-			u8 max;
-		} generic;
 	} u;
 };
 
 #define to_lm3533_dev_attr(_attr) \
 	container_of(_attr, struct lm3533_device_attribute, dev_attr)
 
-static ssize_t show_lm3533_reg(struct device *dev,
-				struct device_attribute *attr, char *buf)
-{
-	struct lm3533 *lm3533 = dev_get_drvdata(dev);
-	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
-	u8 val;
-	int ret;
-
-	ret = lm3533_read(lm3533, lattr->u.generic.reg, &val);
-	if (ret)
-		return ret;
-
-	val = (val & lattr->u.generic.mask) >> lattr->u.generic.shift;
-
-	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
-}
-
-static ssize_t store_lm3533_reg(struct device *dev,
-						struct device_attribute *attr,
-						const char *buf, size_t len)
-{
-	struct lm3533 *lm3533 = dev_get_drvdata(dev);
-	struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
-	u8 val;
-	int ret;
-
-	if (kstrtou8(buf, 0, &val) || val > lattr->u.generic.max)
-		return -EINVAL;
-
-	val = val << lattr->u.generic.shift;
-	ret = lm3533_update(lm3533, lattr->u.generic.reg, val,
-							lattr->u.generic.mask);
-	if (ret)
-		return ret;
-
-	return len;
-}
-
-#define GENERIC_ATTR(_reg, _max, _mask, _shift) \
-	{ .reg		= _reg, \
-	  .max		= _max, \
-	  .mask		= _mask, \
-	  .shift	= _shift }
-
-#define LM3533_GENERIC_ATTR(_name, _mode, _show, _store, _type,	\
-						_reg, _max, _mask, _shift) \
-	struct lm3533_device_attribute lm3533_dev_attr_##_name = { \
-		.dev_attr	= __ATTR(_name, _mode, _show, _store), \
-		.type		= _type, \
-		.u.generic	= GENERIC_ATTR(_reg, _max, _mask, _shift) }
-
-#define LM3533_GENERIC_ATTR_RW(_name, _type, _reg, _max, _mask, _shift) \
-	LM3533_GENERIC_ATTR(_name, S_IRUGO | S_IWUSR, \
-					show_lm3533_reg, store_lm3533_reg, \
-					_type, _reg, _max, _mask, _shift)
-
-#define LM3533_BOOST_ATTR_RW(_name, _NAME) \
-	LM3533_GENERIC_ATTR_RW(_name, LM3533_ATTR_TYPE_BACKLIGHT, \
-				LM3533_REG_BOOST_PWM, LM3533_##_NAME##_MAX, \
-				LM3533_##_NAME##_MASK, LM3533_##_NAME##_SHIFT)
-/*
- * Boost Over Voltage Protection Select
- *
- *   0 - 16 V (default)
- *   1 - 24 V
- *   2 - 32 V
- *   3 - 40 V
- */
-static LM3533_BOOST_ATTR_RW(boost_ovp, BOOST_OVP);
-
-/*
- * Boost Frequency Select
- *
- *   0 - 500 kHz (default)
- *   1 - 1 MHz
- */
-static LM3533_BOOST_ATTR_RW(boost_freq, BOOST_FREQ);
-
 static ssize_t show_output(struct device *dev,
 				struct device_attribute *attr, char *buf)
 {
@@ -432,8 +346,6 @@ static LM3533_OUTPUT_LVLED_ATTR_RW(4);
 static LM3533_OUTPUT_LVLED_ATTR_RW(5);
 
 static struct attribute *lm3533_attributes[] = {
-	&lm3533_dev_attr_boost_freq.dev_attr.attr,
-	&lm3533_dev_attr_boost_ovp.dev_attr.attr,
 	&lm3533_dev_attr_output_hvled1.dev_attr.attr,
 	&lm3533_dev_attr_output_hvled2.dev_attr.attr,
 	&lm3533_dev_attr_output_lvled1.dev_attr.attr,
-- 
1.7.8.5


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

* [PATCH 0/2] mfd: lm3533: update max-current interface
  2012-05-10 12:07         ` Johan Hovold
  (?)
  (?)
@ 2012-05-10 17:18         ` Johan Hovold
  2012-05-10 17:18           ` [PATCH 1/2] mfd: lm3533: remove unused max-current function Johan Hovold
  2012-05-10 17:18           ` [PATCH 2/2] mfd: lm3533: use SI-units for max-current interface Johan Hovold
  -1 siblings, 2 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-10 17:18 UTC (permalink / raw)
  To: Samuel Ortiz; +Cc: linux-kernel, Mark Brown, Richard Purdie, Johan Hovold

Hi Samuel, 

Here are the final two changes needed to lm3533 core to reflect changes that
have been decided for the subdriver interfaces.

Sorry I didn't have time to submit a v3 with this included before you applied
the patch.

Thanks,
Johan


Johan Hovold (2):
  mfd: lm3533: remove unused max-current function
  mfd: lm3533: use SI-units for max-current interface

 drivers/mfd/lm3533-ctrlbank.c |   44 +++++++++++++++++++++++++++--------------
 include/linux/mfd/lm3533.h    |    9 +++----
 2 files changed, 33 insertions(+), 20 deletions(-)

-- 
1.7.8.5


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

* [PATCH 1/2] mfd: lm3533: remove unused max-current function
  2012-05-10 17:18         ` [PATCH 0/2] mfd: lm3533: update max-current interface Johan Hovold
@ 2012-05-10 17:18           ` Johan Hovold
  2012-05-10 17:18           ` [PATCH 2/2] mfd: lm3533: use SI-units for max-current interface Johan Hovold
  1 sibling, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-10 17:18 UTC (permalink / raw)
  To: Samuel Ortiz; +Cc: linux-kernel, Mark Brown, Richard Purdie, Johan Hovold

The max-current attributes of the subdrivers have been dropped so
remove the no longer used lm3533_ctrlbank_get_max_current function.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/mfd/lm3533-ctrlbank.c |    1 -
 include/linux/mfd/lm3533.h    |    2 --
 2 files changed, 0 insertions(+), 3 deletions(-)

diff --git a/drivers/mfd/lm3533-ctrlbank.c b/drivers/mfd/lm3533-ctrlbank.c
index c2732a3..adf4c1a 100644
--- a/drivers/mfd/lm3533-ctrlbank.c
+++ b/drivers/mfd/lm3533-ctrlbank.c
@@ -113,7 +113,6 @@ lm3533_ctrlbank_get(brightness, BRIGHTNESS);
  *   31 - 29.8 mA
  */
 lm3533_ctrlbank_set(max_current, MAX_CURRENT);
-lm3533_ctrlbank_get(max_current, MAX_CURRENT);
 
 /*
  * PWM-input control mask:
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
index 3361137..7cfef9e 100644
--- a/include/linux/mfd/lm3533.h
+++ b/include/linux/mfd/lm3533.h
@@ -92,8 +92,6 @@ extern int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb);
 extern int lm3533_ctrlbank_set_brightness(struct lm3533_ctrlbank *cb, u8 val);
 extern int lm3533_ctrlbank_get_brightness(struct lm3533_ctrlbank *cb, u8 *val);
 extern int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb, u8 val);
-extern int lm3533_ctrlbank_get_max_current(struct lm3533_ctrlbank *cb,
-								u8 *val);
 extern int lm3533_ctrlbank_set_pwm(struct lm3533_ctrlbank *cb, u8 val);
 extern int lm3533_ctrlbank_get_pwm(struct lm3533_ctrlbank *cb, u8 *val);
 
-- 
1.7.8.5


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

* [PATCH 2/2] mfd: lm3533: use SI-units for max-current interface
  2012-05-10 17:18         ` [PATCH 0/2] mfd: lm3533: update max-current interface Johan Hovold
  2012-05-10 17:18           ` [PATCH 1/2] mfd: lm3533: remove unused max-current function Johan Hovold
@ 2012-05-10 17:18           ` Johan Hovold
  1 sibling, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-10 17:18 UTC (permalink / raw)
  To: Samuel Ortiz; +Cc: linux-kernel, Mark Brown, Richard Purdie, Johan Hovold

Use SI-units (uA) for max-current interface (5000 - 29800 uA).

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---
 drivers/mfd/lm3533-ctrlbank.c |   43 +++++++++++++++++++++++++++-------------
 include/linux/mfd/lm3533.h    |    7 +++--
 2 files changed, 33 insertions(+), 17 deletions(-)

diff --git a/drivers/mfd/lm3533-ctrlbank.c b/drivers/mfd/lm3533-ctrlbank.c
index adf4c1a..a4cb7a5 100644
--- a/drivers/mfd/lm3533-ctrlbank.c
+++ b/drivers/mfd/lm3533-ctrlbank.c
@@ -17,8 +17,11 @@
 #include <linux/mfd/lm3533.h>
 
 
+#define LM3533_MAX_CURRENT_MIN		5000
+#define LM3533_MAX_CURRENT_MAX		29800
+#define LM3533_MAX_CURRENT_STEP		800
+
 #define LM3533_BRIGHTNESS_MAX		255
-#define LM3533_MAX_CURRENT_MAX		31
 #define LM3533_PWM_MAX			0x3f
 
 #define LM3533_REG_PWM_BASE		0x14
@@ -65,6 +68,31 @@ int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb)
 }
 EXPORT_SYMBOL_GPL(lm3533_ctrlbank_disable);
 
+/*
+ * Full-scale current.
+ *
+ * imax		5000 - 29800 uA (800 uA step)
+ */
+int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb, u16 imax)
+{
+	u8 reg;
+	u8 val;
+	int ret;
+
+	if (imax < LM3533_MAX_CURRENT_MIN || imax > LM3533_MAX_CURRENT_MAX)
+		return -EINVAL;
+
+	val = (imax - LM3533_MAX_CURRENT_MIN) / LM3533_MAX_CURRENT_STEP;
+
+	reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_MAX_CURRENT_BASE);
+	ret = lm3533_write(cb->lm3533, reg, val);
+	if (ret)
+		dev_err(cb->dev, "failed to set max current\n");
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_set_max_current);
+
 #define lm3533_ctrlbank_set(_name, _NAME)				\
 int lm3533_ctrlbank_set_##_name(struct lm3533_ctrlbank *cb, u8 val)	\
 {									\
@@ -102,19 +130,6 @@ lm3533_ctrlbank_set(brightness, BRIGHTNESS);
 lm3533_ctrlbank_get(brightness, BRIGHTNESS);
 
 /*
- * Full scale current.
- *
- * Imax = 5 + val * 0.8 mA, e.g.:
- *
- *    0 - 5 mA
- *     ...
- *   19 - 20.2 mA (default)
- *     ...
- *   31 - 29.8 mA
- */
-lm3533_ctrlbank_set(max_current, MAX_CURRENT);
-
-/*
  * PWM-input control mask:
  *
  *   bit 5 - PWM-input enabled in Zone 4
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
index 7cfef9e..9660feb 100644
--- a/include/linux/mfd/lm3533.h
+++ b/include/linux/mfd/lm3533.h
@@ -47,15 +47,15 @@ struct lm3533_als_platform_data {
 
 struct lm3533_bl_platform_data {
 	char *name;
+	u16 max_current;		/* 5000 - 29800 uA (800 uA step) */
 	u8 default_brightness;		/* 0 - 255 */
-	u8 max_current;			/* 0 - 31 */
 	u8 pwm;				/* 0 - 0x3f */
 };
 
 struct lm3533_led_platform_data {
 	char *name;
 	const char *default_trigger;
-	u8 max_current;			/* 0 - 31 */
+	u16 max_current;		/* 5000 - 29800 uA (800 uA step) */
 	u8 pwm;				/* 0 - 0x3f */
 };
 
@@ -91,7 +91,8 @@ extern int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb);
 
 extern int lm3533_ctrlbank_set_brightness(struct lm3533_ctrlbank *cb, u8 val);
 extern int lm3533_ctrlbank_get_brightness(struct lm3533_ctrlbank *cb, u8 *val);
-extern int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb,
+								u16 imax);
 extern int lm3533_ctrlbank_set_pwm(struct lm3533_ctrlbank *cb, u8 val);
 extern int lm3533_ctrlbank_get_pwm(struct lm3533_ctrlbank *cb, u8 *val);
 
-- 
1.7.8.5


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

* [PATCH v3] leds: add LM3533 LED driver
  2012-05-03 10:26     ` Johan Hovold
  (?)
  (?)
@ 2012-05-10 18:27     ` Johan Hovold
  2012-05-10 18:48       ` Andrew Morton
  2012-05-14 10:31       ` [PATCH v4] " Johan Hovold
  -1 siblings, 2 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-10 18:27 UTC (permalink / raw)
  To: Richard Purdie
  Cc: Rob Landley, Samuel Ortiz, Jonathan Cameron, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Arnd Bergmann, Andrew Morton,
	Mark Brown, linux-doc, linux-kernel, Johan Hovold

Add sub-driver for the LEDs on National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels, hardware accelerated blinking
as well as ambient-light-sensor and pwm input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2
 - add sysfs-ABI documentation
 - open code max_current/pwm macros
v3
 - remove max_current attribute


 .../ABI/testing/sysfs-class-led-driver-lm3533      |   58 ++
 drivers/leds/Kconfig                               |   13 +
 drivers/leds/Makefile                              |    1 +
 drivers/leds/leds-lm3533.c                         |  704 ++++++++++++++++++++
 4 files changed, 776 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-led-driver-lm3533
 create mode 100644 drivers/leds/leds-lm3533.c

diff --git a/Documentation/ABI/testing/sysfs-class-led-driver-lm3533 b/Documentation/ABI/testing/sysfs-class-led-driver-lm3533
new file mode 100644
index 0000000..a9633fd
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-led-driver-lm3533
@@ -0,0 +1,58 @@
+What:		/sys/class/leds/<led>/als
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS-control mode (0, 2, 3), where
+
+		0 - disabled
+		2 - ALS-mapper 2
+		3 - ALS-mapper 3
+
+What:		/sys/class/leds/<led>/falltime
+What:		/sys/class/leds/<led>/risetime
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the pattern generator fall and rise times (0..7), where
+
+		0 - 2048 us
+		1 - 262 ms
+		2 - 524 ms
+		3 - 1.049 s
+		4 - 2.097 s
+		5 - 4.194 s
+		6 - 8.389 s
+		7 - 16.78 s
+
+What:		/sys/class/leds/<led>/id
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the id of this led (0..3).
+
+What:		/sys/class/leds/<led>/linear
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the brightness-mapping mode (0, 1), where
+
+		0 - exponential mode
+		1 - linear mode
+
+What:		/sys/class/leds/<led>/pwm
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the PWM-input control mask (5 bits), where
+
+		bit 5 - PWM-input enabled in Zone 4
+		bit 4 - PWM-input enabled in Zone 3
+		bit 3 - PWM-input enabled in Zone 2
+		bit 2 - PWM-input enabled in Zone 1
+		bit 1 - PWM-input enabled in Zone 0
+		bit 0 - PWM-input enabled
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index ff4b8cf..19bd829 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -50,6 +50,19 @@ config LEDS_LM3530
 	  controlled manually or using PWM input or using ambient
 	  light automatically.
 
+config LEDS_LM3533
+	tristate "LED support for LM3533"
+	depends on LEDS_CLASS
+	depends on MFD_LM3533
+	help
+	  This option enables support for the LEDs on National Semiconductor /
+	  TI LM3533 Lighting Power chips.
+
+	  The LEDs can be controlled directly, through PWM input, or by the
+	  ambient-light-sensor interface. The chip supports
+	  hardware-accelerated blinking with maximum on and off periods of 9.8
+	  and 77 seconds respectively.
+
 config LEDS_LOCOMO
 	tristate "LED Support for Locomo device"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 890481c..f39a526 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_ATMEL_PWM)		+= leds-atmel-pwm.o
 obj-$(CONFIG_LEDS_BD2802)		+= leds-bd2802.o
 obj-$(CONFIG_LEDS_LOCOMO)		+= leds-locomo.o
 obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
+obj-$(CONFIG_LEDS_LM3533)		+= leds-lm3533.o
 obj-$(CONFIG_LEDS_MIKROTIK_RB532)	+= leds-rb532.o
 obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
 obj-$(CONFIG_LEDS_NET48XX)		+= leds-net48xx.o
diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c
new file mode 100644
index 0000000..2112197
--- /dev/null
+++ b/drivers/leds/leds-lm3533.c
@@ -0,0 +1,704 @@
+/*
+ * leds-lm3533.c -- LM3533 LED driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/mfd/core.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_LVCTRLBANK_MIN		2
+#define LM3533_LVCTRLBANK_MAX		5
+#define LM3533_LVCTRLBANK_COUNT		4
+#define LM3533_RISEFALLTIME_MAX		7
+#define LM3533_ALS_LV_MIN		2
+#define LM3533_ALS_LV_MAX		3
+
+#define LM3533_REG_CTRLBANK_BCONF_BASE		0x1b
+#define LM3533_REG_PATTERN_ENABLE		0x28
+#define LM3533_REG_PATTERN_LOW_TIME_BASE	0x71
+#define LM3533_REG_PATTERN_HIGH_TIME_BASE	0x72
+#define LM3533_REG_PATTERN_RISETIME_BASE	0x74
+#define LM3533_REG_PATTERN_FALLTIME_BASE	0x75
+
+#define LM3533_REG_PATTERN_STEP			0x10
+
+#define LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK	0x04
+#define LM3533_REG_CTRLBANK_BCONF_ALS_MASK	0x03
+
+#define LM3533_LED_FLAG_PATTERN_ENABLE		1
+
+
+struct lm3533_led {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct led_classdev cdev;
+	int id;
+
+	struct mutex mutex;
+	unsigned long flags;
+
+	struct work_struct work;
+	u8 new_brightness;
+};
+
+#define to_lm3533_led(_cdev) \
+	container_of(_cdev, struct lm3533_led, cdev)
+
+
+static inline int lm3533_led_get_ctrlbank_id(struct lm3533_led *led)
+{
+	return led->id + 2;
+}
+
+static inline u8 lm3533_led_get_lv_reg(struct lm3533_led *led, u8 base)
+{
+	return base + led->id;
+}
+
+static inline u8 lm3533_led_get_pattern(struct lm3533_led *led)
+{
+	return led->id;
+}
+
+static inline u8 lm3533_led_get_pattern_reg(struct lm3533_led *led,
+								u8 base)
+{
+	return base + lm3533_led_get_pattern(led) * LM3533_REG_PATTERN_STEP;
+}
+
+static int lm3533_led_pattern_enable(struct lm3533_led *led, int enable)
+{
+	u8 mask;
+	u8 val;
+	int pattern;
+	int state;
+	int ret = 0;
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, enable);
+
+	mutex_lock(&led->mutex);
+
+	state = test_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+	if ((enable && state) || (!enable && !state))
+		goto out;
+
+	pattern = lm3533_led_get_pattern(led);
+	mask = 1 << (2 * pattern);
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, LM3533_REG_PATTERN_ENABLE, val, mask);
+	if (ret) {
+		dev_err(led->cdev.dev, "failed to enable pattern %d (%d)\n",
+							pattern, enable);
+		goto out;
+	}
+
+	__change_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+out:
+	mutex_unlock(&led->mutex);
+
+	return ret;
+}
+
+static void lm3533_led_work(struct work_struct *work)
+{
+	struct lm3533_led *led = container_of(work, struct lm3533_led, work);
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, led->new_brightness);
+
+	if (led->new_brightness == 0)
+		lm3533_led_pattern_enable(led, 0);	/* disable blink */
+
+	lm3533_ctrlbank_set_brightness(&led->cb, led->new_brightness);
+}
+
+static void lm3533_led_set(struct led_classdev *cdev,
+						enum led_brightness value)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, value);
+
+	led->new_brightness = value;
+	schedule_work(&led->work);
+}
+
+static enum led_brightness lm3533_led_get(struct led_classdev *cdev)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&led->cb, &val);
+	if (ret)
+		return ret;
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, val);
+
+	return val;
+}
+
+/* Pattern generator defines -- delays in us */
+#define LM3533_LED_DELAY_GROUP1_BASE	0x00
+#define LM3533_LED_DELAY_GROUP2_BASE	0x3d
+#define LM3533_LED_DELAY_GROUP3_BASE	0x80
+#define LM3533_LED_DELAY_MAX		0xff
+
+#define LM3533_LED_DELAY_GROUP1_STEP	16384
+#define LM3533_LED_DELAY_GROUP2_STEP	131072
+#define LM3533_LED_DELAY_GROUP3_STEP	524288
+#define LM3533_LED_DELAY_GROUP1_MIN	16384
+#define LM3533_LED_DELAY_GROUP2_MIN	1130496
+#define LM3533_LED_DELAY_GROUP3_MIN	10305536
+#define LM3533_LED_DELAY_GROUP1_MAX	999424
+#define LM3533_LED_DELAY_GROUP2_MAX	9781248
+#define LM3533_LED_DELAY_GROUP3_MAX	76890112
+
+/* Delay limits in ms */
+#define LM3533_LED_DELAY_ON_MAX		9845
+#define LM3533_LED_DELAY_OFF_MAX	77140
+
+static int time_to_val(long *t, long t_min, long t_max, long t_step,
+							int v_min, int v_max)
+{
+	int val;
+
+	*t += t_step / 2;
+	val = (*t - t_min) / t_step + v_min;
+	val = clamp(val, v_min, v_max);
+	*t = t_step * (val - v_min) + t_min;
+
+	return val;
+}
+
+static int lm3533_led_get_delay(long *delay)
+{
+	int val;
+
+	*delay *= 1000;
+
+	if (*delay >= LM3533_LED_DELAY_GROUP3_MIN -
+					LM3533_LED_DELAY_GROUP3_STEP / 2) {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP3_MIN,
+					LM3533_LED_DELAY_GROUP3_MAX,
+					LM3533_LED_DELAY_GROUP3_STEP,
+					LM3533_LED_DELAY_GROUP3_BASE,
+					0xff);
+	} else if (*delay >= LM3533_LED_DELAY_GROUP2_MIN -
+					LM3533_LED_DELAY_GROUP2_STEP / 2) {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP2_MIN,
+					LM3533_LED_DELAY_GROUP2_MAX,
+					LM3533_LED_DELAY_GROUP2_STEP,
+					LM3533_LED_DELAY_GROUP2_BASE,
+					LM3533_LED_DELAY_GROUP3_BASE - 1);
+	} else {
+		val = time_to_val(delay, LM3533_LED_DELAY_GROUP1_MIN,
+					LM3533_LED_DELAY_GROUP1_MAX,
+					LM3533_LED_DELAY_GROUP1_STEP,
+					LM3533_LED_DELAY_GROUP1_BASE,
+					LM3533_LED_DELAY_GROUP2_BASE - 1);
+	}
+
+	*delay /= 1000;
+
+	return val;
+}
+
+static int lm3533_led_delay_set(struct lm3533_led *led, u8 base,
+							unsigned long *delay)
+{
+	u8 val;
+	u8 reg;
+	long t;
+	int ret;
+
+	t = *delay;
+	val = lm3533_led_get_delay(&t);
+
+	dev_dbg(led->cdev.dev, "%s - %lu: %ld (0x%02x)\n", __func__,
+							*delay, t, val);
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
+
+	*delay = t;
+
+	return ret;
+}
+
+static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
+{
+	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP2_MAX / 1000);
+
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
+}
+
+static int lm3533_led_delay_off_set(struct lm3533_led *led, unsigned long *t)
+{
+	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP3_MAX / 1000);
+
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_LOW_TIME_BASE, t);
+}
+
+static int lm3533_led_blink_set(struct led_classdev *cdev,
+				unsigned long *delay_on,
+				unsigned long *delay_off)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	int ret;
+
+	dev_dbg(led->cdev.dev, "%s - on = %lu, off = %lu\n", __func__,
+							*delay_on, *delay_off);
+
+	if (*delay_on > LM3533_LED_DELAY_ON_MAX ||
+					*delay_off > LM3533_LED_DELAY_OFF_MAX)
+		return -EINVAL;
+
+	if (*delay_on == 0 && *delay_off == 0) {
+		*delay_on = 500;
+		*delay_off = 500;
+	}
+
+	ret = lm3533_led_delay_on_set(led, delay_on);
+	if (ret)
+		return ret;
+
+	ret = lm3533_led_delay_off_set(led, delay_off);
+	if (ret)
+		return ret;
+
+	return lm3533_led_pattern_enable(led, 1);
+}
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", led->id);
+}
+
+/*
+ * Pattern generator rise/fall times:
+ *
+ *   0 - 2048 us (default)
+ *   1 - 262 ms
+ *   2 - 524 ms
+ *   3 - 1.049 s
+ *   4 - 2.097 s
+ *   5 - 4.194 s
+ *   6 - 8.389 s
+ *   7 - 16.78 s
+ */
+static ssize_t show_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					char *buf, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	ssize_t ret;
+	u8 reg;
+	u8 val;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", val);
+}
+
+static ssize_t show_risetime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t show_falltime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+static ssize_t store_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	u8 reg;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > LM3533_RISEFALLTIME_MAX)
+		return -EINVAL;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t store_risetime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t store_falltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+/*
+ * ALS-control setting:
+ *
+ *   0 - ALS disabled
+ *   2 - ALS-mapper 2
+ *   3 - ALS-mapper 3
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int als;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	als = val & LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 als;
+	u8 reg;
+	u8 mask;
+	int ret;
+
+	if (kstrtou8(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	ret = lm3533_update(led->lm3533, reg, als, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int linear;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	if (val & LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	unsigned long linear;
+	u8 reg;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK;
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, reg, val, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_pwm(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_pwm(&led->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_pwm(&led->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RW(falltime);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(pwm);
+static LM3533_ATTR_RW(risetime);
+
+static struct attribute *lm3533_led_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_falltime.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_pwm.attr,
+	&dev_attr_risetime.attr,
+	NULL,
+};
+
+static mode_t lm3533_led_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	mode_t mode = attr->mode;
+
+	if (attr == &dev_attr_als.attr) {
+		if (!led->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_led_attribute_group = {
+	.is_visible	= lm3533_led_attr_is_visible,
+	.attrs		= lm3533_led_attributes
+};
+
+static int __devinit lm3533_led_setup(struct lm3533_led *led,
+					struct lm3533_led_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&led->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&led->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_led_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_led_platform_data *pdata;
+	struct lm3533_led *led;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_LVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal LED id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	led = kzalloc(sizeof(*led), GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	led->lm3533 = lm3533;
+	led->cdev.name = pdata->name;
+	led->cdev.default_trigger = pdata->default_trigger;
+	led->cdev.brightness_set = lm3533_led_set;
+	led->cdev.brightness_get = lm3533_led_get;
+	led->cdev.blink_set = lm3533_led_blink_set;
+	led->cdev.brightness = LED_OFF;
+	led->id = pdev->id;
+
+	mutex_init(&led->mutex);
+	INIT_WORK(&led->work, lm3533_led_work);
+
+	/* The class framework makes a callback to get brightness during
+	 * registration so use parent device (for error reporting) until
+	 * registered.
+	 */
+	led->cb.lm3533 = lm3533;
+	led->cb.id = lm3533_led_get_ctrlbank_id(led);
+	led->cb.dev = lm3533->dev;
+
+	platform_set_drvdata(pdev, led);
+
+	ret = led_classdev_register(pdev->dev.parent, &led->cdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id);
+		goto err_free;
+	}
+
+	led->cb.dev = led->cdev.dev;
+
+	ret = sysfs_create_group(&led->cdev.dev->kobj,
+						&lm3533_led_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	ret = lm3533_led_setup(led, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&led->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+err_unregister:
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+err_free:
+	kfree(led);
+
+	return ret;
+}
+
+static int __devexit lm3533_led_remove(struct platform_device *pdev)
+{
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+	kfree(led);
+
+	return 0;
+}
+
+static void lm3533_led_shutdown(struct platform_device *pdev)
+{
+
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	lm3533_led_set(&led->cdev, LED_OFF);		/* disable blink */
+	flush_work_sync(&led->work);
+}
+
+static struct platform_driver lm3533_led_driver = {
+	.driver = {
+		.name = "lm3533-leds",
+		.owner = THIS_MODULE,
+	},
+	.probe		= lm3533_led_probe,
+	.remove		= __devexit_p(lm3533_led_remove),
+	.shutdown	= lm3533_led_shutdown,
+};
+module_platform_driver(lm3533_led_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-leds");
-- 
1.7.8.5


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

* [PATCH v3] backlight: add LM3533 backlight driver
  2012-05-03 10:26     ` Johan Hovold
@ 2012-05-10 18:29       ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-10 18:29 UTC (permalink / raw)
  To: Richard Purdie
  Cc: Rob Landley, Samuel Ortiz, Jonathan Cameron, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Arnd Bergmann, Andrew Morton,
	Mark Brown, linux-doc, linux-kernel, linux-fbdev, Johan Hovold

Add sub-driver for the backlights on National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels and ambient-light-sensor and pwm
input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2
 - add sysfs-ABI documentation
 - open code max_current/pwm macros
v3
 - remove max_current attribute


 .../testing/sysfs-class-backlight-driver-lm3533    |   41 ++
 drivers/video/backlight/Kconfig                    |   12 +
 drivers/video/backlight/Makefile                   |    1 +
 drivers/video/backlight/lm3533_bl.c                |  423 ++++++++++++++++++++
 4 files changed, 477 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
 create mode 100644 drivers/video/backlight/lm3533_bl.c

diff --git a/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533 b/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
new file mode 100644
index 0000000..9fda257
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
@@ -0,0 +1,41 @@
+What:		/sys/class/backlight/<backlight>/als
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS-control mode (0,..2), where
+
+		0 - disabled
+		1 - ALS-mapper 1 (backlight 0)
+		2 - ALS-mapper 2 (backlight 1)
+
+What:		/sys/class/backlight/<backlight>/id
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the id of this backlight (0, 1).
+
+What:		/sys/class/backlight/<backlight>/linear
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the brightness-mapping mode (0, 1), where
+
+		0 - exponential mode
+		1 - linear mode
+
+What:		/sys/class/backlight/<backlight>/pwm
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the PWM-input control mask (5 bits), where
+
+		bit 5 - PWM-input enabled in Zone 4
+		bit 4 - PWM-input enabled in Zone 3
+		bit 3 - PWM-input enabled in Zone 2
+		bit 2 - PWM-input enabled in Zone 1
+		bit 1 - PWM-input enabled in Zone 0
+		bit 0 - PWM-input enabled
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index af16884..fa2b037 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -184,6 +184,18 @@ config BACKLIGHT_GENERIC
 	  known as the Corgi backlight driver. If you have a Sharp Zaurus
 	  SL-C7xx, SL-Cxx00 or SL-6000x say y.
 
+config BACKLIGHT_LM3533
+	tristate "Backlight Driver for LM3533"
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on MFD_LM3533
+	help
+	  Say Y to enable the backlight driver for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  The backlights can be controlled directly, through PWM input, or by
+	  the ambient-light-sensor interface. The chip supports 256 brightness
+	  levels.
+
 config BACKLIGHT_LOCOMO
 	tristate "Sharp LOCOMO LCD/Backlight Driver"
 	depends on SHARP_LOCOMO
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 36855ae..a2ac9cf 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_BACKLIGHT_EP93XX)	+= ep93xx_bl.o
 obj-$(CONFIG_BACKLIGHT_GENERIC)	+= generic_bl.o
 obj-$(CONFIG_BACKLIGHT_HP700)	+= jornada720_bl.o
 obj-$(CONFIG_BACKLIGHT_HP680)	+= hp680_bl.o
+obj-$(CONFIG_BACKLIGHT_LM3533)	+= lm3533_bl.o
 obj-$(CONFIG_BACKLIGHT_LOCOMO)	+= locomolcd.o
 obj-$(CONFIG_BACKLIGHT_LP855X)	+= lp855x_bl.o
 obj-$(CONFIG_BACKLIGHT_OMAP1)	+= omap1_bl.o
diff --git a/drivers/video/backlight/lm3533_bl.c b/drivers/video/backlight/lm3533_bl.c
new file mode 100644
index 0000000..5abca65
--- /dev/null
+++ b/drivers/video/backlight/lm3533_bl.c
@@ -0,0 +1,423 @@
+/*
+ * lm3533-bl.c -- LM3533 Backlight driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_HVCTRLBANK_COUNT		2
+#define LM3533_BL_MAX_BRIGHTNESS	255
+
+#define LM3533_REG_CTRLBANK_AB_BCONF	0x1a
+
+
+struct lm3533_bl {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct backlight_device *bd;
+	int id;
+};
+
+
+static inline int lm3533_bl_get_ctrlbank_id(struct lm3533_bl *bl)
+{
+	return bl->id;
+}
+
+static int lm3533_bl_update_status(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	int brightness = bd->props.brightness;
+
+	if (bd->props.power != FB_BLANK_UNBLANK)
+		brightness = 0;
+	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	return lm3533_ctrlbank_set_brightness(&bl->cb, (u8)brightness);
+}
+
+static int lm3533_bl_get_brightness(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return val;
+}
+
+static const struct backlight_ops lm3533_bl_ops = {
+	.get_brightness	= lm3533_bl_get_brightness,
+	.update_status	= lm3533_bl_update_status,
+};
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", bl->id);
+}
+
+/*
+ * ALS-control setting:
+ *
+ *   0 - ALS disabled
+ *   1 - ALS-mapper 1 (backlight 0)
+ *   2 - ALS-mapper 2 (backlight 1)
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	u8 val;
+	u8 mask;
+	int als;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 2 * ctrlbank;
+	als = val & mask;
+	if (als)
+		als = ctrlbank + 1;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	int als;
+	u8 val;
+	u8 mask;
+	int ret;
+
+	if (kstrtoint(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als != ctrlbank + 1))
+		return -EINVAL;
+
+	mask = 1 << (2 * ctrlbank);
+
+	if (als)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	u8 mask;
+	int linear;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (val & mask)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	unsigned long linear;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_pwm(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_pwm(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_pwm(&bl->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(pwm);
+
+static struct attribute *lm3533_bl_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_pwm.attr,
+	NULL,
+};
+
+static mode_t lm3533_bl_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	mode_t mode = attr->mode;
+
+	if (attr == &dev_attr_als.attr) {
+		if (!bl->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_bl_attribute_group = {
+	.is_visible	= lm3533_bl_attr_is_visible,
+	.attrs		= lm3533_bl_attributes
+};
+
+static int __devinit lm3533_bl_setup(struct lm3533_bl *bl,
+					struct lm3533_bl_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&bl->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&bl->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_bl_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_bl_platform_data *pdata;
+	struct lm3533_bl *bl;
+	struct backlight_device *bd;
+	struct backlight_properties props;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_HVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal backlight id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	bl = kzalloc(sizeof(*bl), GFP_KERNEL);
+	if (!bl) {
+		dev_err(&pdev->dev,
+				"failed to allocate memory for backlight\n");
+		return -ENOMEM;
+	}
+
+	bl->lm3533 = lm3533;
+	bl->id = pdev->id;
+
+	bl->cb.lm3533 = lm3533;
+	bl->cb.id = lm3533_bl_get_ctrlbank_id(bl);
+	bl->cb.dev = NULL;			/* until registered */
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = LM3533_BL_MAX_BRIGHTNESS;
+	props.brightness = pdata->default_brightness;
+	bd = backlight_device_register(pdata->name, pdev->dev.parent, bl,
+						&lm3533_bl_ops, &props);
+	if (IS_ERR(bd)) {
+		dev_err(&pdev->dev, "failed to register backlight device\n");
+		ret = PTR_ERR(bd);
+		goto err_free;
+	}
+
+	bl->bd = bd;
+	bl->cb.dev = &bl->bd->dev;
+
+	platform_set_drvdata(pdev, bl);
+
+	ret = sysfs_create_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	backlight_update_status(bd);
+
+	ret = lm3533_bl_setup(bl, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&bl->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+err_unregister:
+	backlight_device_unregister(bd);
+err_free:
+	kfree(bl);
+
+	return ret;
+}
+
+static int __devexit lm3533_bl_remove(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+	struct backlight_device *bd = bl->bd;
+
+	dev_dbg(&bd->dev, "%s\n", __func__);
+
+	bd->props.power = FB_BLANK_POWERDOWN;
+	bd->props.brightness = 0;
+
+	lm3533_ctrlbank_disable(&bl->cb);
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	backlight_device_unregister(bd);
+	kfree(bl);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int lm3533_bl_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static int lm3533_bl_resume(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_enable(&bl->cb);
+}
+#else
+#define lm3533_bl_suspend	NULL
+#define lm3533_bl_resume	NULL
+#endif
+
+static void lm3533_bl_shutdown(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static struct platform_driver lm3533_bl_driver = {
+	.driver = {
+		.name	= "lm3533-backlight",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= lm3533_bl_probe,
+	.remove		= __devexit_p(lm3533_bl_remove),
+	.shutdown	= lm3533_bl_shutdown,
+	.suspend	= lm3533_bl_suspend,
+	.resume		= lm3533_bl_resume,
+};
+module_platform_driver(lm3533_bl_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Backlight driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-backlight");
-- 
1.7.8.5


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

* [PATCH v3] backlight: add LM3533 backlight driver
@ 2012-05-10 18:29       ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-10 18:29 UTC (permalink / raw)
  To: Richard Purdie
  Cc: Rob Landley, Samuel Ortiz, Jonathan Cameron, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Arnd Bergmann, Andrew Morton,
	Mark Brown, linux-doc, linux-kernel, linux-fbdev, Johan Hovold

Add sub-driver for the backlights on National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels and ambient-light-sensor and pwm
input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2
 - add sysfs-ABI documentation
 - open code max_current/pwm macros
v3
 - remove max_current attribute


 .../testing/sysfs-class-backlight-driver-lm3533    |   41 ++
 drivers/video/backlight/Kconfig                    |   12 +
 drivers/video/backlight/Makefile                   |    1 +
 drivers/video/backlight/lm3533_bl.c                |  423 ++++++++++++++++++++
 4 files changed, 477 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
 create mode 100644 drivers/video/backlight/lm3533_bl.c

diff --git a/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533 b/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
new file mode 100644
index 0000000..9fda257
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
@@ -0,0 +1,41 @@
+What:		/sys/class/backlight/<backlight>/als
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS-control mode (0,..2), where
+
+		0 - disabled
+		1 - ALS-mapper 1 (backlight 0)
+		2 - ALS-mapper 2 (backlight 1)
+
+What:		/sys/class/backlight/<backlight>/id
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the id of this backlight (0, 1).
+
+What:		/sys/class/backlight/<backlight>/linear
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the brightness-mapping mode (0, 1), where
+
+		0 - exponential mode
+		1 - linear mode
+
+What:		/sys/class/backlight/<backlight>/pwm
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the PWM-input control mask (5 bits), where
+
+		bit 5 - PWM-input enabled in Zone 4
+		bit 4 - PWM-input enabled in Zone 3
+		bit 3 - PWM-input enabled in Zone 2
+		bit 2 - PWM-input enabled in Zone 1
+		bit 1 - PWM-input enabled in Zone 0
+		bit 0 - PWM-input enabled
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index af16884..fa2b037 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -184,6 +184,18 @@ config BACKLIGHT_GENERIC
 	  known as the Corgi backlight driver. If you have a Sharp Zaurus
 	  SL-C7xx, SL-Cxx00 or SL-6000x say y.
 
+config BACKLIGHT_LM3533
+	tristate "Backlight Driver for LM3533"
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on MFD_LM3533
+	help
+	  Say Y to enable the backlight driver for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  The backlights can be controlled directly, through PWM input, or by
+	  the ambient-light-sensor interface. The chip supports 256 brightness
+	  levels.
+
 config BACKLIGHT_LOCOMO
 	tristate "Sharp LOCOMO LCD/Backlight Driver"
 	depends on SHARP_LOCOMO
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 36855ae..a2ac9cf 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_BACKLIGHT_EP93XX)	+= ep93xx_bl.o
 obj-$(CONFIG_BACKLIGHT_GENERIC)	+= generic_bl.o
 obj-$(CONFIG_BACKLIGHT_HP700)	+= jornada720_bl.o
 obj-$(CONFIG_BACKLIGHT_HP680)	+= hp680_bl.o
+obj-$(CONFIG_BACKLIGHT_LM3533)	+= lm3533_bl.o
 obj-$(CONFIG_BACKLIGHT_LOCOMO)	+= locomolcd.o
 obj-$(CONFIG_BACKLIGHT_LP855X)	+= lp855x_bl.o
 obj-$(CONFIG_BACKLIGHT_OMAP1)	+= omap1_bl.o
diff --git a/drivers/video/backlight/lm3533_bl.c b/drivers/video/backlight/lm3533_bl.c
new file mode 100644
index 0000000..5abca65
--- /dev/null
+++ b/drivers/video/backlight/lm3533_bl.c
@@ -0,0 +1,423 @@
+/*
+ * lm3533-bl.c -- LM3533 Backlight driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_HVCTRLBANK_COUNT		2
+#define LM3533_BL_MAX_BRIGHTNESS	255
+
+#define LM3533_REG_CTRLBANK_AB_BCONF	0x1a
+
+
+struct lm3533_bl {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct backlight_device *bd;
+	int id;
+};
+
+
+static inline int lm3533_bl_get_ctrlbank_id(struct lm3533_bl *bl)
+{
+	return bl->id;
+}
+
+static int lm3533_bl_update_status(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	int brightness = bd->props.brightness;
+
+	if (bd->props.power != FB_BLANK_UNBLANK)
+		brightness = 0;
+	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	return lm3533_ctrlbank_set_brightness(&bl->cb, (u8)brightness);
+}
+
+static int lm3533_bl_get_brightness(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return val;
+}
+
+static const struct backlight_ops lm3533_bl_ops = {
+	.get_brightness	= lm3533_bl_get_brightness,
+	.update_status	= lm3533_bl_update_status,
+};
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", bl->id);
+}
+
+/*
+ * ALS-control setting:
+ *
+ *   0 - ALS disabled
+ *   1 - ALS-mapper 1 (backlight 0)
+ *   2 - ALS-mapper 2 (backlight 1)
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	u8 val;
+	u8 mask;
+	int als;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 2 * ctrlbank;
+	als = val & mask;
+	if (als)
+		als = ctrlbank + 1;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	int als;
+	u8 val;
+	u8 mask;
+	int ret;
+
+	if (kstrtoint(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als != ctrlbank + 1))
+		return -EINVAL;
+
+	mask = 1 << (2 * ctrlbank);
+
+	if (als)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	u8 mask;
+	int linear;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (val & mask)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	unsigned long linear;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_pwm(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_pwm(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_pwm(&bl->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(pwm);
+
+static struct attribute *lm3533_bl_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_pwm.attr,
+	NULL,
+};
+
+static mode_t lm3533_bl_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	mode_t mode = attr->mode;
+
+	if (attr = &dev_attr_als.attr) {
+		if (!bl->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_bl_attribute_group = {
+	.is_visible	= lm3533_bl_attr_is_visible,
+	.attrs		= lm3533_bl_attributes
+};
+
+static int __devinit lm3533_bl_setup(struct lm3533_bl *bl,
+					struct lm3533_bl_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&bl->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&bl->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_bl_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_bl_platform_data *pdata;
+	struct lm3533_bl *bl;
+	struct backlight_device *bd;
+	struct backlight_properties props;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_HVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal backlight id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	bl = kzalloc(sizeof(*bl), GFP_KERNEL);
+	if (!bl) {
+		dev_err(&pdev->dev,
+				"failed to allocate memory for backlight\n");
+		return -ENOMEM;
+	}
+
+	bl->lm3533 = lm3533;
+	bl->id = pdev->id;
+
+	bl->cb.lm3533 = lm3533;
+	bl->cb.id = lm3533_bl_get_ctrlbank_id(bl);
+	bl->cb.dev = NULL;			/* until registered */
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = LM3533_BL_MAX_BRIGHTNESS;
+	props.brightness = pdata->default_brightness;
+	bd = backlight_device_register(pdata->name, pdev->dev.parent, bl,
+						&lm3533_bl_ops, &props);
+	if (IS_ERR(bd)) {
+		dev_err(&pdev->dev, "failed to register backlight device\n");
+		ret = PTR_ERR(bd);
+		goto err_free;
+	}
+
+	bl->bd = bd;
+	bl->cb.dev = &bl->bd->dev;
+
+	platform_set_drvdata(pdev, bl);
+
+	ret = sysfs_create_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	backlight_update_status(bd);
+
+	ret = lm3533_bl_setup(bl, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&bl->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+err_unregister:
+	backlight_device_unregister(bd);
+err_free:
+	kfree(bl);
+
+	return ret;
+}
+
+static int __devexit lm3533_bl_remove(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+	struct backlight_device *bd = bl->bd;
+
+	dev_dbg(&bd->dev, "%s\n", __func__);
+
+	bd->props.power = FB_BLANK_POWERDOWN;
+	bd->props.brightness = 0;
+
+	lm3533_ctrlbank_disable(&bl->cb);
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	backlight_device_unregister(bd);
+	kfree(bl);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int lm3533_bl_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static int lm3533_bl_resume(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_enable(&bl->cb);
+}
+#else
+#define lm3533_bl_suspend	NULL
+#define lm3533_bl_resume	NULL
+#endif
+
+static void lm3533_bl_shutdown(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static struct platform_driver lm3533_bl_driver = {
+	.driver = {
+		.name	= "lm3533-backlight",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= lm3533_bl_probe,
+	.remove		= __devexit_p(lm3533_bl_remove),
+	.shutdown	= lm3533_bl_shutdown,
+	.suspend	= lm3533_bl_suspend,
+	.resume		= lm3533_bl_resume,
+};
+module_platform_driver(lm3533_bl_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Backlight driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-backlight");
-- 
1.7.8.5


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

* Re: [PATCH v3] leds: add LM3533 LED driver
  2012-05-10 18:27     ` [PATCH v3] " Johan Hovold
@ 2012-05-10 18:48       ` Andrew Morton
  2012-05-11  9:54         ` Johan Hovold
  2012-05-14 10:31       ` [PATCH v4] " Johan Hovold
  1 sibling, 1 reply; 131+ messages in thread
From: Andrew Morton @ 2012-05-10 18:48 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Richard Purdie, Rob Landley, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Mark Brown, linux-doc, linux-kernel, Bryan Wu

On Thu, 10 May 2012 20:27:05 +0200
Johan Hovold <jhovold@gmail.com> wrote:

> Add sub-driver for the LEDs on National Semiconductor / TI LM3533
> lighting power chips.
> 
> The chip provides 256 brightness levels, hardware accelerated blinking
> as well as ambient-light-sensor and pwm input control.
> 
>
> ...
>
> +#define to_lm3533_led(_cdev) \
> +	container_of(_cdev, struct lm3533_led, cdev)

Minor thing: container_of() is not fully type-safe: it can be passed
the address of any struct which contains a field called cdev and will
return a struct lm3533_led* (or something like that - it has holes...).

A way to fix that is to wrap container_of() in a real C function, not a
macro:

static inline struct lm3533_led *to_lm3533_led(struct struct led_classdev *cdev)
{
	return container_of(_cdev, struct lm3533_led, cdev);
}

This has been another episode in the ongoing series "macros are always
wrong" :)

>
> ...
>
> +static int time_to_val(long *t, long t_min, long t_max, long t_step,
> +							int v_min, int v_max)
> +{
> +	int val;
> +
> +	*t += t_step / 2;
> +	val = (*t - t_min) / t_step + v_min;
> +	val = clamp(val, v_min, v_max);
> +	*t = t_step * (val - v_min) + t_min;
> +
> +	return val;
> +}

Oh wow, what does all this do.  Please, take pity upon the poor reader
and add a comment documenting this function's intent?

> +static int lm3533_led_get_delay(long *delay)
> +{
> +	int val;
> +
> +	*delay *= 1000;
> +
> +	if (*delay >= LM3533_LED_DELAY_GROUP3_MIN -
> +					LM3533_LED_DELAY_GROUP3_STEP / 2) {
> +		val = time_to_val(delay, LM3533_LED_DELAY_GROUP3_MIN,
> +					LM3533_LED_DELAY_GROUP3_MAX,
> +					LM3533_LED_DELAY_GROUP3_STEP,
> +					LM3533_LED_DELAY_GROUP3_BASE,
> +					0xff);
> +	} else if (*delay >= LM3533_LED_DELAY_GROUP2_MIN -
> +					LM3533_LED_DELAY_GROUP2_STEP / 2) {
> +		val = time_to_val(delay, LM3533_LED_DELAY_GROUP2_MIN,
> +					LM3533_LED_DELAY_GROUP2_MAX,
> +					LM3533_LED_DELAY_GROUP2_STEP,
> +					LM3533_LED_DELAY_GROUP2_BASE,
> +					LM3533_LED_DELAY_GROUP3_BASE - 1);
> +	} else {
> +		val = time_to_val(delay, LM3533_LED_DELAY_GROUP1_MIN,
> +					LM3533_LED_DELAY_GROUP1_MAX,
> +					LM3533_LED_DELAY_GROUP1_STEP,
> +					LM3533_LED_DELAY_GROUP1_BASE,
> +					LM3533_LED_DELAY_GROUP2_BASE - 1);
> +	}
> +
> +	*delay /= 1000;
> +
> +	return val;
> +}

And this one, please.

> +static int lm3533_led_delay_set(struct lm3533_led *led, u8 base,
> +							unsigned long *delay)
> +{
> +	u8 val;
> +	u8 reg;
> +	long t;
> +	int ret;
> +
> +	t = *delay;
> +	val = lm3533_led_get_delay(&t);
> +
> +	dev_dbg(led->cdev.dev, "%s - %lu: %ld (0x%02x)\n", __func__,
> +							*delay, t, val);
> +	reg = lm3533_led_get_pattern_reg(led, base);
> +	ret = lm3533_write(led->lm3533, reg, val);
> +	if (ret)
> +		dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
> +
> +	*delay = t;
> +
> +	return ret;
> +}

Should `t' have unsigned long type?  I think so.  The above functions
confuddle longs with unsigned longs.  As a negative delay is an
absurdity, perhaps everything should use unsigned long consistently?

> +static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
> +{
> +	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP2_MAX / 1000);

The use of min_t is often a sign that the types are mucked up.  How to
fix this?

Are the LM3533_LED_DELAY_* constants logically to be considered to have
unsigned long type?  If so, put a "L" after their values and everything
should work out nicely.


> +	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
> +}
> +
>
> ...
>
> +static ssize_t store_als(struct device *dev,
> +					struct device_attribute *attr,
> +					const char *buf, size_t len)
> +{
> +	struct led_classdev *led_cdev = dev_get_drvdata(dev);
> +	struct lm3533_led *led = to_lm3533_led(led_cdev);
> +	u8 als;
> +	u8 reg;
> +	u8 mask;
> +	int ret;
> +
> +	if (kstrtou8(buf, 0, &als))
> +		return -EINVAL;
> +
> +	if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
> +		return -EINVAL;

The `als != 0' test doesn't do anything, and looks odd.  Is there some
magical reason why als==0 would be illegal even if LM3533_ALS_LV_MIN
was negative?  If so, it should be documented.

> +
> +	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
> +	mask = LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
> +
> +	ret = lm3533_update(led->lm3533, reg, als, mask);
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
>
> ...
>


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

* Re: [PATCH v3] leds: add LM3533 LED driver
  2012-05-10 18:48       ` Andrew Morton
@ 2012-05-11  9:54         ` Johan Hovold
  2012-05-11 22:24           ` Andrew Morton
  0 siblings, 1 reply; 131+ messages in thread
From: Johan Hovold @ 2012-05-11  9:54 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Richard Purdie, Rob Landley, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Mark Brown, linux-doc, linux-kernel, Bryan Wu

On Thu, May 10, 2012 at 11:48:17AM -0700, Andrew Morton wrote:
> On Thu, 10 May 2012 20:27:05 +0200
> Johan Hovold <jhovold@gmail.com> wrote:
> 
> > Add sub-driver for the LEDs on National Semiconductor / TI LM3533
> > lighting power chips.
> > 
> > The chip provides 256 brightness levels, hardware accelerated blinking
> > as well as ambient-light-sensor and pwm input control.
> > 
> > ...
> >
> > +#define to_lm3533_led(_cdev) \
> > +	container_of(_cdev, struct lm3533_led, cdev)
> 
> Minor thing: container_of() is not fully type-safe: it can be passed
> the address of any struct which contains a field called cdev and will
> return a struct lm3533_led* (or something like that - it has holes...).
> 
> A way to fix that is to wrap container_of() in a real C function, not a
> macro:
> 
> static inline struct lm3533_led *to_lm3533_led(struct struct led_classdev *cdev)
> {
> 	return container_of(_cdev, struct lm3533_led, cdev);
> }
> 
> This has been another episode in the ongoing series "macros are always
> wrong" :)

Fair enough. :) Seems like the vast majority of drivers still use
convenience macros such as the this one for this kind of use (where the
functions are either passed the class device or it is retrieved through
device driver data).

Do you want me to replace the other three instances of container_of
convenience macros in the iio-subdriver and core (already added to the
mfd tree) as well?

> > ...
> >
> > +static int time_to_val(long *t, long t_min, long t_max, long t_step,
> > +							int v_min, int v_max)
> > +{
> > +	int val;
> > +
> > +	*t += t_step / 2;
> > +	val = (*t - t_min) / t_step + v_min;
> > +	val = clamp(val, v_min, v_max);
> > +	*t = t_step * (val - v_min) + t_min;
> > +
> > +	return val;
> > +}
> 
> Oh wow, what does all this do.  Please, take pity upon the poor reader
> and add a comment documenting this function's intent?

Mapping a time in SI-units (us) to device-specific discrete time
and returning the actual time that will be used (multiple of t_step +
offset).

I'll try to make it more clear and add some comments.

> > +static int lm3533_led_get_delay(long *delay)
> > +{
> > +	int val;
> > +
> > +	*delay *= 1000;
> > +
> > +	if (*delay >= LM3533_LED_DELAY_GROUP3_MIN -
> > +					LM3533_LED_DELAY_GROUP3_STEP / 2) {
> > +		val = time_to_val(delay, LM3533_LED_DELAY_GROUP3_MIN,
> > +					LM3533_LED_DELAY_GROUP3_MAX,
> > +					LM3533_LED_DELAY_GROUP3_STEP,
> > +					LM3533_LED_DELAY_GROUP3_BASE,
> > +					0xff);
> > +	} else if (*delay >= LM3533_LED_DELAY_GROUP2_MIN -
> > +					LM3533_LED_DELAY_GROUP2_STEP / 2) {
> > +		val = time_to_val(delay, LM3533_LED_DELAY_GROUP2_MIN,
> > +					LM3533_LED_DELAY_GROUP2_MAX,
> > +					LM3533_LED_DELAY_GROUP2_STEP,
> > +					LM3533_LED_DELAY_GROUP2_BASE,
> > +					LM3533_LED_DELAY_GROUP3_BASE - 1);
> > +	} else {
> > +		val = time_to_val(delay, LM3533_LED_DELAY_GROUP1_MIN,
> > +					LM3533_LED_DELAY_GROUP1_MAX,
> > +					LM3533_LED_DELAY_GROUP1_STEP,
> > +					LM3533_LED_DELAY_GROUP1_BASE,
> > +					LM3533_LED_DELAY_GROUP2_BASE - 1);
> > +	}
> > +
> > +	*delay /= 1000;
> > +
> > +	return val;
> > +}
> 
> And this one, please.

Will do. [ The device has three ranges of supported hardware-accelerated
blink times with different step sizes. ]

> > +static int lm3533_led_delay_set(struct lm3533_led *led, u8 base,
> > +							unsigned long *delay)
> > +{
> > +	u8 val;
> > +	u8 reg;
> > +	long t;
> > +	int ret;
> > +
> > +	t = *delay;
> > +	val = lm3533_led_get_delay(&t);
> > +
> > +	dev_dbg(led->cdev.dev, "%s - %lu: %ld (0x%02x)\n", __func__,
> > +							*delay, t, val);
> > +	reg = lm3533_led_get_pattern_reg(led, base);
> > +	ret = lm3533_write(led->lm3533, reg, val);
> > +	if (ret)
> > +		dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
> > +
> > +	*delay = t;
> > +
> > +	return ret;
> > +}
> 
> Should `t' have unsigned long type?  I think so.  The above functions
> confuddle longs with unsigned longs.  As a negative delay is an
> absurdity, perhaps everything should use unsigned long consistently?

Indeed. 

> > +static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
> > +{
> > +	*t = min_t(long, *t, LM3533_LED_DELAY_GROUP2_MAX / 1000);
> 
> The use of min_t is often a sign that the types are mucked up.  How to
> fix this?
> 
> Are the LM3533_LED_DELAY_* constants logically to be considered to have
> unsigned long type?  If so, put a "L" after their values and everything
> should work out nicely.

Will do.

> > +	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
> > +}
> > +
> >
> > ...
> >
> > +static ssize_t store_als(struct device *dev,
> > +					struct device_attribute *attr,
> > +					const char *buf, size_t len)
> > +{
> > +	struct led_classdev *led_cdev = dev_get_drvdata(dev);
> > +	struct lm3533_led *led = to_lm3533_led(led_cdev);
> > +	u8 als;
> > +	u8 reg;
> > +	u8 mask;
> > +	int ret;
> > +
> > +	if (kstrtou8(buf, 0, &als))
> > +		return -EINVAL;
> > +
> > +	if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
> > +		return -EINVAL;
> 
> The `als != 0' test doesn't do anything, and looks odd.  Is there some
> magical reason why als==0 would be illegal even if LM3533_ALS_LV_MIN
> was negative?  If so, it should be documented.

The non-zero-test is not redundant as 0 is the only valid input outside
of [LV_MIN,LV_MAX] (in fact, the only three valid values are 0,2 and 3).

Would you prefer

	if ((als < LM3533_ALS_LV_MIN && als != 0) || als > LM3533_ALS_LV_MAX)
		return -EINVAL;

or nested conditionals? Or should I simply add a comment?

Thanks,
Johan

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
  2012-05-10 12:07         ` Johan Hovold
@ 2012-05-11 13:32           ` Samuel Ortiz
  -1 siblings, 0 replies; 131+ messages in thread
From: Samuel Ortiz @ 2012-05-11 13:32 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

Hi Johan,

On Thu, May 10, 2012 at 02:07:42PM +0200, Johan Hovold wrote:
> On Wed, May 09, 2012 at 04:42:18PM +0200, Samuel Ortiz wrote:
> > Hi Johan
> > 
> > On Thu, May 03, 2012 at 12:26:36PM +0200, Johan Hovold wrote:
> > > Add support for National Semiconductor / TI LM3533 lighting power chips.
> > > 
> > > This is the core driver which provides register access over I2C and
> > > registers the ambient-light-sensor, LED and backlight sub-drivers.
> > > 
> > > Signed-off-by: Johan Hovold <jhovold@gmail.com>
> > > ---
> > > 
> > > v2:
> > >  - add sysfs-ABI documentation
> > >  - merge i2c implementation with core
> > >  - use regmap and kill custom debugfs interface
> > > 
> > > 
> > >  .../ABI/testing/sysfs-bus-i2c-devices-lm3533       |   38 +
> > >  drivers/mfd/Kconfig                                |   13 +
> > >  drivers/mfd/Makefile                               |    1 +
> > >  drivers/mfd/lm3533-core.c                          |  717 ++++++++++++++++++++
> > >  drivers/mfd/lm3533-ctrlbank.c                      |  134 ++++
> > >  include/linux/mfd/lm3533.h                         |   89 +++
> > >  6 files changed, 992 insertions(+), 0 deletions(-)
> > >  create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
> > >  create mode 100644 drivers/mfd/lm3533-core.c
> > >  create mode 100644 drivers/mfd/lm3533-ctrlbank.c
> > >  create mode 100644 include/linux/mfd/lm3533.h
> > Patch applied to my for-next branch, thanks.
> 
> I've been travelling for a few days and didn't have time to submit a
> discussed change to move two attributes to the platform data before I
> left.
> 
> Could you please apply the following two patches on top of this one?
All of your 4 pending patches have been applied, thanks.

Cheers,
Samuel.

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

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

* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
@ 2012-05-11 13:32           ` Samuel Ortiz
  0 siblings, 0 replies; 131+ messages in thread
From: Samuel Ortiz @ 2012-05-11 13:32 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Rob Landley, Richard Purdie, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

Hi Johan,

On Thu, May 10, 2012 at 02:07:42PM +0200, Johan Hovold wrote:
> On Wed, May 09, 2012 at 04:42:18PM +0200, Samuel Ortiz wrote:
> > Hi Johan
> > 
> > On Thu, May 03, 2012 at 12:26:36PM +0200, Johan Hovold wrote:
> > > Add support for National Semiconductor / TI LM3533 lighting power chips.
> > > 
> > > This is the core driver which provides register access over I2C and
> > > registers the ambient-light-sensor, LED and backlight sub-drivers.
> > > 
> > > Signed-off-by: Johan Hovold <jhovold@gmail.com>
> > > ---
> > > 
> > > v2:
> > >  - add sysfs-ABI documentation
> > >  - merge i2c implementation with core
> > >  - use regmap and kill custom debugfs interface
> > > 
> > > 
> > >  .../ABI/testing/sysfs-bus-i2c-devices-lm3533       |   38 +
> > >  drivers/mfd/Kconfig                                |   13 +
> > >  drivers/mfd/Makefile                               |    1 +
> > >  drivers/mfd/lm3533-core.c                          |  717 ++++++++++++++++++++
> > >  drivers/mfd/lm3533-ctrlbank.c                      |  134 ++++
> > >  include/linux/mfd/lm3533.h                         |   89 +++
> > >  6 files changed, 992 insertions(+), 0 deletions(-)
> > >  create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
> > >  create mode 100644 drivers/mfd/lm3533-core.c
> > >  create mode 100644 drivers/mfd/lm3533-ctrlbank.c
> > >  create mode 100644 include/linux/mfd/lm3533.h
> > Patch applied to my for-next branch, thanks.
> 
> I've been travelling for a few days and didn't have time to submit a
> discussed change to move two attributes to the platform data before I
> left.
> 
> Could you please apply the following two patches on top of this one?
All of your 4 pending patches have been applied, thanks.

Cheers,
Samuel.

-- 
Intel Open Source Technology Centre
http://oss.intel.com/

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

* Re: [PATCH v3] leds: add LM3533 LED driver
  2012-05-11  9:54         ` Johan Hovold
@ 2012-05-11 22:24           ` Andrew Morton
  2012-05-14 10:25             ` Johan Hovold
  0 siblings, 1 reply; 131+ messages in thread
From: Andrew Morton @ 2012-05-11 22:24 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Richard Purdie, Rob Landley, Samuel Ortiz, Jonathan Cameron,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Mark Brown, linux-doc, linux-kernel, Bryan Wu

On Fri, 11 May 2012 11:54:11 +0200
Johan Hovold <jhovold@gmail.com> wrote:

> On Thu, May 10, 2012 at 11:48:17AM -0700, Andrew Morton wrote:
> > On Thu, 10 May 2012 20:27:05 +0200
> > Johan Hovold <jhovold@gmail.com> wrote:
> > 
> > > Add sub-driver for the LEDs on National Semiconductor / TI LM3533
> > > lighting power chips.
> > > 
> > > The chip provides 256 brightness levels, hardware accelerated blinking
> > > as well as ambient-light-sensor and pwm input control.
> > > 
> > > ...
> > >
> > > +#define to_lm3533_led(_cdev) \
> > > +	container_of(_cdev, struct lm3533_led, cdev)
> > 
> > Minor thing: container_of() is not fully type-safe: it can be passed
> > the address of any struct which contains a field called cdev and will
> > return a struct lm3533_led* (or something like that - it has holes...).
> > 
> > A way to fix that is to wrap container_of() in a real C function, not a
> > macro:
> > 
> > static inline struct lm3533_led *to_lm3533_led(struct struct led_classdev *cdev)
> > {
> > 	return container_of(_cdev, struct lm3533_led, cdev);
> > }
> > 
> > This has been another episode in the ongoing series "macros are always
> > wrong" :)
> 
> Fair enough. :) Seems like the vast majority of drivers still use
> convenience macros such as the this one for this kind of use (where the
> functions are either passed the class device or it is retrieved through
> device driver data).
> 
> Do you want me to replace the other three instances of container_of
> convenience macros in the iio-subdriver and core (already added to the
> mfd tree) as well?

Well, it does result in better code.  How could I say no? ;)

> > > +static ssize_t store_als(struct device *dev,
> > > +					struct device_attribute *attr,
> > > +					const char *buf, size_t len)
> > > +{
> > > +	struct led_classdev *led_cdev = dev_get_drvdata(dev);
> > > +	struct lm3533_led *led = to_lm3533_led(led_cdev);
> > > +	u8 als;
> > > +	u8 reg;
> > > +	u8 mask;
> > > +	int ret;
> > > +
> > > +	if (kstrtou8(buf, 0, &als))
> > > +		return -EINVAL;
> > > +
> > > +	if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
> > > +		return -EINVAL;
> > 
> > The `als != 0' test doesn't do anything, and looks odd.  Is there some
> > magical reason why als==0 would be illegal even if LM3533_ALS_LV_MIN
> > was negative?  If so, it should be documented.
> 
> The non-zero-test is not redundant as 0 is the only valid input outside
> of [LV_MIN,LV_MAX] (in fact, the only three valid values are 0,2 and 3).

ah, OK.  One day I'll get the hang of this C thingy.

> Would you prefer
> 
> 	if ((als < LM3533_ALS_LV_MIN && als != 0) || als > LM3533_ALS_LV_MAX)
> 		return -EINVAL;
> 
> or nested conditionals? Or should I simply add a comment?

A comment would be nice.  That 0 is also permitted is a surprise.

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

* Re: [PATCH v3] leds: add LM3533 LED driver
  2012-05-11 22:24           ` Andrew Morton
@ 2012-05-14 10:25             ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-14 10:25 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Johan Hovold, Richard Purdie, Rob Landley, Samuel Ortiz,
	Jonathan Cameron, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Mark Brown, linux-doc, linux-kernel, Bryan Wu

On Fri, May 11, 2012 at 03:24:36PM -0700, Andrew Morton wrote:
> On Fri, 11 May 2012 11:54:11 +0200
> Johan Hovold <jhovold@gmail.com> wrote:
> > On Thu, May 10, 2012 at 11:48:17AM -0700, Andrew Morton wrote:
> > > On Thu, 10 May 2012 20:27:05 +0200
> > > Johan Hovold <jhovold@gmail.com> wrote:
> > > 
> > > > Add sub-driver for the LEDs on National Semiconductor / TI LM3533
> > > > lighting power chips.
> > > > 
> > > > The chip provides 256 brightness levels, hardware accelerated blinking
> > > > as well as ambient-light-sensor and pwm input control.

[...]

> > > > +static ssize_t store_als(struct device *dev,
> > > > +					struct device_attribute *attr,
> > > > +					const char *buf, size_t len)
> > > > +{
> > > > +	struct led_classdev *led_cdev = dev_get_drvdata(dev);
> > > > +	struct lm3533_led *led = to_lm3533_led(led_cdev);
> > > > +	u8 als;
> > > > +	u8 reg;
> > > > +	u8 mask;
> > > > +	int ret;
> > > > +
> > > > +	if (kstrtou8(buf, 0, &als))
> > > > +		return -EINVAL;
> > > > +
> > > > +	if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
> > > > +		return -EINVAL;
> > > 
> > > The `als != 0' test doesn't do anything, and looks odd.  Is there some
> > > magical reason why als==0 would be illegal even if LM3533_ALS_LV_MIN
> > > was negative?  If so, it should be documented.
> > 
> > The non-zero-test is not redundant as 0 is the only valid input outside
> > of [LV_MIN,LV_MAX] (in fact, the only three valid values are 0,2 and 3).
> 
> ah, OK.  One day I'll get the hang of this C thingy.
> 
> > Would you prefer
> > 
> > 	if ((als < LM3533_ALS_LV_MIN && als != 0) || als > LM3533_ALS_LV_MAX)
> > 		return -EINVAL;
> > 
> > or nested conditionals? Or should I simply add a comment?
> 
> A comment would be nice.  That 0 is also permitted is a surprise.

Actually, there is a comment already documenting the valid values and
it's placed above the show function immediately above the store one:

/*
 * ALS-control setting:
 *
 *   0 - ALS disabled
 *   2 - ALS-mapper 2
 *   3 - ALS-mapper 3
 */
static ssize_t show_als(struct device *dev,
...
static ssize_t store_als(struct device *dev,


Thanks,
Johan

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

* [PATCH v4] leds: add LM3533 LED driver
  2012-05-10 18:27     ` [PATCH v3] " Johan Hovold
  2012-05-10 18:48       ` Andrew Morton
@ 2012-05-14 10:31       ` Johan Hovold
  1 sibling, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-14 10:31 UTC (permalink / raw)
  To: Richard Purdie, Andrew Morton
  Cc: Rob Landley, Samuel Ortiz, Jonathan Cameron, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Arnd Bergmann, Mark Brown, linux-doc,
	linux-kernel, Johan Hovold

Add sub-driver for the LEDs on National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels, hardware accelerated blinking
as well as ambient-light-sensor and pwm input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2
 - add sysfs-ABI documentation
 - open code max_current/pwm macros
v3
 - remove max_current attribute
v4
 - fix return type of attribute is_visible
 - replace to_lm3533_leds macro with inline function
 - clean up delay handling


 .../ABI/testing/sysfs-class-led-driver-lm3533      |   58 ++
 drivers/leds/Kconfig                               |   13 +
 drivers/leds/Makefile                              |    1 +
 drivers/leds/leds-lm3533.c                         |  738 ++++++++++++++++++++
 4 files changed, 810 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-led-driver-lm3533
 create mode 100644 drivers/leds/leds-lm3533.c

diff --git a/Documentation/ABI/testing/sysfs-class-led-driver-lm3533 b/Documentation/ABI/testing/sysfs-class-led-driver-lm3533
new file mode 100644
index 0000000..a9633fd
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-led-driver-lm3533
@@ -0,0 +1,58 @@
+What:		/sys/class/leds/<led>/als
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS-control mode (0, 2, 3), where
+
+		0 - disabled
+		2 - ALS-mapper 2
+		3 - ALS-mapper 3
+
+What:		/sys/class/leds/<led>/falltime
+What:		/sys/class/leds/<led>/risetime
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the pattern generator fall and rise times (0..7), where
+
+		0 - 2048 us
+		1 - 262 ms
+		2 - 524 ms
+		3 - 1.049 s
+		4 - 2.097 s
+		5 - 4.194 s
+		6 - 8.389 s
+		7 - 16.78 s
+
+What:		/sys/class/leds/<led>/id
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the id of this led (0..3).
+
+What:		/sys/class/leds/<led>/linear
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the brightness-mapping mode (0, 1), where
+
+		0 - exponential mode
+		1 - linear mode
+
+What:		/sys/class/leds/<led>/pwm
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the PWM-input control mask (5 bits), where
+
+		bit 5 - PWM-input enabled in Zone 4
+		bit 4 - PWM-input enabled in Zone 3
+		bit 3 - PWM-input enabled in Zone 2
+		bit 2 - PWM-input enabled in Zone 1
+		bit 1 - PWM-input enabled in Zone 0
+		bit 0 - PWM-input enabled
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index ff4b8cf..19bd829 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -50,6 +50,19 @@ config LEDS_LM3530
 	  controlled manually or using PWM input or using ambient
 	  light automatically.
 
+config LEDS_LM3533
+	tristate "LED support for LM3533"
+	depends on LEDS_CLASS
+	depends on MFD_LM3533
+	help
+	  This option enables support for the LEDs on National Semiconductor /
+	  TI LM3533 Lighting Power chips.
+
+	  The LEDs can be controlled directly, through PWM input, or by the
+	  ambient-light-sensor interface. The chip supports
+	  hardware-accelerated blinking with maximum on and off periods of 9.8
+	  and 77 seconds respectively.
+
 config LEDS_LOCOMO
 	tristate "LED Support for Locomo device"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 890481c..f39a526 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_ATMEL_PWM)		+= leds-atmel-pwm.o
 obj-$(CONFIG_LEDS_BD2802)		+= leds-bd2802.o
 obj-$(CONFIG_LEDS_LOCOMO)		+= leds-locomo.o
 obj-$(CONFIG_LEDS_LM3530)		+= leds-lm3530.o
+obj-$(CONFIG_LEDS_LM3533)		+= leds-lm3533.o
 obj-$(CONFIG_LEDS_MIKROTIK_RB532)	+= leds-rb532.o
 obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
 obj-$(CONFIG_LEDS_NET48XX)		+= leds-net48xx.o
diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c
new file mode 100644
index 0000000..4ef496c
--- /dev/null
+++ b/drivers/leds/leds-lm3533.c
@@ -0,0 +1,738 @@
+/*
+ * leds-lm3533.c -- LM3533 LED driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/mfd/core.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_LVCTRLBANK_MIN		2
+#define LM3533_LVCTRLBANK_MAX		5
+#define LM3533_LVCTRLBANK_COUNT		4
+#define LM3533_RISEFALLTIME_MAX		7
+#define LM3533_ALS_LV_MIN		2
+#define LM3533_ALS_LV_MAX		3
+
+#define LM3533_REG_CTRLBANK_BCONF_BASE		0x1b
+#define LM3533_REG_PATTERN_ENABLE		0x28
+#define LM3533_REG_PATTERN_LOW_TIME_BASE	0x71
+#define LM3533_REG_PATTERN_HIGH_TIME_BASE	0x72
+#define LM3533_REG_PATTERN_RISETIME_BASE	0x74
+#define LM3533_REG_PATTERN_FALLTIME_BASE	0x75
+
+#define LM3533_REG_PATTERN_STEP			0x10
+
+#define LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK	0x04
+#define LM3533_REG_CTRLBANK_BCONF_ALS_MASK	0x03
+
+#define LM3533_LED_FLAG_PATTERN_ENABLE		1
+
+
+struct lm3533_led {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct led_classdev cdev;
+	int id;
+
+	struct mutex mutex;
+	unsigned long flags;
+
+	struct work_struct work;
+	u8 new_brightness;
+};
+
+
+static inline struct lm3533_led *to_lm3533_led(struct led_classdev *cdev)
+{
+	return container_of(cdev, struct lm3533_led, cdev);
+}
+
+static inline int lm3533_led_get_ctrlbank_id(struct lm3533_led *led)
+{
+	return led->id + 2;
+}
+
+static inline u8 lm3533_led_get_lv_reg(struct lm3533_led *led, u8 base)
+{
+	return base + led->id;
+}
+
+static inline u8 lm3533_led_get_pattern(struct lm3533_led *led)
+{
+	return led->id;
+}
+
+static inline u8 lm3533_led_get_pattern_reg(struct lm3533_led *led,
+								u8 base)
+{
+	return base + lm3533_led_get_pattern(led) * LM3533_REG_PATTERN_STEP;
+}
+
+static int lm3533_led_pattern_enable(struct lm3533_led *led, int enable)
+{
+	u8 mask;
+	u8 val;
+	int pattern;
+	int state;
+	int ret = 0;
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, enable);
+
+	mutex_lock(&led->mutex);
+
+	state = test_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+	if ((enable && state) || (!enable && !state))
+		goto out;
+
+	pattern = lm3533_led_get_pattern(led);
+	mask = 1 << (2 * pattern);
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, LM3533_REG_PATTERN_ENABLE, val, mask);
+	if (ret) {
+		dev_err(led->cdev.dev, "failed to enable pattern %d (%d)\n",
+							pattern, enable);
+		goto out;
+	}
+
+	__change_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+out:
+	mutex_unlock(&led->mutex);
+
+	return ret;
+}
+
+static void lm3533_led_work(struct work_struct *work)
+{
+	struct lm3533_led *led = container_of(work, struct lm3533_led, work);
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, led->new_brightness);
+
+	if (led->new_brightness == 0)
+		lm3533_led_pattern_enable(led, 0);	/* disable blink */
+
+	lm3533_ctrlbank_set_brightness(&led->cb, led->new_brightness);
+}
+
+static void lm3533_led_set(struct led_classdev *cdev,
+						enum led_brightness value)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+
+	dev_dbg(led->cdev.dev, "%s - %d\n", __func__, value);
+
+	led->new_brightness = value;
+	schedule_work(&led->work);
+}
+
+static enum led_brightness lm3533_led_get(struct led_classdev *cdev)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&led->cb, &val);
+	if (ret)
+		return ret;
+
+	dev_dbg(led->cdev.dev, "%s - %u\n", __func__, val);
+
+	return val;
+}
+
+/* Pattern generator defines (delays in us). */
+#define LM3533_LED_DELAY1_VMIN	0x00
+#define LM3533_LED_DELAY2_VMIN	0x3d
+#define LM3533_LED_DELAY3_VMIN	0x80
+
+#define LM3533_LED_DELAY1_VMAX	(LM3533_LED_DELAY2_VMIN - 1)
+#define LM3533_LED_DELAY2_VMAX	(LM3533_LED_DELAY3_VMIN - 1)
+#define LM3533_LED_DELAY3_VMAX	0xff
+
+#define LM3533_LED_DELAY1_TMIN	16384U
+#define LM3533_LED_DELAY2_TMIN	1130496U
+#define LM3533_LED_DELAY3_TMIN	10305536U
+
+#define LM3533_LED_DELAY1_TMAX	999424U
+#define LM3533_LED_DELAY2_TMAX	9781248U
+#define LM3533_LED_DELAY3_TMAX	76890112U
+
+/* t_step = (t_max - t_min) / (v_max - v_min) */
+#define LM3533_LED_DELAY1_TSTEP	16384
+#define LM3533_LED_DELAY2_TSTEP	131072
+#define LM3533_LED_DELAY3_TSTEP	524288
+
+/* Delay limits for hardware accelerated blinking (in ms). */
+#define LM3533_LED_DELAY_ON_MAX \
+	((LM3533_LED_DELAY2_TMAX + LM3533_LED_DELAY2_TSTEP / 2) / 1000)
+#define LM3533_LED_DELAY_OFF_MAX \
+	((LM3533_LED_DELAY3_TMAX + LM3533_LED_DELAY3_TSTEP / 2) / 1000)
+
+/*
+ * Returns linear map of *t from [t_min,t_max] to [v_min,v_max] with a step
+ * size of t_step, where
+ *
+ *	t_step = (t_max - t_min) / (v_max - v_min)
+ *
+ * and updates *t to reflect the mapped value.
+ */
+static u8 time_to_val(unsigned *t, unsigned t_min, unsigned t_step,
+							u8 v_min, u8 v_max)
+{
+	unsigned val;
+
+	val = (*t + t_step / 2 - t_min) / t_step + v_min;
+
+	*t = t_step * (val - v_min) + t_min;
+
+	return (u8)val;
+}
+
+/*
+ * Returns time code corresponding to *delay (in ms) and updates *delay to
+ * reflect actual hardware delay.
+ *
+ * Hardware supports 256 discrete delay times, divided into three groups with
+ * the following ranges and step-sizes:
+ *
+ *	[   16,   999]	[0x00, 0x3e]	step  16 ms
+ *	[ 1130,  9781]	[0x3d, 0x7f]	step 131 ms
+ *	[10306, 76890]	[0x80, 0xff]	step 524 ms
+ *
+ * Note that delay group 3 is only available for delay_off.
+ */
+static u8 lm3533_led_get_hw_delay(unsigned *delay)
+{
+	unsigned t;
+	u8 val;
+
+	t = *delay * 1000;
+
+	if (t >= (LM3533_LED_DELAY2_TMAX + LM3533_LED_DELAY3_TMIN) / 2) {
+		t = clamp(t, LM3533_LED_DELAY3_TMIN, LM3533_LED_DELAY3_TMAX);
+		val = time_to_val(&t,	LM3533_LED_DELAY3_TMIN,
+					LM3533_LED_DELAY3_TSTEP,
+					LM3533_LED_DELAY3_VMIN,
+					LM3533_LED_DELAY3_VMAX);
+	} else if (t >= (LM3533_LED_DELAY1_TMAX + LM3533_LED_DELAY2_TMIN) / 2) {
+		t = clamp(t, LM3533_LED_DELAY2_TMIN, LM3533_LED_DELAY2_TMAX);
+		val = time_to_val(&t,	LM3533_LED_DELAY2_TMIN,
+					LM3533_LED_DELAY2_TSTEP,
+					LM3533_LED_DELAY2_VMIN,
+					LM3533_LED_DELAY2_VMAX);
+	} else {
+		t = clamp(t, LM3533_LED_DELAY1_TMIN, LM3533_LED_DELAY1_TMAX);
+		val = time_to_val(&t,	LM3533_LED_DELAY1_TMIN,
+					LM3533_LED_DELAY1_TSTEP,
+					LM3533_LED_DELAY1_VMIN,
+					LM3533_LED_DELAY1_VMAX);
+	}
+
+	*delay = (t + 500) / 1000;
+
+	return val;
+}
+
+/*
+ * Set delay register base to *delay (in ms) and update *delay to reflect
+ * actual hardware delay used.
+ */
+static u8 lm3533_led_delay_set(struct lm3533_led *led, u8 base,
+							unsigned long *delay)
+{
+	unsigned t;
+	u8 val;
+	u8 reg;
+	int ret;
+
+	t = (unsigned)*delay;
+
+	/* Delay group 3 is only available for low time (delay off). */
+	if (base != LM3533_REG_PATTERN_LOW_TIME_BASE)
+		t = min(t, LM3533_LED_DELAY2_TMAX / 1000);
+
+	val = lm3533_led_get_hw_delay(&t);
+
+	dev_dbg(led->cdev.dev, "%s - %lu: %u (0x%02x)\n", __func__,
+							*delay, t, val);
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
+
+	*delay = t;
+
+	return ret;
+}
+
+static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
+{
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
+}
+
+static int lm3533_led_delay_off_set(struct lm3533_led *led, unsigned long *t)
+{
+	return lm3533_led_delay_set(led, LM3533_REG_PATTERN_LOW_TIME_BASE, t);
+}
+
+static int lm3533_led_blink_set(struct led_classdev *cdev,
+				unsigned long *delay_on,
+				unsigned long *delay_off)
+{
+	struct lm3533_led *led = to_lm3533_led(cdev);
+	int ret;
+
+	dev_dbg(led->cdev.dev, "%s - on = %lu, off = %lu\n", __func__,
+							*delay_on, *delay_off);
+
+	if (*delay_on > LM3533_LED_DELAY_ON_MAX ||
+					*delay_off > LM3533_LED_DELAY_OFF_MAX)
+		return -EINVAL;
+
+	if (*delay_on == 0 && *delay_off == 0) {
+		*delay_on = 500;
+		*delay_off = 500;
+	}
+
+	ret = lm3533_led_delay_on_set(led, delay_on);
+	if (ret)
+		return ret;
+
+	ret = lm3533_led_delay_off_set(led, delay_off);
+	if (ret)
+		return ret;
+
+	return lm3533_led_pattern_enable(led, 1);
+}
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", led->id);
+}
+
+/*
+ * Pattern generator rise/fall times:
+ *
+ *   0 - 2048 us (default)
+ *   1 - 262 ms
+ *   2 - 524 ms
+ *   3 - 1.049 s
+ *   4 - 2.097 s
+ *   5 - 4.194 s
+ *   6 - 8.389 s
+ *   7 - 16.78 s
+ */
+static ssize_t show_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					char *buf, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	ssize_t ret;
+	u8 reg;
+	u8 val;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", val);
+}
+
+static ssize_t show_risetime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t show_falltime(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return show_risefalltime(dev, attr, buf,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+static ssize_t store_risefalltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len, u8 base)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	u8 reg;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > LM3533_RISEFALLTIME_MAX)
+		return -EINVAL;
+
+	reg = lm3533_led_get_pattern_reg(led, base);
+	ret = lm3533_write(led->lm3533, reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t store_risetime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t store_falltime(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	return store_risefalltime(dev, attr, buf, len,
+					LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+/*
+ * ALS-control setting:
+ *
+ *   0 - ALS disabled
+ *   2 - ALS-mapper 2
+ *   3 - ALS-mapper 3
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int als;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	als = val & LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 als;
+	u8 reg;
+	u8 mask;
+	int ret;
+
+	if (kstrtou8(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+	ret = lm3533_update(led->lm3533, reg, als, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 reg;
+	u8 val;
+	int linear;
+	int ret;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	ret = lm3533_read(led->lm3533, reg, &val);
+	if (ret)
+		return ret;
+
+	if (val & LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	unsigned long linear;
+	u8 reg;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+	mask = LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK;
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(led->lm3533, reg, val, mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_pwm(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_pwm(&led->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_pwm(&led->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RW(falltime);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(pwm);
+static LM3533_ATTR_RW(risetime);
+
+static struct attribute *lm3533_led_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_falltime.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_pwm.attr,
+	&dev_attr_risetime.attr,
+	NULL,
+};
+
+static umode_t lm3533_led_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm3533_led *led = to_lm3533_led(led_cdev);
+	umode_t mode = attr->mode;
+
+	if (attr == &dev_attr_als.attr) {
+		if (!led->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_led_attribute_group = {
+	.is_visible	= lm3533_led_attr_is_visible,
+	.attrs		= lm3533_led_attributes
+};
+
+static int __devinit lm3533_led_setup(struct lm3533_led *led,
+					struct lm3533_led_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&led->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&led->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_led_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_led_platform_data *pdata;
+	struct lm3533_led *led;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_LVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal LED id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	led = kzalloc(sizeof(*led), GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	led->lm3533 = lm3533;
+	led->cdev.name = pdata->name;
+	led->cdev.default_trigger = pdata->default_trigger;
+	led->cdev.brightness_set = lm3533_led_set;
+	led->cdev.brightness_get = lm3533_led_get;
+	led->cdev.blink_set = lm3533_led_blink_set;
+	led->cdev.brightness = LED_OFF;
+	led->id = pdev->id;
+
+	mutex_init(&led->mutex);
+	INIT_WORK(&led->work, lm3533_led_work);
+
+	/* The class framework makes a callback to get brightness during
+	 * registration so use parent device (for error reporting) until
+	 * registered.
+	 */
+	led->cb.lm3533 = lm3533;
+	led->cb.id = lm3533_led_get_ctrlbank_id(led);
+	led->cb.dev = lm3533->dev;
+
+	platform_set_drvdata(pdev, led);
+
+	ret = led_classdev_register(pdev->dev.parent, &led->cdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id);
+		goto err_free;
+	}
+
+	led->cb.dev = led->cdev.dev;
+
+	ret = sysfs_create_group(&led->cdev.dev->kobj,
+						&lm3533_led_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	ret = lm3533_led_setup(led, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&led->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+err_unregister:
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+err_free:
+	kfree(led);
+
+	return ret;
+}
+
+static int __devexit lm3533_led_remove(struct platform_device *pdev)
+{
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+	led_classdev_unregister(&led->cdev);
+	flush_work_sync(&led->work);
+	kfree(led);
+
+	return 0;
+}
+
+static void lm3533_led_shutdown(struct platform_device *pdev)
+{
+
+	struct lm3533_led *led = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&led->cb);
+	lm3533_led_set(&led->cdev, LED_OFF);		/* disable blink */
+	flush_work_sync(&led->work);
+}
+
+static struct platform_driver lm3533_led_driver = {
+	.driver = {
+		.name = "lm3533-leds",
+		.owner = THIS_MODULE,
+	},
+	.probe		= lm3533_led_probe,
+	.remove		= __devexit_p(lm3533_led_remove),
+	.shutdown	= lm3533_led_shutdown,
+};
+module_platform_driver(lm3533_led_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-leds");
-- 
1.7.8.5


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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
  2012-05-08 13:47           ` Jonathan Cameron
@ 2012-05-15 16:44             ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-15 16:44 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Johan Hovold, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

On Tue, May 08, 2012 at 02:47:19PM +0100, Jonathan Cameron wrote:
> On 5/3/2012 5:36 PM, Johan Hovold wrote:
> > On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
> >> On 5/3/2012 11:26 AM, Johan Hovold wrote:
> >>> Add sub-driver for the ambient light sensor interface on National
> >>> Semiconductor / TI LM3533 lighting power chips.
> >>>
> >>> The sensor interface can be used to control the LEDs and backlights of
> >>> the chip through defining five light zones and three sets of
> >>> corresponding brightness target levels.
> >>>
> >>> The driver provides raw and mean adc readings along with the current
> >>> light zone through sysfs. A threshold event can be generated on zone
> >>> changes.
> >> Code is fine.  Pretty much all my comments are to do with the interface.
> > [...]
> >
> >>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> >>> new file mode 100644
> >>> index 0000000..9849d14
> >>> --- /dev/null
> >>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> >>> @@ -0,0 +1,62 @@
> >>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
> >>> +Date:		April 2012
> >>> +KernelVersion:	3.5
> >>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>> +Description:
> >>> +		Set the ALS gain-resistor setting (0..127) for analog input
> >>> +		mode, where
> >>> +
> >>> +		0000000 - ALS input is high impedance
> >>> +		0000001 - 200kOhm (10uA at 2V full-scale)
> >>> +		0000010 - 100kOhm (20uA at 2V full-scale)
> >>> +		...
> >>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
> >>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
> >>> +
> >>> +		R_als = 2V / (10uA * gain)	(gain>   0)
> >> Firstly, no magic numbers.  These are definitely magic.
> > Not that magic as they're clearly documented (in code and public
> > datasheets), right? What would you prefer instead?
> The numbers on the right of the - look good to me though then this isn't
> a gain.  (200kohm) and the infinite element is annoying.  Why not
> compute the actual gains?
> Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
> a bit of fixed point maths in the driver but the advantage is you'll
> have real values that are standardizable across multiple devices
> and hence allow your device to be operated by generic userspace
> code.  Welcome to standardising interfaces - my favourite occupation ;)
>
> >> Secondly see in_illuminance0_scale for a suitable existing attribute.
> > I didn't consider scale to be appropriate given the following
> > documentation (e.g, for in_voltageY_scale):
> sorry  I just did this to someone else in another review (so I'm 
> consistently wrong!)
> 
> in_voltageY_calibscale is what I should have said.  That one applies a
> scaling before the raw reading is generated (so in hardware).

Ok, then calibscale is the appropriate attribute for the resistor
setting. But as this is a device-specific hardware-calibration setting
I would suggest using the following interface:

What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance_calibscale
Description:
		Set the ALS calibration scale (internal resistors) for
		analog input mode, where the scale factor is the current in uA
		at 2V full-scale (10..1270, 10uA step), that is,

		R_als = 2V / in_illuminance_calibscale

		This setting is ignored in PWM mode.

[...]

> >>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
> >>> +Date:		April 2012
> >>> +KernelVersion:	3.5
> >>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>> +Description:
> >>> +		Set the target brightness for ALS-mapper m in light zone n
> >>> +		(0..255), where m in 1..3 and n in 0..4.
> >> Don't suppose you could do a quick summary of what these zones are
> >> and why there are 3 ALS-mappers?  I'm not getting terribly far on a
> >> quick look at the datasheet!
> > Of course. The average adc readings are mapped to five light zones using
> > eight zone boundary registers (4 boundaries with hysteresis) and a set
> > of rules.
> This is going to be fun.  We'll need the boundaries and attached 
> hysteresis attributes to fully specify these (nothing would indicate
> that hysterisis is involved otherwise).

You can't define the hysteresis explicitly with the lm3533 register
interface, rather it's is defined implicitly in case threshY_falling is
less than threshY_rasising.

So the raising/falling attributes should be enough, right?

> > To simplify somewhat (by ignoring some of the rules): If the average
> > adc input drops below boundary0_low, the zone register reads 0; if it
> > drops below boundary1_low, it reads 1, and so on. If the input it
> > increases over boundary3_high, the zone register return 4; if it
> > increases passed boundary2_high, it returns zone 3, etc.
> >
> > That is, roughly something like (we get 8-bits of input from the ADC):
> >
> > 	zone 0
> >
> > boundary0_low	51
> > boundary0_high	53
> >
> > 	zone 1
> >
> > boundary1_low	102
> > boundary1_high	106
> >
> > 	zone 2
> >
> > boundary2_low	153
> > boundary2_high	161
> >
> > 	zone 3
> >
> > boundary3_low	204
> > boundary3_high	220
> >
> > 	zone 4
> >
> > [ Figure 6 on page 20 in the datasheets should make it clear. ]
> >
> > The ALS interface and it's zone concept can then be used to control the
> > LEDs and backlights of the chip, by determining the target brightness for
> > each zone, e.g., set brightness to 52 when in zone 0.
> >
> > To complicate things further (and it is complicated), there are three
> > such sets of target brightness values: ALSM1, ALSM2, ALSM3.
> >
> > So for each LED or backlight you can set ALS-input control mode, by
> > saying that the device should get it's brightness levels from target set
> > 1, 2, or 3.
> >
> > [ And it gets even more complicated, as ALSM1 can only control
> >    backlight0, where as ALSM2 and ALSM3 can control any of the remaining
> >    devices, but that's irrelevant here. ]
> >
> > Initially, I thought this interface to be too esoteric to be worth
> > generalising, but it sort of fits with event thresholds so I gave it a
> > try.
> Glad you did and it pretty much fits, be it with a few extensions being 
> necessary.
> > The biggest conceptual problem, I think, is that the zone
> > boundaries can be used to control the other devices, even when the event
> > is not enabled (or even an irq line not configured). That is, I find it
> > a bit awkward that the event thresholds also defines the zones (a sort of
> > discrete scaling factor).
> That is indeed awkward. I'm not sure how we handle this either.  If we 
> need to control these from the other devices (e.g. the back light
> driver) then we'll have to get them into chan_spec and use the
> inkernel interfaces to do it.  Not infeasible but I was hoping to
> avoid that until we have had a few months to see what similar devices
> show up (on basis nothing in this world is a one off for long ;)

I don't think the control bits can or should be generalised at this
point. The same ALS-target values may be used to control more than one
device, so they need to be set from the als rather from the controlled
device (otherwise, changing the target value of led1 could change that
of the other three leds without the user realising that this can be a
side effect).

> > Perhaps simply keeping the attributes outside of events (e.g. named
> > boundary[n]_{low,high}) and having a custom event enabled (e.g.
> > in_illuminance_zone_change_en) is the best solution?
> Maybe, but it's ugly and as you have said, they do correspond pretty well to
> thresholds so I'd rather you went with that.
> The core stuff for registering events clearly needs a rethink.... For 
> now doing it as you describe above (with the addition fo hysteresis
> attributes) should be fine.  Just document the 'quirks'.

Ok, I'll keep the event/zone interface as it stands for now and we'll
see if it can be generalised later. [ See my comment on the hysteresis
above: there are only the rising/falling thresholds (low/high
boundaries) and no boundary or hysteresis settings. ]

Thanks,
Johan

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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
@ 2012-05-15 16:44             ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-15 16:44 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Johan Hovold, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

On Tue, May 08, 2012 at 02:47:19PM +0100, Jonathan Cameron wrote:
> On 5/3/2012 5:36 PM, Johan Hovold wrote:
> > On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
> >> On 5/3/2012 11:26 AM, Johan Hovold wrote:
> >>> Add sub-driver for the ambient light sensor interface on National
> >>> Semiconductor / TI LM3533 lighting power chips.
> >>>
> >>> The sensor interface can be used to control the LEDs and backlights of
> >>> the chip through defining five light zones and three sets of
> >>> corresponding brightness target levels.
> >>>
> >>> The driver provides raw and mean adc readings along with the current
> >>> light zone through sysfs. A threshold event can be generated on zone
> >>> changes.
> >> Code is fine.  Pretty much all my comments are to do with the interface.
> > [...]
> >
> >>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> >>> new file mode 100644
> >>> index 0000000..9849d14
> >>> --- /dev/null
> >>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> >>> @@ -0,0 +1,62 @@
> >>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
> >>> +Date:		April 2012
> >>> +KernelVersion:	3.5
> >>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>> +Description:
> >>> +		Set the ALS gain-resistor setting (0..127) for analog input
> >>> +		mode, where
> >>> +
> >>> +		0000000 - ALS input is high impedance
> >>> +		0000001 - 200kOhm (10uA at 2V full-scale)
> >>> +		0000010 - 100kOhm (20uA at 2V full-scale)
> >>> +		...
> >>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
> >>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
> >>> +
> >>> +		R_als = 2V / (10uA * gain)	(gain>   0)
> >> Firstly, no magic numbers.  These are definitely magic.
> > Not that magic as they're clearly documented (in code and public
> > datasheets), right? What would you prefer instead?
> The numbers on the right of the - look good to me though then this isn't
> a gain.  (200kohm) and the infinite element is annoying.  Why not
> compute the actual gains?
> Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
> a bit of fixed point maths in the driver but the advantage is you'll
> have real values that are standardizable across multiple devices
> and hence allow your device to be operated by generic userspace
> code.  Welcome to standardising interfaces - my favourite occupation ;)
>
> >> Secondly see in_illuminance0_scale for a suitable existing attribute.
> > I didn't consider scale to be appropriate given the following
> > documentation (e.g, for in_voltageY_scale):
> sorry  I just did this to someone else in another review (so I'm 
> consistently wrong!)
> 
> in_voltageY_calibscale is what I should have said.  That one applies a
> scaling before the raw reading is generated (so in hardware).

Ok, then calibscale is the appropriate attribute for the resistor
setting. But as this is a device-specific hardware-calibration setting
I would suggest using the following interface:

What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance_calibscale
Description:
		Set the ALS calibration scale (internal resistors) for
		analog input mode, where the scale factor is the current in uA
		at 2V full-scale (10..1270, 10uA step), that is,

		R_als = 2V / in_illuminance_calibscale

		This setting is ignored in PWM mode.

[...]

> >>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
> >>> +Date:		April 2012
> >>> +KernelVersion:	3.5
> >>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>> +Description:
> >>> +		Set the target brightness for ALS-mapper m in light zone n
> >>> +		(0..255), where m in 1..3 and n in 0..4.
> >> Don't suppose you could do a quick summary of what these zones are
> >> and why there are 3 ALS-mappers?  I'm not getting terribly far on a
> >> quick look at the datasheet!
> > Of course. The average adc readings are mapped to five light zones using
> > eight zone boundary registers (4 boundaries with hysteresis) and a set
> > of rules.
> This is going to be fun.  We'll need the boundaries and attached 
> hysteresis attributes to fully specify these (nothing would indicate
> that hysterisis is involved otherwise).

You can't define the hysteresis explicitly with the lm3533 register
interface, rather it's is defined implicitly in case threshY_falling is
less than threshY_rasising.

So the raising/falling attributes should be enough, right?

> > To simplify somewhat (by ignoring some of the rules): If the average
> > adc input drops below boundary0_low, the zone register reads 0; if it
> > drops below boundary1_low, it reads 1, and so on. If the input it
> > increases over boundary3_high, the zone register return 4; if it
> > increases passed boundary2_high, it returns zone 3, etc.
> >
> > That is, roughly something like (we get 8-bits of input from the ADC):
> >
> > 	zone 0
> >
> > boundary0_low	51
> > boundary0_high	53
> >
> > 	zone 1
> >
> > boundary1_low	102
> > boundary1_high	106
> >
> > 	zone 2
> >
> > boundary2_low	153
> > boundary2_high	161
> >
> > 	zone 3
> >
> > boundary3_low	204
> > boundary3_high	220
> >
> > 	zone 4
> >
> > [ Figure 6 on page 20 in the datasheets should make it clear. ]
> >
> > The ALS interface and it's zone concept can then be used to control the
> > LEDs and backlights of the chip, by determining the target brightness for
> > each zone, e.g., set brightness to 52 when in zone 0.
> >
> > To complicate things further (and it is complicated), there are three
> > such sets of target brightness values: ALSM1, ALSM2, ALSM3.
> >
> > So for each LED or backlight you can set ALS-input control mode, by
> > saying that the device should get it's brightness levels from target set
> > 1, 2, or 3.
> >
> > [ And it gets even more complicated, as ALSM1 can only control
> >    backlight0, where as ALSM2 and ALSM3 can control any of the remaining
> >    devices, but that's irrelevant here. ]
> >
> > Initially, I thought this interface to be too esoteric to be worth
> > generalising, but it sort of fits with event thresholds so I gave it a
> > try.
> Glad you did and it pretty much fits, be it with a few extensions being 
> necessary.
> > The biggest conceptual problem, I think, is that the zone
> > boundaries can be used to control the other devices, even when the event
> > is not enabled (or even an irq line not configured). That is, I find it
> > a bit awkward that the event thresholds also defines the zones (a sort of
> > discrete scaling factor).
> That is indeed awkward. I'm not sure how we handle this either.  If we 
> need to control these from the other devices (e.g. the back light
> driver) then we'll have to get them into chan_spec and use the
> inkernel interfaces to do it.  Not infeasible but I was hoping to
> avoid that until we have had a few months to see what similar devices
> show up (on basis nothing in this world is a one off for long ;)

I don't think the control bits can or should be generalised at this
point. The same ALS-target values may be used to control more than one
device, so they need to be set from the als rather from the controlled
device (otherwise, changing the target value of led1 could change that
of the other three leds without the user realising that this can be a
side effect).

> > Perhaps simply keeping the attributes outside of events (e.g. named
> > boundary[n]_{low,high}) and having a custom event enabled (e.g.
> > in_illuminance_zone_change_en) is the best solution?
> Maybe, but it's ugly and as you have said, they do correspond pretty well to
> thresholds so I'd rather you went with that.
> The core stuff for registering events clearly needs a rethink.... For 
> now doing it as you describe above (with the addition fo hysteresis
> attributes) should be fine.  Just document the 'quirks'.

Ok, I'll keep the event/zone interface as it stands for now and we'll
see if it can be generalised later. [ See my comment on the hysteresis
above: there are only the rising/falling thresholds (low/high
boundaries) and no boundary or hysteresis settings. ]

Thanks,
Johan

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

* [PATCH v3] iio: add LM3533 ambient-light-sensor driver
  2012-05-03 10:26     ` Johan Hovold
  (?)
  (?)
@ 2012-05-15 16:46     ` Johan Hovold
  2012-05-15 19:27       ` Andrew Morton
  2012-05-18 13:07       ` [PATCH v4] " Johan Hovold
  -1 siblings, 2 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-15 16:46 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Arnd Bergmann, Andrew Morton,
	Mark Brown, linux-doc, linux-kernel, linux-iio, devel,
	Johan Hovold

Add sub-driver for the ambient-light-sensor interface on National
Semiconductor / TI LM3533 lighting power chips.

The sensor interface can be used to control the LEDs and backlights of
the chip through defining five light zones and three sets of
corresponding brightness target levels.

The driver provides raw and mean adc readings along with the current
light zone through sysfs. A threshold event can be generated on zone
changes.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

This is a v3 rebased against staging-next of today (93c66ee1186a). Note
that I added calibscale to the platform data and that the modification
of the header file probably needs to go in via mfd once we have agreed
on the type.

Thanks,
Johan


v2:
 - reimplement using iio
 - add sysfs-ABI documentation
v3
 - use indexed channel
 - fix sysfs-ABI documentation typo and style
 - replace gain attribute with in_illuminance0_calibscale
 - add calibscale to platform data
 - fix adc register definitions
 - replace to_lm3533_dev_attr macro with inline function
 - fix device used for error reporting at irq allocation
 - use iio device for error reporting during setup/enable
 - rebase on staging-next
   - fix header include paths
   - use dev_to_iio_dev
   - add IIO_CHAN_INFO_RAW to info mask
   - use iio_device_{alloc,free}


 .../Documentation/sysfs-bus-iio-light-lm3533-als   |   52 ++
 drivers/staging/iio/light/Kconfig                  |   16 +
 drivers/staging/iio/light/Makefile                 |    1 +
 drivers/staging/iio/light/lm3533-als.c             |  726 ++++++++++++++++++++
 include/linux/mfd/lm3533.h                         |    3 +
 5 files changed, 798 insertions(+), 0 deletions(-)
 create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
 create mode 100644 drivers/staging/iio/light/lm3533-als.c

diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
new file mode 100644
index 0000000..ba31538
--- /dev/null
+++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
@@ -0,0 +1,52 @@
+What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_calibscale
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS calibration scale (internal resistors) for
+		analog input mode, where the scale factor is the current in uA
+		at 2V full-scale (10..1270, 10uA step), that is,
+
+		R_als = 2V / in_illuminance0_calibscale
+
+		This setting is ignored in PWM mode.
+
+What:		/sys/.../events/in_illuminance0_thresh_either_en
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Event generated when channel passes one of the four thresholds
+		in each direction (rising|falling) and a zone change occurs.
+		The corresponding light zone can be read from
+		in_illuminance0_zone.
+
+What:		/sys/.../events/illuminance_threshY_falling_value
+What:		/sys/.../events/illuminance_threshY_raising_value
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Specifies the value of threshold that the device is
+		comparing against for the events enabled by
+		in_illuminance0_thresh_either_en, where Y in 0..3.
+
+		These thresholds correspond to the eight zone-boundary
+		registers (boundaryY_{low,high}) and defines the five light
+		zones.
+
+What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the current light zone (0..4) as defined by the
+		in_illuminance0_threshY_{falling,rising} thresholds.
+
+What:		/sys/bus/iio/devices/iio:deviceX/targetY_Z
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the target brightness for ALS-mapper Y in light zone Z
+		(0..255), where Y in 1..3 and Z in 0..4.
diff --git a/drivers/staging/iio/light/Kconfig b/drivers/staging/iio/light/Kconfig
index 4bed30e..2170124 100644
--- a/drivers/staging/iio/light/Kconfig
+++ b/drivers/staging/iio/light/Kconfig
@@ -35,6 +35,22 @@ config SENSORS_TSL2563
 	 This driver can also be built as a module.  If so, the module
 	 will be called tsl2563.
 
+config SENSORS_LM3533
+	tristate "LM3533 ambient light sensor"
+	depends on MFD_LM3533
+	help
+	  If you say yes here you get support for the ambient light sensor
+	  interface on National Semiconductor / TI LM3533 Lighting Power
+	  chips.
+
+	  The sensor interface can be used to control the LEDs and backlights
+	  of the chip through defining five light zones and three sets of
+	  corresponding brightness target levels.
+
+	  The driver provides raw and mean adc readings along with the current
+	  light zone through sysfs. A threshold event can be generated on zone
+	  changes.
+
 config TSL2583
 	tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters"
 	depends on I2C
diff --git a/drivers/staging/iio/light/Makefile b/drivers/staging/iio/light/Makefile
index 141af1e..a8c6144 100644
--- a/drivers/staging/iio/light/Makefile
+++ b/drivers/staging/iio/light/Makefile
@@ -5,5 +5,6 @@
 obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o
 obj-$(CONFIG_SENSORS_ISL29018)	+= isl29018.o
 obj-$(CONFIG_SENSORS_ISL29028)	+= isl29028.o
+obj-$(CONFIG_SENSORS_LM3533)	+= lm3533-als.o
 obj-$(CONFIG_TSL2583)	+= tsl2583.o
 obj-$(CONFIG_TSL2x7x)	+= tsl2x7x_core.o
diff --git a/drivers/staging/iio/light/lm3533-als.c b/drivers/staging/iio/light/lm3533-als.c
new file mode 100644
index 0000000..75b315c
--- /dev/null
+++ b/drivers/staging/iio/light/lm3533-als.c
@@ -0,0 +1,726 @@
+/*
+ * lm3533-als.c -- LM3533 Ambient Light Sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/atomic.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_ALS_ADC_MAX			0xff
+#define LM3533_ALS_BOUNDARY_MAX			LM3533_ALS_ADC_MAX
+#define LM3533_ALS_CALIBSCALE_MIN		10
+#define LM3533_ALS_CALIBSCALE_MAX		1270
+#define LM3533_ALS_CALIBSCALE_STEP		10
+#define LM3533_ALS_TARGET_MAX			LM3533_ALS_ADC_MAX
+#define LM3533_ALS_ZONE_MAX			4
+
+#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
+#define LM3533_REG_ALS_CONF			0x31
+#define LM3533_REG_ALS_ZONE_INFO		0x34
+#define LM3533_REG_ALS_READ_ADC_RAW		0x37
+#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x38
+#define LM3533_REG_ALS_BOUNDARY0_HIGH		0x50
+#define LM3533_REG_ALS_BOUNDARY0_LOW		0x51
+#define LM3533_REG_ALS_BOUNDARY1_HIGH		0x52
+#define LM3533_REG_ALS_BOUNDARY1_LOW		0x53
+#define LM3533_REG_ALS_BOUNDARY2_HIGH		0x54
+#define LM3533_REG_ALS_BOUNDARY2_LOW		0x55
+#define LM3533_REG_ALS_BOUNDARY3_HIGH		0x56
+#define LM3533_REG_ALS_BOUNDARY3_LOW		0x57
+#define LM3533_REG_ALS_M1_TARGET_0		0x60
+#define LM3533_REG_ALS_M1_TARGET_1		0x61
+#define LM3533_REG_ALS_M1_TARGET_2		0x62
+#define LM3533_REG_ALS_M1_TARGET_3		0x63
+#define LM3533_REG_ALS_M1_TARGET_4		0x64
+#define LM3533_REG_ALS_M2_TARGET_0		0x65
+#define LM3533_REG_ALS_M2_TARGET_1		0x66
+#define LM3533_REG_ALS_M2_TARGET_2		0x67
+#define LM3533_REG_ALS_M2_TARGET_3		0x68
+#define LM3533_REG_ALS_M2_TARGET_4		0x69
+#define LM3533_REG_ALS_M3_TARGET_0		0x6a
+#define LM3533_REG_ALS_M3_TARGET_1		0x6b
+#define LM3533_REG_ALS_M3_TARGET_2		0x6c
+#define LM3533_REG_ALS_M3_TARGET_3		0x6d
+#define LM3533_REG_ALS_M3_TARGET_4		0x6e
+
+#define LM3533_ALS_ENABLE_MASK			0x01
+#define LM3533_ALS_INPUT_MODE_MASK		0x02
+#define LM3533_ALS_INT_ENABLE_MASK		0x01
+
+#define LM3533_ALS_ZONE_SHIFT			2
+#define LM3533_ALS_ZONE_MASK			0x1c
+
+#define LM3533_ALS_FLAG_INT_ENABLED		1
+
+
+struct lm3533_als {
+	struct lm3533 *lm3533;
+
+	unsigned long flags;
+	int irq;
+
+	int pwm_mode:1;
+
+	atomic_t zone;
+};
+
+
+static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
+								int *adc)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	u8 val;
+	int ret;
+
+	if (average)
+		reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
+	else
+		reg = LM3533_REG_ALS_READ_ADC_RAW;
+
+	ret = lm3533_read(als->lm3533, reg, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to read adc\n");
+		return ret;
+	}
+
+	*adc = val;
+
+	return 0;
+}
+
+static int lm3533_als_get_calibscale(struct iio_dev *indio_dev, int *scale)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 val;
+	int ret;
+
+	/* calibscale is ignored in pwm-mode */
+	if (als->pwm_mode) {
+		*scale = 0;
+		return 0;
+	}
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to read calibscale\n");
+		return ret;
+	}
+
+	*scale = val * LM3533_ALS_CALIBSCALE_STEP;
+
+	return 0;
+}
+
+static int lm3533_als_set_calibscale(struct iio_dev *indio_dev, int scale)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 val;
+	int ret;
+
+	/* calibscale is ignored in pwm-mode */
+	if (als->pwm_mode)
+		return -EINVAL;
+
+	if (scale < LM3533_ALS_CALIBSCALE_MIN ||
+					scale > LM3533_ALS_CALIBSCALE_MAX)
+		return -EINVAL;
+
+	val = (u8)(scale / LM3533_ALS_CALIBSCALE_STEP);
+
+	ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to write calibscale\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int lm3533_als_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	int ret;
+
+	switch (mask) {
+	case 0:
+		ret = lm3533_als_get_adc(indio_dev, false, val);
+		break;
+	case IIO_CHAN_INFO_AVERAGE_RAW:
+		ret = lm3533_als_get_adc(indio_dev, true, val);
+		break;
+	case IIO_CHAN_INFO_CALIBSCALE:
+		ret = lm3533_als_get_calibscale(indio_dev, val);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (ret)
+		return ret;
+
+	return IIO_VAL_INT;
+}
+
+static int lm3533_als_write_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int val, int val2, long mask)
+{
+	if (mask != IIO_CHAN_INFO_CALIBSCALE)
+		return -EINVAL;
+
+	return lm3533_als_set_calibscale(indio_dev, val);
+}
+
+static const struct iio_chan_spec lm3533_als_channels[] = {
+	{
+		.type		= IIO_LIGHT,
+		.channel	= 0,
+		.indexed	= 1,
+		.info_mask	= (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
+				   IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT |
+				   IIO_CHAN_INFO_RAW_SEPARATE_BIT),
+	}
+};
+
+static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to read zone\n");
+		return ret;
+	}
+
+	val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
+	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
+
+	return 0;
+}
+
+static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
+{
+
+	struct iio_dev *indio_dev = dev_id;
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 zone;
+	int ret;
+
+	/* Clear interrupt by reading the ALS zone register. */
+	ret = lm3533_als_get_zone(indio_dev, &zone);
+	if (ret)
+		goto out;
+
+	atomic_set(&als->zone, zone);
+
+	iio_push_event(indio_dev,
+		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+					    0,
+					    IIO_EV_TYPE_THRESH,
+					    IIO_EV_DIR_EITHER),
+		       iio_get_time_ns());
+out:
+	return IRQ_HANDLED;
+}
+
+static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+	int ret;
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
+								enable);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to get int mode\n");
+		return ret;
+	}
+
+	*enable = !!(val & mask);
+
+	return 0;
+}
+
+static int show_thresh_either_en(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	int enable;
+	int ret;
+
+	if (als->irq) {
+		ret = lm3533_als_get_int_mode(indio_dev, &enable);
+		if (ret)
+			return ret;
+	} else {
+		enable = 0;
+	}
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
+}
+
+static int store_thresh_either_en(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	unsigned long enable;
+	bool int_enabled;
+	u8 zone;
+	int ret;
+
+	if (!als->irq)
+		return -EBUSY;
+
+	if (kstrtoul(buf, 0, &enable))
+		return -EINVAL;
+
+	int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+	if (enable && !int_enabled) {
+		ret = lm3533_als_get_zone(indio_dev, &zone);
+		if (ret)
+			return ret;
+
+		atomic_set(&als->zone, zone);
+
+		set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+	}
+
+	ret = lm3533_als_set_int_mode(indio_dev, enable);
+	if (ret) {
+		if (!int_enabled)
+			clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+		return ret;
+	}
+
+	if (!enable)
+		clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+	return len;
+}
+
+static ssize_t show_zone(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 zone;
+	int ret;
+
+	if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) {
+		zone = atomic_read(&als->zone);
+	} else {
+		ret = lm3533_als_get_zone(indio_dev, &zone);
+		if (ret)
+			return ret;
+	}
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
+}
+
+struct lm3533_device_attribute {
+	struct device_attribute dev_attr;
+	u8 reg;
+	u8 max;
+};
+
+static inline struct lm3533_device_attribute *
+to_lm3533_dev_attr(struct device_attribute *attr)
+{
+	return container_of(attr, struct lm3533_device_attribute, dev_attr);
+}
+
+static ssize_t show_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, lm3533_attr->reg, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_lm3533_als_reg(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val) || val > lm3533_attr->max)
+		return -EINVAL;
+
+	ret = lm3533_write(als->lm3533, lm3533_attr->reg, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	{ .dev_attr = __ATTR(_name, _mode, _show, _store), \
+	  .reg = _reg, \
+	  .max = _max }
+
+#define LM3533_REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+	struct lm3533_device_attribute lm3533_dev_attr_##_name \
+		= REG_ATTR(_name, _mode, _show, _store, _reg, _max)
+
+#define LM3533_REG_ATTR_RW(_name, _reg, _max) \
+	LM3533_REG_ATTR(_name, S_IRUGO | S_IWUSR, show_lm3533_als_reg, \
+					store_lm3533_als_reg, _reg, _max)
+
+#define ALS_THRESH_FALLING_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(in_illuminance0_thresh##_nr##_falling_value, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_LOW, LM3533_ALS_BOUNDARY_MAX)
+
+#define ALS_THRESH_RAISING_ATTR_RW(_nr) \
+	LM3533_REG_ATTR_RW(in_illuminance0_thresh##_nr##_raising_value, \
+		LM3533_REG_ALS_BOUNDARY##_nr##_HIGH, LM3533_ALS_BOUNDARY_MAX)
+
+/*
+ * ALS Zone thresholds (boundaries)
+ *
+ * in_illuminance0_thresh[0-3]_falling_value	0-255
+ * in_illuminance0_thresh[0-3]_raising_value	0-255
+ */
+static ALS_THRESH_FALLING_ATTR_RW(0);
+static ALS_THRESH_FALLING_ATTR_RW(1);
+static ALS_THRESH_FALLING_ATTR_RW(2);
+static ALS_THRESH_FALLING_ATTR_RW(3);
+
+static ALS_THRESH_RAISING_ATTR_RW(0);
+static ALS_THRESH_RAISING_ATTR_RW(1);
+static ALS_THRESH_RAISING_ATTR_RW(2);
+static ALS_THRESH_RAISING_ATTR_RW(3);
+
+#define LM3533_ALS_ATTR_RO(_name) \
+	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
+#define LM3533_ALS_ATTR_RW(_name) \
+	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
+						show_##_name, store_##_name)
+
+/*
+ * ALS Zone threshold-event enable
+ *
+ * in_illuminance0_thresh_either_en		0,1
+ */
+static LM3533_ALS_ATTR_RW(thresh_either_en);
+
+/*
+ * ALS Current Zone
+ *
+ * in_illuminance0_zone		0-4
+ */
+static LM3533_ALS_ATTR_RO(zone);
+
+#define ALS_TARGET_ATTR_RW(_mapper, _nr) \
+	LM3533_REG_ATTR_RW(target##_mapper##_##_nr, \
+		LM3533_REG_ALS_M##_mapper##_TARGET_##_nr, LM3533_ALS_TARGET_MAX)
+
+/*
+ * ALS Mapper targets
+ *
+ * target[1-3]_[0-4]		0-255
+ */
+static ALS_TARGET_ATTR_RW(1, 0);
+static ALS_TARGET_ATTR_RW(1, 1);
+static ALS_TARGET_ATTR_RW(1, 2);
+static ALS_TARGET_ATTR_RW(1, 3);
+static ALS_TARGET_ATTR_RW(1, 4);
+
+static ALS_TARGET_ATTR_RW(2, 0);
+static ALS_TARGET_ATTR_RW(2, 1);
+static ALS_TARGET_ATTR_RW(2, 2);
+static ALS_TARGET_ATTR_RW(2, 3);
+static ALS_TARGET_ATTR_RW(2, 4);
+
+static ALS_TARGET_ATTR_RW(3, 0);
+static ALS_TARGET_ATTR_RW(3, 1);
+static ALS_TARGET_ATTR_RW(3, 2);
+static ALS_TARGET_ATTR_RW(3, 3);
+static ALS_TARGET_ATTR_RW(3, 4);
+
+static struct attribute *lm3533_als_event_attributes[] = {
+	&dev_attr_in_illuminance0_thresh_either_en.attr,
+	&lm3533_dev_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
+	&lm3533_dev_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group lm3533_als_event_attribute_group = {
+	.attrs = lm3533_als_event_attributes
+};
+
+static struct attribute *lm3533_als_attributes[] = {
+	&lm3533_dev_attr_target1_0.dev_attr.attr,
+	&lm3533_dev_attr_target1_1.dev_attr.attr,
+	&lm3533_dev_attr_target1_2.dev_attr.attr,
+	&lm3533_dev_attr_target1_3.dev_attr.attr,
+	&lm3533_dev_attr_target1_4.dev_attr.attr,
+	&lm3533_dev_attr_target2_0.dev_attr.attr,
+	&lm3533_dev_attr_target2_1.dev_attr.attr,
+	&lm3533_dev_attr_target2_2.dev_attr.attr,
+	&lm3533_dev_attr_target2_3.dev_attr.attr,
+	&lm3533_dev_attr_target2_4.dev_attr.attr,
+	&lm3533_dev_attr_target3_0.dev_attr.attr,
+	&lm3533_dev_attr_target3_1.dev_attr.attr,
+	&lm3533_dev_attr_target3_2.dev_attr.attr,
+	&lm3533_dev_attr_target3_3.dev_attr.attr,
+	&lm3533_dev_attr_target3_4.dev_attr.attr,
+	&dev_attr_in_illuminance0_zone.attr,
+	NULL
+};
+
+static struct attribute_group lm3533_als_attribute_group = {
+	.attrs = lm3533_als_attributes
+};
+
+static int __devinit lm3533_als_set_input_mode(struct iio_dev *indio_dev,
+								int pwm_mode)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
+	u8 val;
+	int ret;
+
+	if (pwm_mode)
+		val = mask;	/* pwm input */
+	else
+		val = 0;	/* analog input */
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret) {
+		dev_err(&indio_dev->dev,
+				"failed to set input mode %d\n", pwm_mode);
+	}
+
+	return ret;
+}
+
+static int __devinit lm3533_als_setup(struct iio_dev *indio_dev,
+					struct lm3533_als_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_als_set_input_mode(indio_dev, pdata->pwm_mode);
+	if (ret)
+		return ret;
+
+	if (!pdata->pwm_mode) {
+		ret = lm3533_als_set_calibscale(indio_dev, pdata->calibscale);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int __devinit lm3533_als_enable(struct iio_dev *indio_dev)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret)
+		dev_err(&indio_dev->dev, "failed to enable ALS\n");
+
+	return ret;
+}
+
+static int __devexit lm3533_als_disable(struct iio_dev *indio_dev)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
+	if (ret)
+		dev_err(&indio_dev->dev, "failed to disable ALS\n");
+
+	return ret;
+}
+
+static const struct iio_info lm3533_als_info = {
+	.attrs		= &lm3533_als_attribute_group,
+	.event_attrs	= &lm3533_als_event_attribute_group,
+	.driver_module	= THIS_MODULE,
+	.read_raw	= &lm3533_als_read_raw,
+	.write_raw	= &lm3533_als_write_raw,
+};
+
+static int __devinit lm3533_als_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_als_platform_data *pdata;
+	struct lm3533_als *als;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	indio_dev = iio_device_alloc(sizeof(*als));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	indio_dev->info = &lm3533_als_info;
+	indio_dev->channels = lm3533_als_channels;
+	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
+	indio_dev->name = "lm3533-als";
+	indio_dev->dev.parent = pdev->dev.parent;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	als = iio_priv(indio_dev);
+	als->lm3533 = lm3533;
+	als->irq = lm3533->irq;
+	als->pwm_mode = pdata->pwm_mode;
+	atomic_set(&als->zone, 0);
+
+	if (als->irq) {
+		ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
+						IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+						indio_dev->name, indio_dev);
+		if (ret) {
+			dev_err(&pdev->dev, "failed to request irq %d\n",
+								als->irq);
+			goto err_free_dev;
+		}
+	}
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	ret = iio_device_register(indio_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register ALS\n");
+		goto err_free_irq;
+	}
+
+	ret = lm3533_als_setup(indio_dev, pdata);
+	if (ret)
+		goto err_unregister;
+
+	ret = lm3533_als_enable(indio_dev);
+	if (ret)
+		goto err_unregister;
+
+	return 0;
+
+err_unregister:
+	iio_device_unregister(indio_dev);
+err_free_irq:
+	if (als->irq)
+		free_irq(als->irq, indio_dev);
+err_free_dev:
+	iio_device_free(indio_dev);
+
+	return ret;
+}
+
+static int __devexit lm3533_als_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_als_disable(indio_dev);
+	iio_device_unregister(indio_dev);
+	if (als->irq)
+		free_irq(als->irq, indio_dev);
+	iio_device_free(indio_dev);
+
+	return 0;
+}
+
+static struct platform_driver lm3533_als_driver = {
+	.driver	= {
+		.name	= "lm3533-als",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= lm3533_als_probe,
+	.remove		= __devexit_p(lm3533_als_remove),
+};
+module_platform_driver(lm3533_als_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
index 9660feb..c1404cc 100644
--- a/include/linux/mfd/lm3533.h
+++ b/include/linux/mfd/lm3533.h
@@ -43,6 +43,9 @@ struct lm3533_ctrlbank {
 
 struct lm3533_als_platform_data {
 	unsigned pwm_mode:1;		/* PWM input mode (default analog) */
+	u16 calibscale;			/* 10 - 1270 uA (10 uA step), current
+					 * at 2V full-scale (analog mode)
+					 */
 };
 
 struct lm3533_bl_platform_data {
-- 
1.7.8.5


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

* [PATCH v4] backlight: add LM3533 backlight driver
  2012-05-10 18:29       ` Johan Hovold
@ 2012-05-15 19:13         ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-15 19:13 UTC (permalink / raw)
  To: Richard Purdie, Florian Tobias Schandinat, Andrew Morton
  Cc: Rob Landley, Samuel Ortiz, Jonathan Cameron, Greg Kroah-Hartman,
	Arnd Bergmann, Mark Brown, linux-doc, linux-kernel, linux-fbdev,
	Johan Hovold

Add sub-driver for the backlights on National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels and ambient-light-sensor and pwm
input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2
 - add sysfs-ABI documentation
 - open code max_current/pwm macros
v3
 - remove max_current attribute
v4
 - fix return type of attribute is_visible
 - fix sysfs-ABI-documentation typo


 .../testing/sysfs-class-backlight-driver-lm3533    |   41 ++
 drivers/video/backlight/Kconfig                    |   12 +
 drivers/video/backlight/Makefile                   |    1 +
 drivers/video/backlight/lm3533_bl.c                |  423 ++++++++++++++++++++
 4 files changed, 477 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
 create mode 100644 drivers/video/backlight/lm3533_bl.c

diff --git a/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533 b/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
new file mode 100644
index 0000000..ea91f71
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
@@ -0,0 +1,41 @@
+What:		/sys/class/backlight/<backlight>/als
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS-control mode (0..2), where
+
+		0 - disabled
+		1 - ALS-mapper 1 (backlight 0)
+		2 - ALS-mapper 2 (backlight 1)
+
+What:		/sys/class/backlight/<backlight>/id
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the id of this backlight (0, 1).
+
+What:		/sys/class/backlight/<backlight>/linear
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the brightness-mapping mode (0, 1), where
+
+		0 - exponential mode
+		1 - linear mode
+
+What:		/sys/class/backlight/<backlight>/pwm
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the PWM-input control mask (5 bits), where
+
+		bit 5 - PWM-input enabled in Zone 4
+		bit 4 - PWM-input enabled in Zone 3
+		bit 3 - PWM-input enabled in Zone 2
+		bit 2 - PWM-input enabled in Zone 1
+		bit 1 - PWM-input enabled in Zone 0
+		bit 0 - PWM-input enabled
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index af16884..fa2b037 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -184,6 +184,18 @@ config BACKLIGHT_GENERIC
 	  known as the Corgi backlight driver. If you have a Sharp Zaurus
 	  SL-C7xx, SL-Cxx00 or SL-6000x say y.
 
+config BACKLIGHT_LM3533
+	tristate "Backlight Driver for LM3533"
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on MFD_LM3533
+	help
+	  Say Y to enable the backlight driver for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  The backlights can be controlled directly, through PWM input, or by
+	  the ambient-light-sensor interface. The chip supports 256 brightness
+	  levels.
+
 config BACKLIGHT_LOCOMO
 	tristate "Sharp LOCOMO LCD/Backlight Driver"
 	depends on SHARP_LOCOMO
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 36855ae..a2ac9cf 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_BACKLIGHT_EP93XX)	+= ep93xx_bl.o
 obj-$(CONFIG_BACKLIGHT_GENERIC)	+= generic_bl.o
 obj-$(CONFIG_BACKLIGHT_HP700)	+= jornada720_bl.o
 obj-$(CONFIG_BACKLIGHT_HP680)	+= hp680_bl.o
+obj-$(CONFIG_BACKLIGHT_LM3533)	+= lm3533_bl.o
 obj-$(CONFIG_BACKLIGHT_LOCOMO)	+= locomolcd.o
 obj-$(CONFIG_BACKLIGHT_LP855X)	+= lp855x_bl.o
 obj-$(CONFIG_BACKLIGHT_OMAP1)	+= omap1_bl.o
diff --git a/drivers/video/backlight/lm3533_bl.c b/drivers/video/backlight/lm3533_bl.c
new file mode 100644
index 0000000..0148227
--- /dev/null
+++ b/drivers/video/backlight/lm3533_bl.c
@@ -0,0 +1,423 @@
+/*
+ * lm3533-bl.c -- LM3533 Backlight driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_HVCTRLBANK_COUNT		2
+#define LM3533_BL_MAX_BRIGHTNESS	255
+
+#define LM3533_REG_CTRLBANK_AB_BCONF	0x1a
+
+
+struct lm3533_bl {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct backlight_device *bd;
+	int id;
+};
+
+
+static inline int lm3533_bl_get_ctrlbank_id(struct lm3533_bl *bl)
+{
+	return bl->id;
+}
+
+static int lm3533_bl_update_status(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	int brightness = bd->props.brightness;
+
+	if (bd->props.power != FB_BLANK_UNBLANK)
+		brightness = 0;
+	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	return lm3533_ctrlbank_set_brightness(&bl->cb, (u8)brightness);
+}
+
+static int lm3533_bl_get_brightness(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return val;
+}
+
+static const struct backlight_ops lm3533_bl_ops = {
+	.get_brightness	= lm3533_bl_get_brightness,
+	.update_status	= lm3533_bl_update_status,
+};
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", bl->id);
+}
+
+/*
+ * ALS-control setting:
+ *
+ *   0 - ALS disabled
+ *   1 - ALS-mapper 1 (backlight 0)
+ *   2 - ALS-mapper 2 (backlight 1)
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	u8 val;
+	u8 mask;
+	int als;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 2 * ctrlbank;
+	als = val & mask;
+	if (als)
+		als = ctrlbank + 1;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	int als;
+	u8 val;
+	u8 mask;
+	int ret;
+
+	if (kstrtoint(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als != ctrlbank + 1))
+		return -EINVAL;
+
+	mask = 1 << (2 * ctrlbank);
+
+	if (als)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	u8 mask;
+	int linear;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (val & mask)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	unsigned long linear;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_pwm(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_pwm(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_pwm(&bl->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(pwm);
+
+static struct attribute *lm3533_bl_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_pwm.attr,
+	NULL,
+};
+
+static umode_t lm3533_bl_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	umode_t mode = attr->mode;
+
+	if (attr == &dev_attr_als.attr) {
+		if (!bl->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_bl_attribute_group = {
+	.is_visible	= lm3533_bl_attr_is_visible,
+	.attrs		= lm3533_bl_attributes
+};
+
+static int __devinit lm3533_bl_setup(struct lm3533_bl *bl,
+					struct lm3533_bl_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&bl->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&bl->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_bl_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_bl_platform_data *pdata;
+	struct lm3533_bl *bl;
+	struct backlight_device *bd;
+	struct backlight_properties props;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_HVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal backlight id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	bl = kzalloc(sizeof(*bl), GFP_KERNEL);
+	if (!bl) {
+		dev_err(&pdev->dev,
+				"failed to allocate memory for backlight\n");
+		return -ENOMEM;
+	}
+
+	bl->lm3533 = lm3533;
+	bl->id = pdev->id;
+
+	bl->cb.lm3533 = lm3533;
+	bl->cb.id = lm3533_bl_get_ctrlbank_id(bl);
+	bl->cb.dev = NULL;			/* until registered */
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = LM3533_BL_MAX_BRIGHTNESS;
+	props.brightness = pdata->default_brightness;
+	bd = backlight_device_register(pdata->name, pdev->dev.parent, bl,
+						&lm3533_bl_ops, &props);
+	if (IS_ERR(bd)) {
+		dev_err(&pdev->dev, "failed to register backlight device\n");
+		ret = PTR_ERR(bd);
+		goto err_free;
+	}
+
+	bl->bd = bd;
+	bl->cb.dev = &bl->bd->dev;
+
+	platform_set_drvdata(pdev, bl);
+
+	ret = sysfs_create_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	backlight_update_status(bd);
+
+	ret = lm3533_bl_setup(bl, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&bl->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+err_unregister:
+	backlight_device_unregister(bd);
+err_free:
+	kfree(bl);
+
+	return ret;
+}
+
+static int __devexit lm3533_bl_remove(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+	struct backlight_device *bd = bl->bd;
+
+	dev_dbg(&bd->dev, "%s\n", __func__);
+
+	bd->props.power = FB_BLANK_POWERDOWN;
+	bd->props.brightness = 0;
+
+	lm3533_ctrlbank_disable(&bl->cb);
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	backlight_device_unregister(bd);
+	kfree(bl);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int lm3533_bl_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static int lm3533_bl_resume(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_enable(&bl->cb);
+}
+#else
+#define lm3533_bl_suspend	NULL
+#define lm3533_bl_resume	NULL
+#endif
+
+static void lm3533_bl_shutdown(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static struct platform_driver lm3533_bl_driver = {
+	.driver = {
+		.name	= "lm3533-backlight",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= lm3533_bl_probe,
+	.remove		= __devexit_p(lm3533_bl_remove),
+	.shutdown	= lm3533_bl_shutdown,
+	.suspend	= lm3533_bl_suspend,
+	.resume		= lm3533_bl_resume,
+};
+module_platform_driver(lm3533_bl_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Backlight driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-backlight");
-- 
1.7.8.5


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

* [PATCH v4] backlight: add LM3533 backlight driver
@ 2012-05-15 19:13         ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-15 19:13 UTC (permalink / raw)
  To: Richard Purdie, Florian Tobias Schandinat, Andrew Morton
  Cc: Rob Landley, Samuel Ortiz, Jonathan Cameron, Greg Kroah-Hartman,
	Arnd Bergmann, Mark Brown, linux-doc, linux-kernel, linux-fbdev,
	Johan Hovold

Add sub-driver for the backlights on National Semiconductor / TI LM3533
lighting power chips.

The chip provides 256 brightness levels and ambient-light-sensor and pwm
input control.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2
 - add sysfs-ABI documentation
 - open code max_current/pwm macros
v3
 - remove max_current attribute
v4
 - fix return type of attribute is_visible
 - fix sysfs-ABI-documentation typo


 .../testing/sysfs-class-backlight-driver-lm3533    |   41 ++
 drivers/video/backlight/Kconfig                    |   12 +
 drivers/video/backlight/Makefile                   |    1 +
 drivers/video/backlight/lm3533_bl.c                |  423 ++++++++++++++++++++
 4 files changed, 477 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
 create mode 100644 drivers/video/backlight/lm3533_bl.c

diff --git a/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533 b/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
new file mode 100644
index 0000000..ea91f71
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-backlight-driver-lm3533
@@ -0,0 +1,41 @@
+What:		/sys/class/backlight/<backlight>/als
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS-control mode (0..2), where
+
+		0 - disabled
+		1 - ALS-mapper 1 (backlight 0)
+		2 - ALS-mapper 2 (backlight 1)
+
+What:		/sys/class/backlight/<backlight>/id
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the id of this backlight (0, 1).
+
+What:		/sys/class/backlight/<backlight>/linear
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the brightness-mapping mode (0, 1), where
+
+		0 - exponential mode
+		1 - linear mode
+
+What:		/sys/class/backlight/<backlight>/pwm
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the PWM-input control mask (5 bits), where
+
+		bit 5 - PWM-input enabled in Zone 4
+		bit 4 - PWM-input enabled in Zone 3
+		bit 3 - PWM-input enabled in Zone 2
+		bit 2 - PWM-input enabled in Zone 1
+		bit 1 - PWM-input enabled in Zone 0
+		bit 0 - PWM-input enabled
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index af16884..fa2b037 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -184,6 +184,18 @@ config BACKLIGHT_GENERIC
 	  known as the Corgi backlight driver. If you have a Sharp Zaurus
 	  SL-C7xx, SL-Cxx00 or SL-6000x say y.
 
+config BACKLIGHT_LM3533
+	tristate "Backlight Driver for LM3533"
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on MFD_LM3533
+	help
+	  Say Y to enable the backlight driver for National Semiconductor / TI
+	  LM3533 Lighting Power chips.
+
+	  The backlights can be controlled directly, through PWM input, or by
+	  the ambient-light-sensor interface. The chip supports 256 brightness
+	  levels.
+
 config BACKLIGHT_LOCOMO
 	tristate "Sharp LOCOMO LCD/Backlight Driver"
 	depends on SHARP_LOCOMO
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 36855ae..a2ac9cf 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_BACKLIGHT_EP93XX)	+= ep93xx_bl.o
 obj-$(CONFIG_BACKLIGHT_GENERIC)	+= generic_bl.o
 obj-$(CONFIG_BACKLIGHT_HP700)	+= jornada720_bl.o
 obj-$(CONFIG_BACKLIGHT_HP680)	+= hp680_bl.o
+obj-$(CONFIG_BACKLIGHT_LM3533)	+= lm3533_bl.o
 obj-$(CONFIG_BACKLIGHT_LOCOMO)	+= locomolcd.o
 obj-$(CONFIG_BACKLIGHT_LP855X)	+= lp855x_bl.o
 obj-$(CONFIG_BACKLIGHT_OMAP1)	+= omap1_bl.o
diff --git a/drivers/video/backlight/lm3533_bl.c b/drivers/video/backlight/lm3533_bl.c
new file mode 100644
index 0000000..0148227
--- /dev/null
+++ b/drivers/video/backlight/lm3533_bl.c
@@ -0,0 +1,423 @@
+/*
+ * lm3533-bl.c -- LM3533 Backlight driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_HVCTRLBANK_COUNT		2
+#define LM3533_BL_MAX_BRIGHTNESS	255
+
+#define LM3533_REG_CTRLBANK_AB_BCONF	0x1a
+
+
+struct lm3533_bl {
+	struct lm3533 *lm3533;
+	struct lm3533_ctrlbank cb;
+	struct backlight_device *bd;
+	int id;
+};
+
+
+static inline int lm3533_bl_get_ctrlbank_id(struct lm3533_bl *bl)
+{
+	return bl->id;
+}
+
+static int lm3533_bl_update_status(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	int brightness = bd->props.brightness;
+
+	if (bd->props.power != FB_BLANK_UNBLANK)
+		brightness = 0;
+	if (bd->props.fb_blank != FB_BLANK_UNBLANK)
+		brightness = 0;
+
+	return lm3533_ctrlbank_set_brightness(&bl->cb, (u8)brightness);
+}
+
+static int lm3533_bl_get_brightness(struct backlight_device *bd)
+{
+	struct lm3533_bl *bl = bl_get_data(bd);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_brightness(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return val;
+}
+
+static const struct backlight_ops lm3533_bl_ops = {
+	.get_brightness	= lm3533_bl_get_brightness,
+	.update_status	= lm3533_bl_update_status,
+};
+
+static ssize_t show_id(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", bl->id);
+}
+
+/*
+ * ALS-control setting:
+ *
+ *   0 - ALS disabled
+ *   1 - ALS-mapper 1 (backlight 0)
+ *   2 - ALS-mapper 2 (backlight 1)
+ */
+static ssize_t show_als(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	u8 val;
+	u8 mask;
+	int als;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 2 * ctrlbank;
+	als = val & mask;
+	if (als)
+		als = ctrlbank + 1;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
+	int als;
+	u8 val;
+	u8 mask;
+	int ret;
+
+	if (kstrtoint(buf, 0, &als))
+		return -EINVAL;
+
+	if (als != 0 && (als != ctrlbank + 1))
+		return -EINVAL;
+
+	mask = 1 << (2 * ctrlbank);
+
+	if (als)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	u8 mask;
+	int linear;
+	int ret;
+
+	ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
+	if (ret)
+		return ret;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (val & mask)
+		linear = 1;
+	else
+		linear = 0;
+
+	return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	unsigned long linear;
+	u8 mask;
+	u8 val;
+	int ret;
+
+	if (kstrtoul(buf, 0, &linear))
+		return -EINVAL;
+
+	mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
+
+	if (linear)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
+									mask);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t show_pwm(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_ctrlbank_get_pwm(&bl->cb, &val);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	ret = lm3533_ctrlbank_set_pwm(&bl->cb, val);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(pwm);
+
+static struct attribute *lm3533_bl_attributes[] = {
+	&dev_attr_als.attr,
+	&dev_attr_id.attr,
+	&dev_attr_linear.attr,
+	&dev_attr_pwm.attr,
+	NULL,
+};
+
+static umode_t lm3533_bl_attr_is_visible(struct kobject *kobj,
+					     struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, struct device, kobj);
+	struct lm3533_bl *bl = dev_get_drvdata(dev);
+	umode_t mode = attr->mode;
+
+	if (attr = &dev_attr_als.attr) {
+		if (!bl->lm3533->have_als)
+			mode = 0;
+	}
+
+	return mode;
+};
+
+static struct attribute_group lm3533_bl_attribute_group = {
+	.is_visible	= lm3533_bl_attr_is_visible,
+	.attrs		= lm3533_bl_attributes
+};
+
+static int __devinit lm3533_bl_setup(struct lm3533_bl *bl,
+					struct lm3533_bl_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_ctrlbank_set_max_current(&bl->cb, pdata->max_current);
+	if (ret)
+		return ret;
+
+	return lm3533_ctrlbank_set_pwm(&bl->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_bl_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_bl_platform_data *pdata;
+	struct lm3533_bl *bl;
+	struct backlight_device *bd;
+	struct backlight_properties props;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	if (pdev->id < 0 || pdev->id >= LM3533_HVCTRLBANK_COUNT) {
+		dev_err(&pdev->dev, "illegal backlight id %d\n", pdev->id);
+		return -EINVAL;
+	}
+
+	bl = kzalloc(sizeof(*bl), GFP_KERNEL);
+	if (!bl) {
+		dev_err(&pdev->dev,
+				"failed to allocate memory for backlight\n");
+		return -ENOMEM;
+	}
+
+	bl->lm3533 = lm3533;
+	bl->id = pdev->id;
+
+	bl->cb.lm3533 = lm3533;
+	bl->cb.id = lm3533_bl_get_ctrlbank_id(bl);
+	bl->cb.dev = NULL;			/* until registered */
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = LM3533_BL_MAX_BRIGHTNESS;
+	props.brightness = pdata->default_brightness;
+	bd = backlight_device_register(pdata->name, pdev->dev.parent, bl,
+						&lm3533_bl_ops, &props);
+	if (IS_ERR(bd)) {
+		dev_err(&pdev->dev, "failed to register backlight device\n");
+		ret = PTR_ERR(bd);
+		goto err_free;
+	}
+
+	bl->bd = bd;
+	bl->cb.dev = &bl->bd->dev;
+
+	platform_set_drvdata(pdev, bl);
+
+	ret = sysfs_create_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+		goto err_unregister;
+	}
+
+	backlight_update_status(bd);
+
+	ret = lm3533_bl_setup(bl, pdata);
+	if (ret)
+		goto err_sysfs_remove;
+
+	ret = lm3533_ctrlbank_enable(&bl->cb);
+	if (ret)
+		goto err_sysfs_remove;
+
+	return 0;
+
+err_sysfs_remove:
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+err_unregister:
+	backlight_device_unregister(bd);
+err_free:
+	kfree(bl);
+
+	return ret;
+}
+
+static int __devexit lm3533_bl_remove(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+	struct backlight_device *bd = bl->bd;
+
+	dev_dbg(&bd->dev, "%s\n", __func__);
+
+	bd->props.power = FB_BLANK_POWERDOWN;
+	bd->props.brightness = 0;
+
+	lm3533_ctrlbank_disable(&bl->cb);
+	sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
+	backlight_device_unregister(bd);
+	kfree(bl);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int lm3533_bl_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static int lm3533_bl_resume(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	return lm3533_ctrlbank_enable(&bl->cb);
+}
+#else
+#define lm3533_bl_suspend	NULL
+#define lm3533_bl_resume	NULL
+#endif
+
+static void lm3533_bl_shutdown(struct platform_device *pdev)
+{
+	struct lm3533_bl *bl = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_ctrlbank_disable(&bl->cb);
+}
+
+static struct platform_driver lm3533_bl_driver = {
+	.driver = {
+		.name	= "lm3533-backlight",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= lm3533_bl_probe,
+	.remove		= __devexit_p(lm3533_bl_remove),
+	.shutdown	= lm3533_bl_shutdown,
+	.suspend	= lm3533_bl_suspend,
+	.resume		= lm3533_bl_resume,
+};
+module_platform_driver(lm3533_bl_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Backlight driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-backlight");
-- 
1.7.8.5


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

* Re: [PATCH v3] iio: add LM3533 ambient-light-sensor driver
  2012-05-15 16:46     ` [PATCH v3] iio: add LM3533 ambient-light-sensor driver Johan Hovold
@ 2012-05-15 19:27       ` Andrew Morton
  2012-05-15 20:00         ` Johan Hovold
  2012-05-18 13:07       ` [PATCH v4] " Johan Hovold
  1 sibling, 1 reply; 131+ messages in thread
From: Andrew Morton @ 2012-05-15 19:27 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Mark Brown, linux-doc, linux-kernel, linux-iio, devel

On Tue, 15 May 2012 18:46:36 +0200
Johan Hovold <jhovold@gmail.com> wrote:

> Add sub-driver for the ambient-light-sensor interface on National
> Semiconductor / TI LM3533 lighting power chips.
> 
> The sensor interface can be used to control the LEDs and backlights of
> the chip through defining five light zones and three sets of
> corresponding brightness target levels.
> 
> The driver provides raw and mean adc readings along with the current
> light zone through sysfs. A threshold event can be generated on zone
> changes.
>
> ...
>
>  drivers/staging/iio/light/Kconfig                  |   16 +
>  drivers/staging/iio/light/Makefile                 |    1 +
>  drivers/staging/iio/light/lm3533-als.c             |  726 ++++++++++++++++++++

Why is this aimed at drivers/staging/, rather than directly into
drivers/iio/?


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

* Re: [PATCH v3] iio: add LM3533 ambient-light-sensor driver
  2012-05-15 19:27       ` Andrew Morton
@ 2012-05-15 20:00         ` Johan Hovold
  2012-05-15 20:16           ` Jonathan Cameron
  0 siblings, 1 reply; 131+ messages in thread
From: Johan Hovold @ 2012-05-15 20:00 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Johan Hovold, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel

On Tue, May 15, 2012 at 12:27:05PM -0700, Andrew Morton wrote:
> On Tue, 15 May 2012 18:46:36 +0200
> Johan Hovold <jhovold@gmail.com> wrote:
> 
> > Add sub-driver for the ambient-light-sensor interface on National
> > Semiconductor / TI LM3533 lighting power chips.
> > 
> > The sensor interface can be used to control the LEDs and backlights of
> > the chip through defining five light zones and three sets of
> > corresponding brightness target levels.
> > 
> > The driver provides raw and mean adc readings along with the current
> > light zone through sysfs. A threshold event can be generated on zone
> > changes.
> >
> > ...
> >
> >  drivers/staging/iio/light/Kconfig                  |   16 +
> >  drivers/staging/iio/light/Makefile                 |    1 +
> >  drivers/staging/iio/light/lm3533-als.c             |  726 ++++++++++++++++++++
> 
> Why is this aimed at drivers/staging/, rather than directly into
> drivers/iio/?

I understood it as it was only iio core which was moving out of staging
in 3.5, but I see now that some drivers have been added directly to
driver/iio. My mistake.

I'll make sure to move it to drivers/iio/light.

Thanks for pointing it out,
Johan

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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
  2012-05-15 16:44             ` Johan Hovold
@ 2012-05-15 20:00               ` Jonathan Cameron
  -1 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-15 20:00 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

On 05/15/2012 05:44 PM, Johan Hovold wrote:
> On Tue, May 08, 2012 at 02:47:19PM +0100, Jonathan Cameron wrote:
>> On 5/3/2012 5:36 PM, Johan Hovold wrote:
>>> On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
>>>> On 5/3/2012 11:26 AM, Johan Hovold wrote:
>>>>> Add sub-driver for the ambient light sensor interface on National
>>>>> Semiconductor / TI LM3533 lighting power chips.
>>>>>
>>>>> The sensor interface can be used to control the LEDs and backlights of
>>>>> the chip through defining five light zones and three sets of
>>>>> corresponding brightness target levels.
>>>>>
>>>>> The driver provides raw and mean adc readings along with the current
>>>>> light zone through sysfs. A threshold event can be generated on zone
>>>>> changes.
>>>> Code is fine.  Pretty much all my comments are to do with the interface.
>>> [...]
>>>
>>>>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>>>> new file mode 100644
>>>>> index 0000000..9849d14
>>>>> --- /dev/null
>>>>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>>>> @@ -0,0 +1,62 @@
>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
>>>>> +Date:		April 2012
>>>>> +KernelVersion:	3.5
>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>> +Description:
>>>>> +		Set the ALS gain-resistor setting (0..127) for analog input
>>>>> +		mode, where
>>>>> +
>>>>> +		0000000 - ALS input is high impedance
>>>>> +		0000001 - 200kOhm (10uA at 2V full-scale)
>>>>> +		0000010 - 100kOhm (20uA at 2V full-scale)
>>>>> +		...
>>>>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
>>>>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
>>>>> +
>>>>> +		R_als = 2V / (10uA * gain)	(gain>   0)
>>>> Firstly, no magic numbers.  These are definitely magic.
>>> Not that magic as they're clearly documented (in code and public
>>> datasheets), right? What would you prefer instead?
>> The numbers on the right of the - look good to me though then this isn't
>> a gain.  (200kohm) and the infinite element is annoying.  Why not
>> compute the actual gains?
>> Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
>> a bit of fixed point maths in the driver but the advantage is you'll
>> have real values that are standardizable across multiple devices
>> and hence allow your device to be operated by generic userspace
>> code.  Welcome to standardising interfaces - my favourite occupation ;)
>>
>>>> Secondly see in_illuminance0_scale for a suitable existing attribute.
>>> I didn't consider scale to be appropriate given the following
>>> documentation (e.g, for in_voltageY_scale):
>> sorry  I just did this to someone else in another review (so I'm 
>> consistently wrong!)
>>
>> in_voltageY_calibscale is what I should have said.  That one applies a
>> scaling before the raw reading is generated (so in hardware).
> 
> Ok, then calibscale is the appropriate attribute for the resistor
> setting. But as this is a device-specific hardware-calibration setting
> I would suggest using the following interface:
> 
> What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance_calibscale
> Description:
> 		Set the ALS calibration scale (internal resistors) for
> 		analog input mode, where the scale factor is the current in uA
> 		at 2V full-scale (10..1270, 10uA step), that is,
> 
> 		R_als = 2V / in_illuminance_calibscale
> 
> 		This setting is ignored in PWM mode.
This is a generic element that really ought to just fit in with the
equivalent in sysfs-bus-iio for calibscan.  It's a ratio, so it should
be unit free for starters.
> 
> [...]
> 
>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
>>>>> +Date:		April 2012
>>>>> +KernelVersion:	3.5
>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>> +Description:
>>>>> +		Set the target brightness for ALS-mapper m in light zone n
>>>>> +		(0..255), where m in 1..3 and n in 0..4.
>>>> Don't suppose you could do a quick summary of what these zones are
>>>> and why there are 3 ALS-mappers?  I'm not getting terribly far on a
>>>> quick look at the datasheet!
>>> Of course. The average adc readings are mapped to five light zones using
>>> eight zone boundary registers (4 boundaries with hysteresis) and a set
>>> of rules.
>> This is going to be fun.  We'll need the boundaries and attached 
>> hysteresis attributes to fully specify these (nothing would indicate
>> that hysterisis is involved otherwise).
> 
> You can't define the hysteresis explicitly with the lm3533 register
> interface, rather it's is defined implicitly in case threshY_falling is
> less than threshY_rasising.
> 
> So the raising/falling attributes should be enough, right?
Nope, because they don't tell a general userspace application what is
going on.  Without hysterisis attributes it has no way of knowing there
is hysterisis present.  Feel free to make them read only though.
> 
>>> To simplify somewhat (by ignoring some of the rules): If the average
>>> adc input drops below boundary0_low, the zone register reads 0; if it
>>> drops below boundary1_low, it reads 1, and so on. If the input it
>>> increases over boundary3_high, the zone register return 4; if it
>>> increases passed boundary2_high, it returns zone 3, etc.
>>>
>>> That is, roughly something like (we get 8-bits of input from the ADC):
>>>
>>> 	zone 0
>>>
>>> boundary0_low	51
>>> boundary0_high	53
>>>
>>> 	zone 1
>>>
>>> boundary1_low	102
>>> boundary1_high	106
>>>
>>> 	zone 2
>>>
>>> boundary2_low	153
>>> boundary2_high	161
>>>
>>> 	zone 3
>>>
>>> boundary3_low	204
>>> boundary3_high	220
>>>
>>> 	zone 4
>>>
>>> [ Figure 6 on page 20 in the datasheets should make it clear. ]
>>>
>>> The ALS interface and it's zone concept can then be used to control the
>>> LEDs and backlights of the chip, by determining the target brightness for
>>> each zone, e.g., set brightness to 52 when in zone 0.
>>>
>>> To complicate things further (and it is complicated), there are three
>>> such sets of target brightness values: ALSM1, ALSM2, ALSM3.
>>>
>>> So for each LED or backlight you can set ALS-input control mode, by
>>> saying that the device should get it's brightness levels from target set
>>> 1, 2, or 3.
>>>
>>> [ And it gets even more complicated, as ALSM1 can only control
>>>    backlight0, where as ALSM2 and ALSM3 can control any of the remaining
>>>    devices, but that's irrelevant here. ]
>>>
>>> Initially, I thought this interface to be too esoteric to be worth
>>> generalising, but it sort of fits with event thresholds so I gave it a
>>> try.
>> Glad you did and it pretty much fits, be it with a few extensions being 
>> necessary.
>>> The biggest conceptual problem, I think, is that the zone
>>> boundaries can be used to control the other devices, even when the event
>>> is not enabled (or even an irq line not configured). That is, I find it
>>> a bit awkward that the event thresholds also defines the zones (a sort of
>>> discrete scaling factor).
>> That is indeed awkward. I'm not sure how we handle this either.  If we 
>> need to control these from the other devices (e.g. the back light
>> driver) then we'll have to get them into chan_spec and use the
>> inkernel interfaces to do it.  Not infeasible but I was hoping to
>> avoid that until we have had a few months to see what similar devices
>> show up (on basis nothing in this world is a one off for long ;)
> 
> I don't think the control bits can or should be generalised at this
> point. The same ALS-target values may be used to control more than one
> device, so they need to be set from the als rather from the controlled
> device (otherwise, changing the target value of led1 could change that
> of the other three leds without the user realising that this can be a
> side effect).
Good point.  Nasty little device to write an interface for :)
> 
>>> Perhaps simply keeping the attributes outside of events (e.g. named
>>> boundary[n]_{low,high}) and having a custom event enabled (e.g.
>>> in_illuminance_zone_change_en) is the best solution?
>> Maybe, but it's ugly and as you have said, they do correspond pretty well to
>> thresholds so I'd rather you went with that.
>> The core stuff for registering events clearly needs a rethink.... For 
>> now doing it as you describe above (with the addition fo hysteresis
>> attributes) should be fine.  Just document the 'quirks'.
> 
> Ok, I'll keep the event/zone interface as it stands for now and we'll
> see if it can be generalised later. [ See my comment on the hysteresis
> above: there are only the rising/falling thresholds (low/high
> boundaries) and no boundary or hysteresis settings. ]
On that, just to reiterate, to have anything generalizable, userspace
needs to know that hysterisis exists on the individual thresholds
(though it is clearly a function of the neighbouring one).
> 
> Thanks,
> Johan


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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
@ 2012-05-15 20:00               ` Jonathan Cameron
  0 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-15 20:00 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

On 05/15/2012 05:44 PM, Johan Hovold wrote:
> On Tue, May 08, 2012 at 02:47:19PM +0100, Jonathan Cameron wrote:
>> On 5/3/2012 5:36 PM, Johan Hovold wrote:
>>> On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
>>>> On 5/3/2012 11:26 AM, Johan Hovold wrote:
>>>>> Add sub-driver for the ambient light sensor interface on National
>>>>> Semiconductor / TI LM3533 lighting power chips.
>>>>>
>>>>> The sensor interface can be used to control the LEDs and backlights of
>>>>> the chip through defining five light zones and three sets of
>>>>> corresponding brightness target levels.
>>>>>
>>>>> The driver provides raw and mean adc readings along with the current
>>>>> light zone through sysfs. A threshold event can be generated on zone
>>>>> changes.
>>>> Code is fine.  Pretty much all my comments are to do with the interface.
>>> [...]
>>>
>>>>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>>>> new file mode 100644
>>>>> index 0000000..9849d14
>>>>> --- /dev/null
>>>>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>>>> @@ -0,0 +1,62 @@
>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
>>>>> +Date:		April 2012
>>>>> +KernelVersion:	3.5
>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>> +Description:
>>>>> +		Set the ALS gain-resistor setting (0..127) for analog input
>>>>> +		mode, where
>>>>> +
>>>>> +		0000000 - ALS input is high impedance
>>>>> +		0000001 - 200kOhm (10uA at 2V full-scale)
>>>>> +		0000010 - 100kOhm (20uA at 2V full-scale)
>>>>> +		...
>>>>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
>>>>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
>>>>> +
>>>>> +		R_als = 2V / (10uA * gain)	(gain>   0)
>>>> Firstly, no magic numbers.  These are definitely magic.
>>> Not that magic as they're clearly documented (in code and public
>>> datasheets), right? What would you prefer instead?
>> The numbers on the right of the - look good to me though then this isn't
>> a gain.  (200kohm) and the infinite element is annoying.  Why not
>> compute the actual gains?
>> Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
>> a bit of fixed point maths in the driver but the advantage is you'll
>> have real values that are standardizable across multiple devices
>> and hence allow your device to be operated by generic userspace
>> code.  Welcome to standardising interfaces - my favourite occupation ;)
>>
>>>> Secondly see in_illuminance0_scale for a suitable existing attribute.
>>> I didn't consider scale to be appropriate given the following
>>> documentation (e.g, for in_voltageY_scale):
>> sorry  I just did this to someone else in another review (so I'm 
>> consistently wrong!)
>>
>> in_voltageY_calibscale is what I should have said.  That one applies a
>> scaling before the raw reading is generated (so in hardware).
> 
> Ok, then calibscale is the appropriate attribute for the resistor
> setting. But as this is a device-specific hardware-calibration setting
> I would suggest using the following interface:
> 
> What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance_calibscale
> Description:
> 		Set the ALS calibration scale (internal resistors) for
> 		analog input mode, where the scale factor is the current in uA
> 		at 2V full-scale (10..1270, 10uA step), that is,
> 
> 		R_als = 2V / in_illuminance_calibscale
> 
> 		This setting is ignored in PWM mode.
This is a generic element that really ought to just fit in with the
equivalent in sysfs-bus-iio for calibscan.  It's a ratio, so it should
be unit free for starters.
> 
> [...]
> 
>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
>>>>> +Date:		April 2012
>>>>> +KernelVersion:	3.5
>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>> +Description:
>>>>> +		Set the target brightness for ALS-mapper m in light zone n
>>>>> +		(0..255), where m in 1..3 and n in 0..4.
>>>> Don't suppose you could do a quick summary of what these zones are
>>>> and why there are 3 ALS-mappers?  I'm not getting terribly far on a
>>>> quick look at the datasheet!
>>> Of course. The average adc readings are mapped to five light zones using
>>> eight zone boundary registers (4 boundaries with hysteresis) and a set
>>> of rules.
>> This is going to be fun.  We'll need the boundaries and attached 
>> hysteresis attributes to fully specify these (nothing would indicate
>> that hysterisis is involved otherwise).
> 
> You can't define the hysteresis explicitly with the lm3533 register
> interface, rather it's is defined implicitly in case threshY_falling is
> less than threshY_rasising.
> 
> So the raising/falling attributes should be enough, right?
Nope, because they don't tell a general userspace application what is
going on.  Without hysterisis attributes it has no way of knowing there
is hysterisis present.  Feel free to make them read only though.
> 
>>> To simplify somewhat (by ignoring some of the rules): If the average
>>> adc input drops below boundary0_low, the zone register reads 0; if it
>>> drops below boundary1_low, it reads 1, and so on. If the input it
>>> increases over boundary3_high, the zone register return 4; if it
>>> increases passed boundary2_high, it returns zone 3, etc.
>>>
>>> That is, roughly something like (we get 8-bits of input from the ADC):
>>>
>>> 	zone 0
>>>
>>> boundary0_low	51
>>> boundary0_high	53
>>>
>>> 	zone 1
>>>
>>> boundary1_low	102
>>> boundary1_high	106
>>>
>>> 	zone 2
>>>
>>> boundary2_low	153
>>> boundary2_high	161
>>>
>>> 	zone 3
>>>
>>> boundary3_low	204
>>> boundary3_high	220
>>>
>>> 	zone 4
>>>
>>> [ Figure 6 on page 20 in the datasheets should make it clear. ]
>>>
>>> The ALS interface and it's zone concept can then be used to control the
>>> LEDs and backlights of the chip, by determining the target brightness for
>>> each zone, e.g., set brightness to 52 when in zone 0.
>>>
>>> To complicate things further (and it is complicated), there are three
>>> such sets of target brightness values: ALSM1, ALSM2, ALSM3.
>>>
>>> So for each LED or backlight you can set ALS-input control mode, by
>>> saying that the device should get it's brightness levels from target set
>>> 1, 2, or 3.
>>>
>>> [ And it gets even more complicated, as ALSM1 can only control
>>>    backlight0, where as ALSM2 and ALSM3 can control any of the remaining
>>>    devices, but that's irrelevant here. ]
>>>
>>> Initially, I thought this interface to be too esoteric to be worth
>>> generalising, but it sort of fits with event thresholds so I gave it a
>>> try.
>> Glad you did and it pretty much fits, be it with a few extensions being 
>> necessary.
>>> The biggest conceptual problem, I think, is that the zone
>>> boundaries can be used to control the other devices, even when the event
>>> is not enabled (or even an irq line not configured). That is, I find it
>>> a bit awkward that the event thresholds also defines the zones (a sort of
>>> discrete scaling factor).
>> That is indeed awkward. I'm not sure how we handle this either.  If we 
>> need to control these from the other devices (e.g. the back light
>> driver) then we'll have to get them into chan_spec and use the
>> inkernel interfaces to do it.  Not infeasible but I was hoping to
>> avoid that until we have had a few months to see what similar devices
>> show up (on basis nothing in this world is a one off for long ;)
> 
> I don't think the control bits can or should be generalised at this
> point. The same ALS-target values may be used to control more than one
> device, so they need to be set from the als rather from the controlled
> device (otherwise, changing the target value of led1 could change that
> of the other three leds without the user realising that this can be a
> side effect).
Good point.  Nasty little device to write an interface for :)
> 
>>> Perhaps simply keeping the attributes outside of events (e.g. named
>>> boundary[n]_{low,high}) and having a custom event enabled (e.g.
>>> in_illuminance_zone_change_en) is the best solution?
>> Maybe, but it's ugly and as you have said, they do correspond pretty well to
>> thresholds so I'd rather you went with that.
>> The core stuff for registering events clearly needs a rethink.... For 
>> now doing it as you describe above (with the addition fo hysteresis
>> attributes) should be fine.  Just document the 'quirks'.
> 
> Ok, I'll keep the event/zone interface as it stands for now and we'll
> see if it can be generalised later. [ See my comment on the hysteresis
> above: there are only the rising/falling thresholds (low/high
> boundaries) and no boundary or hysteresis settings. ]
On that, just to reiterate, to have anything generalizable, userspace
needs to know that hysterisis exists on the individual thresholds
(though it is clearly a function of the neighbouring one).
> 
> Thanks,
> Johan


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

* Re: [PATCH v3] iio: add LM3533 ambient-light-sensor driver
  2012-05-15 20:00         ` Johan Hovold
@ 2012-05-15 20:16           ` Jonathan Cameron
  0 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-15 20:16 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Andrew Morton, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel

On 05/15/2012 09:00 PM, Johan Hovold wrote:
> On Tue, May 15, 2012 at 12:27:05PM -0700, Andrew Morton wrote:
>> On Tue, 15 May 2012 18:46:36 +0200
>> Johan Hovold <jhovold@gmail.com> wrote:
>>
>>> Add sub-driver for the ambient-light-sensor interface on National
>>> Semiconductor / TI LM3533 lighting power chips.
>>>
>>> The sensor interface can be used to control the LEDs and backlights of
>>> the chip through defining five light zones and three sets of
>>> corresponding brightness target levels.
>>>
>>> The driver provides raw and mean adc readings along with the current
>>> light zone through sysfs. A threshold event can be generated on zone
>>> changes.
>>>
>>> ...
>>>
>>>  drivers/staging/iio/light/Kconfig                  |   16 +
>>>  drivers/staging/iio/light/Makefile                 |    1 +
>>>  drivers/staging/iio/light/lm3533-als.c             |  726 ++++++++++++++++++++
>>
>> Why is this aimed at drivers/staging/, rather than directly into
>> drivers/iio/?
> 
> I understood it as it was only iio core which was moving out of staging
> in 3.5, but I see now that some drivers have been added directly to
> driver/iio. My mistake.
It was more a case that the core was the priority.  Drivers would if
ready and people had time to do the patches!
> 
> I'll make sure to move it to drivers/iio/light.
Yup, that's sensible for a new driver like this.  I'll be even fussier
about the userspace interfaces though ;)

> 
> Thanks for pointing it out,
> Johan


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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
  2012-05-15 20:00               ` Jonathan Cameron
@ 2012-05-16 13:05                 ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-16 13:05 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Johan Hovold, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev

On Tue, May 15, 2012 at 09:00:46PM +0100, Jonathan Cameron wrote:
> On 05/15/2012 05:44 PM, Johan Hovold wrote:
> > On Tue, May 08, 2012 at 02:47:19PM +0100, Jonathan Cameron wrote:
> >> On 5/3/2012 5:36 PM, Johan Hovold wrote:
> >>> On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
> >>>> On 5/3/2012 11:26 AM, Johan Hovold wrote:
> >>>>> Add sub-driver for the ambient light sensor interface on National
> >>>>> Semiconductor / TI LM3533 lighting power chips.
> >>>>>
> >>>>> The sensor interface can be used to control the LEDs and backlights of
> >>>>> the chip through defining five light zones and three sets of
> >>>>> corresponding brightness target levels.
> >>>>>
> >>>>> The driver provides raw and mean adc readings along with the current
> >>>>> light zone through sysfs. A threshold event can be generated on zone
> >>>>> changes.
> >>>> Code is fine.  Pretty much all my comments are to do with the interface.
> >>> [...]
> >>>
> >>>>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> >>>>> new file mode 100644
> >>>>> index 0000000..9849d14
> >>>>> --- /dev/null
> >>>>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> >>>>> @@ -0,0 +1,62 @@
> >>>>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
> >>>>> +Date:		April 2012
> >>>>> +KernelVersion:	3.5
> >>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>> +Description:
> >>>>> +		Set the ALS gain-resistor setting (0..127) for analog input
> >>>>> +		mode, where
> >>>>> +
> >>>>> +		0000000 - ALS input is high impedance
> >>>>> +		0000001 - 200kOhm (10uA at 2V full-scale)
> >>>>> +		0000010 - 100kOhm (20uA at 2V full-scale)
> >>>>> +		...
> >>>>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
> >>>>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
> >>>>> +
> >>>>> +		R_als = 2V / (10uA * gain)	(gain>   0)
> >>>> Firstly, no magic numbers.  These are definitely magic.
> >>> Not that magic as they're clearly documented (in code and public
> >>> datasheets), right? What would you prefer instead?
> >> The numbers on the right of the - look good to me though then this isn't
> >> a gain.  (200kohm) and the infinite element is annoying.  Why not
> >> compute the actual gains?
> >> Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
> >> a bit of fixed point maths in the driver but the advantage is you'll
> >> have real values that are standardizable across multiple devices
> >> and hence allow your device to be operated by generic userspace
> >> code.  Welcome to standardising interfaces - my favourite occupation ;)
> >>
> >>>> Secondly see in_illuminance0_scale for a suitable existing attribute.
> >>> I didn't consider scale to be appropriate given the following
> >>> documentation (e.g, for in_voltageY_scale):
> >> sorry  I just did this to someone else in another review (so I'm 
> >> consistently wrong!)
> >>
> >> in_voltageY_calibscale is what I should have said.  That one applies a
> >> scaling before the raw reading is generated (so in hardware).
> > 
> > Ok, then calibscale is the appropriate attribute for the resistor
> > setting. But as this is a device-specific hardware-calibration setting
> > I would suggest using the following interface:
> > 
> > What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance_calibscale
> > Description:
> > 		Set the ALS calibration scale (internal resistors) for
> > 		analog input mode, where the scale factor is the current in uA
> > 		at 2V full-scale (10..1270, 10uA step), that is,
> > 
> > 		R_als = 2V / in_illuminance_calibscale
> > 
> > 		This setting is ignored in PWM mode.
> This is a generic element that really ought to just fit in with the
> equivalent in sysfs-bus-iio for calibscan.  It's a ratio, so it should
> be unit free for starters.

I'm starting to doubt that calibscale is really appropriate in this case.

For starters, the description in sysfs-bus-iio doesn't really apply:

	"Hardware applied calibration scale factor. (assumed to fix
	 production inaccuracies)."

The resistor setting of the lm3533 is about fitting an external analog
light sensor to the lm3533 als interface (which is basically just an adc
with some extra logic), that is, it is used to match the output current
of the chosen sensor so that the ADC measures 2V at full LUX.

It's not a setting to calibrate "inaccuracies", but rather an
integration parameter that is set once when the characteristics of the
light sensor is known. (Sure, it could be used later to increase
sensitivity as well, but the main purpose is to fit a new light sensor
to a generic input interface.)

> > [...]
> > 
> >>>>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
> >>>>> +Date:		April 2012
> >>>>> +KernelVersion:	3.5
> >>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>> +Description:
> >>>>> +		Set the target brightness for ALS-mapper m in light zone n
> >>>>> +		(0..255), where m in 1..3 and n in 0..4.
> >>>> Don't suppose you could do a quick summary of what these zones are
> >>>> and why there are 3 ALS-mappers?  I'm not getting terribly far on a
> >>>> quick look at the datasheet!
> >>> Of course. The average adc readings are mapped to five light zones using
> >>> eight zone boundary registers (4 boundaries with hysteresis) and a set
> >>> of rules.
> >> This is going to be fun.  We'll need the boundaries and attached 
> >> hysteresis attributes to fully specify these (nothing would indicate
> >> that hysterisis is involved otherwise).
> > 
> > You can't define the hysteresis explicitly with the lm3533 register
> > interface, rather it's is defined implicitly in case threshY_falling is
> > less than threshY_rasising.
> > 
> > So the raising/falling attributes should be enough, right?
> Nope, because they don't tell a general userspace application what is
> going on.  Without hysterisis attributes it has no way of knowing there
> is hysterisis present.

Well an application could simply look at the difference between raising
and falling to determine the hysteresis?

It gets more complicated as the lm3533 allow the raising threshold to
be lower than the falling. It appears the device is using whichever
register is lower for the falling threshold. I guess I should compensate
for this in the driver.

Furthermore, you can define threshold 1 to be lower than threshold 0,
effectively preventing zone 1 to be reached. In this case, dropping
below thres1_falling gives zone 0, and raising above thres1_raising gives
zone 2. In particular, no threshold event is generated when
thres0_{falling/raising} is passed in either direction. But perhaps this
should just be documented as a feature/quirk of the device.

> Feel free to make them read only though.

So you're suggesting something like:

	events/in_illuminance0_threshY_falling_value
	events/in_illuminance0_threshY_raising_value
	events/in_illuminance0_threshY_hysteresis

where hysteresis is a read-only attribute whose value is
	
	threshY_raising_value - threshY_falling_value

> >>> To simplify somewhat (by ignoring some of the rules): If the average
> >>> adc input drops below boundary0_low, the zone register reads 0; if it
> >>> drops below boundary1_low, it reads 1, and so on. If the input it
> >>> increases over boundary3_high, the zone register return 4; if it
> >>> increases passed boundary2_high, it returns zone 3, etc.
> >>>
> >>> That is, roughly something like (we get 8-bits of input from the ADC):
> >>>
> >>> 	zone 0
> >>>
> >>> boundary0_low	51
> >>> boundary0_high	53
> >>>
> >>> 	zone 1
> >>>
> >>> boundary1_low	102
> >>> boundary1_high	106
> >>>
> >>> 	zone 2
> >>>
> >>> boundary2_low	153
> >>> boundary2_high	161
> >>>
> >>> 	zone 3
> >>>
> >>> boundary3_low	204
> >>> boundary3_high	220
> >>>
> >>> 	zone 4
> >>>
> >>> [ Figure 6 on page 20 in the datasheets should make it clear. ]
> >>>
> >>> The ALS interface and it's zone concept can then be used to control the
> >>> LEDs and backlights of the chip, by determining the target brightness for
> >>> each zone, e.g., set brightness to 52 when in zone 0.
> >>>
> >>> To complicate things further (and it is complicated), there are three
> >>> such sets of target brightness values: ALSM1, ALSM2, ALSM3.
> >>>
> >>> So for each LED or backlight you can set ALS-input control mode, by
> >>> saying that the device should get it's brightness levels from target set
> >>> 1, 2, or 3.
> >>>
> >>> [ And it gets even more complicated, as ALSM1 can only control
> >>>    backlight0, where as ALSM2 and ALSM3 can control any of the remaining
> >>>    devices, but that's irrelevant here. ]
> >>>
> >>> Initially, I thought this interface to be too esoteric to be worth
> >>> generalising, but it sort of fits with event thresholds so I gave it a
> >>> try.
> >> Glad you did and it pretty much fits, be it with a few extensions being 
> >> necessary.
> >>> The biggest conceptual problem, I think, is that the zone
> >>> boundaries can be used to control the other devices, even when the event
> >>> is not enabled (or even an irq line not configured). That is, I find it
> >>> a bit awkward that the event thresholds also defines the zones (a sort of
> >>> discrete scaling factor).
> >> That is indeed awkward. I'm not sure how we handle this either.  If we 
> >> need to control these from the other devices (e.g. the back light
> >> driver) then we'll have to get them into chan_spec and use the
> >> inkernel interfaces to do it.  Not infeasible but I was hoping to
> >> avoid that until we have had a few months to see what similar devices
> >> show up (on basis nothing in this world is a one off for long ;)
> > 
> > I don't think the control bits can or should be generalised at this
> > point. The same ALS-target values may be used to control more than one
> > device, so they need to be set from the als rather from the controlled
> > device (otherwise, changing the target value of led1 could change that
> > of the other three leds without the user realising that this can be a
> > side effect).
> Good point.  Nasty little device to write an interface for :)

Indeed. Thanks for appreciating that. ;)
 
> >>> Perhaps simply keeping the attributes outside of events (e.g. named
> >>> boundary[n]_{low,high}) and having a custom event enabled (e.g.
> >>> in_illuminance_zone_change_en) is the best solution?
> >> Maybe, but it's ugly and as you have said, they do correspond pretty well to
> >> thresholds so I'd rather you went with that.
> >> The core stuff for registering events clearly needs a rethink.... For 
> >> now doing it as you describe above (with the addition fo hysteresis
> >> attributes) should be fine.  Just document the 'quirks'.
> > 
> > Ok, I'll keep the event/zone interface as it stands for now and we'll
> > see if it can be generalised later. [ See my comment on the hysteresis
> > above: there are only the rising/falling thresholds (low/high
> > boundaries) and no boundary or hysteresis settings. ]
> On that, just to reiterate, to have anything generalizable, userspace
> needs to know that hysterisis exists on the individual thresholds
> (though it is clearly a function of the neighbouring one).

See my comments above.

Thanks,
Johan

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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
@ 2012-05-16 13:05                 ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-16 13:05 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Johan Hovold, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev

On Tue, May 15, 2012 at 09:00:46PM +0100, Jonathan Cameron wrote:
> On 05/15/2012 05:44 PM, Johan Hovold wrote:
> > On Tue, May 08, 2012 at 02:47:19PM +0100, Jonathan Cameron wrote:
> >> On 5/3/2012 5:36 PM, Johan Hovold wrote:
> >>> On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
> >>>> On 5/3/2012 11:26 AM, Johan Hovold wrote:
> >>>>> Add sub-driver for the ambient light sensor interface on National
> >>>>> Semiconductor / TI LM3533 lighting power chips.
> >>>>>
> >>>>> The sensor interface can be used to control the LEDs and backlights of
> >>>>> the chip through defining five light zones and three sets of
> >>>>> corresponding brightness target levels.
> >>>>>
> >>>>> The driver provides raw and mean adc readings along with the current
> >>>>> light zone through sysfs. A threshold event can be generated on zone
> >>>>> changes.
> >>>> Code is fine.  Pretty much all my comments are to do with the interface.
> >>> [...]
> >>>
> >>>>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> >>>>> new file mode 100644
> >>>>> index 0000000..9849d14
> >>>>> --- /dev/null
> >>>>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> >>>>> @@ -0,0 +1,62 @@
> >>>>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
> >>>>> +Date:		April 2012
> >>>>> +KernelVersion:	3.5
> >>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>> +Description:
> >>>>> +		Set the ALS gain-resistor setting (0..127) for analog input
> >>>>> +		mode, where
> >>>>> +
> >>>>> +		0000000 - ALS input is high impedance
> >>>>> +		0000001 - 200kOhm (10uA at 2V full-scale)
> >>>>> +		0000010 - 100kOhm (20uA at 2V full-scale)
> >>>>> +		...
> >>>>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
> >>>>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
> >>>>> +
> >>>>> +		R_als = 2V / (10uA * gain)	(gain>   0)
> >>>> Firstly, no magic numbers.  These are definitely magic.
> >>> Not that magic as they're clearly documented (in code and public
> >>> datasheets), right? What would you prefer instead?
> >> The numbers on the right of the - look good to me though then this isn't
> >> a gain.  (200kohm) and the infinite element is annoying.  Why not
> >> compute the actual gains?
> >> Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
> >> a bit of fixed point maths in the driver but the advantage is you'll
> >> have real values that are standardizable across multiple devices
> >> and hence allow your device to be operated by generic userspace
> >> code.  Welcome to standardising interfaces - my favourite occupation ;)
> >>
> >>>> Secondly see in_illuminance0_scale for a suitable existing attribute.
> >>> I didn't consider scale to be appropriate given the following
> >>> documentation (e.g, for in_voltageY_scale):
> >> sorry  I just did this to someone else in another review (so I'm 
> >> consistently wrong!)
> >>
> >> in_voltageY_calibscale is what I should have said.  That one applies a
> >> scaling before the raw reading is generated (so in hardware).
> > 
> > Ok, then calibscale is the appropriate attribute for the resistor
> > setting. But as this is a device-specific hardware-calibration setting
> > I would suggest using the following interface:
> > 
> > What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance_calibscale
> > Description:
> > 		Set the ALS calibration scale (internal resistors) for
> > 		analog input mode, where the scale factor is the current in uA
> > 		at 2V full-scale (10..1270, 10uA step), that is,
> > 
> > 		R_als = 2V / in_illuminance_calibscale
> > 
> > 		This setting is ignored in PWM mode.
> This is a generic element that really ought to just fit in with the
> equivalent in sysfs-bus-iio for calibscan.  It's a ratio, so it should
> be unit free for starters.

I'm starting to doubt that calibscale is really appropriate in this case.

For starters, the description in sysfs-bus-iio doesn't really apply:

	"Hardware applied calibration scale factor. (assumed to fix
	 production inaccuracies)."

The resistor setting of the lm3533 is about fitting an external analog
light sensor to the lm3533 als interface (which is basically just an adc
with some extra logic), that is, it is used to match the output current
of the chosen sensor so that the ADC measures 2V at full LUX.

It's not a setting to calibrate "inaccuracies", but rather an
integration parameter that is set once when the characteristics of the
light sensor is known. (Sure, it could be used later to increase
sensitivity as well, but the main purpose is to fit a new light sensor
to a generic input interface.)

> > [...]
> > 
> >>>>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
> >>>>> +Date:		April 2012
> >>>>> +KernelVersion:	3.5
> >>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>> +Description:
> >>>>> +		Set the target brightness for ALS-mapper m in light zone n
> >>>>> +		(0..255), where m in 1..3 and n in 0..4.
> >>>> Don't suppose you could do a quick summary of what these zones are
> >>>> and why there are 3 ALS-mappers?  I'm not getting terribly far on a
> >>>> quick look at the datasheet!
> >>> Of course. The average adc readings are mapped to five light zones using
> >>> eight zone boundary registers (4 boundaries with hysteresis) and a set
> >>> of rules.
> >> This is going to be fun.  We'll need the boundaries and attached 
> >> hysteresis attributes to fully specify these (nothing would indicate
> >> that hysterisis is involved otherwise).
> > 
> > You can't define the hysteresis explicitly with the lm3533 register
> > interface, rather it's is defined implicitly in case threshY_falling is
> > less than threshY_rasising.
> > 
> > So the raising/falling attributes should be enough, right?
> Nope, because they don't tell a general userspace application what is
> going on.  Without hysterisis attributes it has no way of knowing there
> is hysterisis present.

Well an application could simply look at the difference between raising
and falling to determine the hysteresis?

It gets more complicated as the lm3533 allow the raising threshold to
be lower than the falling. It appears the device is using whichever
register is lower for the falling threshold. I guess I should compensate
for this in the driver.

Furthermore, you can define threshold 1 to be lower than threshold 0,
effectively preventing zone 1 to be reached. In this case, dropping
below thres1_falling gives zone 0, and raising above thres1_raising gives
zone 2. In particular, no threshold event is generated when
thres0_{falling/raising} is passed in either direction. But perhaps this
should just be documented as a feature/quirk of the device.

> Feel free to make them read only though.

So you're suggesting something like:

	events/in_illuminance0_threshY_falling_value
	events/in_illuminance0_threshY_raising_value
	events/in_illuminance0_threshY_hysteresis

where hysteresis is a read-only attribute whose value is
	
	threshY_raising_value - threshY_falling_value

> >>> To simplify somewhat (by ignoring some of the rules): If the average
> >>> adc input drops below boundary0_low, the zone register reads 0; if it
> >>> drops below boundary1_low, it reads 1, and so on. If the input it
> >>> increases over boundary3_high, the zone register return 4; if it
> >>> increases passed boundary2_high, it returns zone 3, etc.
> >>>
> >>> That is, roughly something like (we get 8-bits of input from the ADC):
> >>>
> >>> 	zone 0
> >>>
> >>> boundary0_low	51
> >>> boundary0_high	53
> >>>
> >>> 	zone 1
> >>>
> >>> boundary1_low	102
> >>> boundary1_high	106
> >>>
> >>> 	zone 2
> >>>
> >>> boundary2_low	153
> >>> boundary2_high	161
> >>>
> >>> 	zone 3
> >>>
> >>> boundary3_low	204
> >>> boundary3_high	220
> >>>
> >>> 	zone 4
> >>>
> >>> [ Figure 6 on page 20 in the datasheets should make it clear. ]
> >>>
> >>> The ALS interface and it's zone concept can then be used to control the
> >>> LEDs and backlights of the chip, by determining the target brightness for
> >>> each zone, e.g., set brightness to 52 when in zone 0.
> >>>
> >>> To complicate things further (and it is complicated), there are three
> >>> such sets of target brightness values: ALSM1, ALSM2, ALSM3.
> >>>
> >>> So for each LED or backlight you can set ALS-input control mode, by
> >>> saying that the device should get it's brightness levels from target set
> >>> 1, 2, or 3.
> >>>
> >>> [ And it gets even more complicated, as ALSM1 can only control
> >>>    backlight0, where as ALSM2 and ALSM3 can control any of the remaining
> >>>    devices, but that's irrelevant here. ]
> >>>
> >>> Initially, I thought this interface to be too esoteric to be worth
> >>> generalising, but it sort of fits with event thresholds so I gave it a
> >>> try.
> >> Glad you did and it pretty much fits, be it with a few extensions being 
> >> necessary.
> >>> The biggest conceptual problem, I think, is that the zone
> >>> boundaries can be used to control the other devices, even when the event
> >>> is not enabled (or even an irq line not configured). That is, I find it
> >>> a bit awkward that the event thresholds also defines the zones (a sort of
> >>> discrete scaling factor).
> >> That is indeed awkward. I'm not sure how we handle this either.  If we 
> >> need to control these from the other devices (e.g. the back light
> >> driver) then we'll have to get them into chan_spec and use the
> >> inkernel interfaces to do it.  Not infeasible but I was hoping to
> >> avoid that until we have had a few months to see what similar devices
> >> show up (on basis nothing in this world is a one off for long ;)
> > 
> > I don't think the control bits can or should be generalised at this
> > point. The same ALS-target values may be used to control more than one
> > device, so they need to be set from the als rather from the controlled
> > device (otherwise, changing the target value of led1 could change that
> > of the other three leds without the user realising that this can be a
> > side effect).
> Good point.  Nasty little device to write an interface for :)

Indeed. Thanks for appreciating that. ;)
 
> >>> Perhaps simply keeping the attributes outside of events (e.g. named
> >>> boundary[n]_{low,high}) and having a custom event enabled (e.g.
> >>> in_illuminance_zone_change_en) is the best solution?
> >> Maybe, but it's ugly and as you have said, they do correspond pretty well to
> >> thresholds so I'd rather you went with that.
> >> The core stuff for registering events clearly needs a rethink.... For 
> >> now doing it as you describe above (with the addition fo hysteresis
> >> attributes) should be fine.  Just document the 'quirks'.
> > 
> > Ok, I'll keep the event/zone interface as it stands for now and we'll
> > see if it can be generalised later. [ See my comment on the hysteresis
> > above: there are only the rising/falling thresholds (low/high
> > boundaries) and no boundary or hysteresis settings. ]
> On that, just to reiterate, to have anything generalizable, userspace
> needs to know that hysterisis exists on the individual thresholds
> (though it is clearly a function of the neighbouring one).

See my comments above.

Thanks,
Johan

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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
  2012-05-16 13:05                 ` Johan Hovold
@ 2012-05-16 14:21                   ` Jonathan Cameron
  -1 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-16 14:21 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

On 5/16/2012 2:05 PM, Johan Hovold wrote:
> On Tue, May 15, 2012 at 09:00:46PM +0100, Jonathan Cameron wrote:
>> On 05/15/2012 05:44 PM, Johan Hovold wrote:
>>> On Tue, May 08, 2012 at 02:47:19PM +0100, Jonathan Cameron wrote:
>>>> On 5/3/2012 5:36 PM, Johan Hovold wrote:
>>>>> On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
>>>>>> On 5/3/2012 11:26 AM, Johan Hovold wrote:
>>>>>>> Add sub-driver for the ambient light sensor interface on National
>>>>>>> Semiconductor / TI LM3533 lighting power chips.
>>>>>>>
>>>>>>> The sensor interface can be used to control the LEDs and backlights of
>>>>>>> the chip through defining five light zones and three sets of
>>>>>>> corresponding brightness target levels.
>>>>>>>
>>>>>>> The driver provides raw and mean adc readings along with the current
>>>>>>> light zone through sysfs. A threshold event can be generated on zone
>>>>>>> changes.
>>>>>> Code is fine.  Pretty much all my comments are to do with the interface.
>>>>> [...]
>>>>>
>>>>>>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>>>>>> new file mode 100644
>>>>>>> index 0000000..9849d14
>>>>>>> --- /dev/null
>>>>>>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>>>>>> @@ -0,0 +1,62 @@
>>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
>>>>>>> +Date:		April 2012
>>>>>>> +KernelVersion:	3.5
>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>> +Description:
>>>>>>> +		Set the ALS gain-resistor setting (0..127) for analog input
>>>>>>> +		mode, where
>>>>>>> +
>>>>>>> +		0000000 - ALS input is high impedance
>>>>>>> +		0000001 - 200kOhm (10uA at 2V full-scale)
>>>>>>> +		0000010 - 100kOhm (20uA at 2V full-scale)
>>>>>>> +		...
>>>>>>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
>>>>>>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
>>>>>>> +
>>>>>>> +		R_als = 2V / (10uA * gain)	(gain>    0)
>>>>>> Firstly, no magic numbers.  These are definitely magic.
>>>>> Not that magic as they're clearly documented (in code and public
>>>>> datasheets), right? What would you prefer instead?
>>>> The numbers on the right of the - look good to me though then this isn't
>>>> a gain.  (200kohm) and the infinite element is annoying.  Why not
>>>> compute the actual gains?
>>>> Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
>>>> a bit of fixed point maths in the driver but the advantage is you'll
>>>> have real values that are standardizable across multiple devices
>>>> and hence allow your device to be operated by generic userspace
>>>> code.  Welcome to standardising interfaces - my favourite occupation ;)
>>>>
>>>>>> Secondly see in_illuminance0_scale for a suitable existing attribute.
>>>>> I didn't consider scale to be appropriate given the following
>>>>> documentation (e.g, for in_voltageY_scale):
>>>> sorry  I just did this to someone else in another review (so I'm
>>>> consistently wrong!)
>>>>
>>>> in_voltageY_calibscale is what I should have said.  That one applies a
>>>> scaling before the raw reading is generated (so in hardware).
>>>
>>> Ok, then calibscale is the appropriate attribute for the resistor
>>> setting. But as this is a device-specific hardware-calibration setting
>>> I would suggest using the following interface:
>>>
>>> What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance_calibscale
>>> Description:
>>> 		Set the ALS calibration scale (internal resistors) for
>>> 		analog input mode, where the scale factor is the current in uA
>>> 		at 2V full-scale (10..1270, 10uA step), that is,
>>>
>>> 		R_als = 2V / in_illuminance_calibscale
>>>
>>> 		This setting is ignored in PWM mode.
>> This is a generic element that really ought to just fit in with the
>> equivalent in sysfs-bus-iio for calibscan.  It's a ratio, so it should
>> be unit free for starters.
>
> I'm starting to doubt that calibscale is really appropriate in this case.
>
> For starters, the description in sysfs-bus-iio doesn't really apply:
>
> 	"Hardware applied calibration scale factor. (assumed to fix
> 	 production inaccuracies)."
Hmm.. if you really don't like this, Michael Hennerich had a case
where this made even less sense, so we now have hardwaregain.
Use that if you like...
>
> The resistor setting of the lm3533 is about fitting an external analog
> light sensor to the lm3533 als interface (which is basically just an adc
> with some extra logic), that is, it is used to match the output current
> of the chosen sensor so that the ADC measures 2V at full LUX.
>
> It's not a setting to calibrate "inaccuracies", but rather an
> integration parameter that is set once when the characteristics of the
> light sensor is known. (Sure, it could be used later to increase
> sensitivity as well, but the main purpose is to fit a new light sensor
> to a generic input interface.)
>
>>> [...]
>>>
>>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
>>>>>>> +Date:		April 2012
>>>>>>> +KernelVersion:	3.5
>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>> +Description:
>>>>>>> +		Set the target brightness for ALS-mapper m in light zone n
>>>>>>> +		(0..255), where m in 1..3 and n in 0..4.
>>>>>> Don't suppose you could do a quick summary of what these zones are
>>>>>> and why there are 3 ALS-mappers?  I'm not getting terribly far on a
>>>>>> quick look at the datasheet!
>>>>> Of course. The average adc readings are mapped to five light zones using
>>>>> eight zone boundary registers (4 boundaries with hysteresis) and a set
>>>>> of rules.
>>>> This is going to be fun.  We'll need the boundaries and attached
>>>> hysteresis attributes to fully specify these (nothing would indicate
>>>> that hysterisis is involved otherwise).
>>>
>>> You can't define the hysteresis explicitly with the lm3533 register
>>> interface, rather it's is defined implicitly in case threshY_falling is
>>> less than threshY_rasising.
>>>
>>> So the raising/falling attributes should be enough, right?
>> Nope, because they don't tell a general userspace application what is
>> going on.  Without hysterisis attributes it has no way of knowing there
>> is hysterisis present.
>
> Well an application could simply look at the difference between raising
> and falling to determine the hysteresis?
Only if it knows it has your sensor.  For other sensors it could be 
completely separate or not present.  If the parameter is missing 
assumption is that there is no hysterisis.
>
> It gets more complicated as the lm3533 allow the raising threshold to
> be lower than the falling. It appears the device is using whichever
> register is lower for the falling threshold. I guess I should compensate
> for this in the driver.
That's nasty.
>
> Furthermore, you can define threshold 1 to be lower than threshold 0,
> effectively preventing zone 1 to be reached. In this case, dropping
> below thres1_falling gives zone 0, and raising above thres1_raising gives
> zone 2. In particular, no threshold event is generated when
> thres0_{falling/raising} is passed in either direction. But perhaps this
> should just be documented as a feature/quirk of the device.
Seems sensible...
>
>> Feel free to make them read only though.
>
> So you're suggesting something like:
>
> 	events/in_illuminance0_threshY_falling_value
> 	events/in_illuminance0_threshY_raising_value
> 	events/in_illuminance0_threshY_hysteresis
>
> where hysteresis is a read-only attribute whose value is
> 	
> 	threshY_raising_value - threshY_falling_value
yes.  Annoying it may be but it matches existing interface.
>
>>>>> To simplify somewhat (by ignoring some of the rules): If the average
>>>>> adc input drops below boundary0_low, the zone register reads 0; if it
>>>>> drops below boundary1_low, it reads 1, and so on. If the input it
>>>>> increases over boundary3_high, the zone register return 4; if it
>>>>> increases passed boundary2_high, it returns zone 3, etc.
>>>>>
>>>>> That is, roughly something like (we get 8-bits of input from the ADC):
>>>>>
>>>>> 	zone 0
>>>>>
>>>>> boundary0_low	51
>>>>> boundary0_high	53
>>>>>
>>>>> 	zone 1
>>>>>
>>>>> boundary1_low	102
>>>>> boundary1_high	106
>>>>>
>>>>> 	zone 2
>>>>>
>>>>> boundary2_low	153
>>>>> boundary2_high	161
>>>>>
>>>>> 	zone 3
>>>>>
>>>>> boundary3_low	204
>>>>> boundary3_high	220
>>>>>
>>>>> 	zone 4
>>>>>
>>>>> [ Figure 6 on page 20 in the datasheets should make it clear. ]
>>>>>
>>>>> The ALS interface and it's zone concept can then be used to control the
>>>>> LEDs and backlights of the chip, by determining the target brightness for
>>>>> each zone, e.g., set brightness to 52 when in zone 0.
>>>>>
>>>>> To complicate things further (and it is complicated), there are three
>>>>> such sets of target brightness values: ALSM1, ALSM2, ALSM3.
>>>>>
>>>>> So for each LED or backlight you can set ALS-input control mode, by
>>>>> saying that the device should get it's brightness levels from target set
>>>>> 1, 2, or 3.
>>>>>
>>>>> [ And it gets even more complicated, as ALSM1 can only control
>>>>>     backlight0, where as ALSM2 and ALSM3 can control any of the remaining
>>>>>     devices, but that's irrelevant here. ]
>>>>>
>>>>> Initially, I thought this interface to be too esoteric to be worth
>>>>> generalising, but it sort of fits with event thresholds so I gave it a
>>>>> try.
>>>> Glad you did and it pretty much fits, be it with a few extensions being
>>>> necessary.
>>>>> The biggest conceptual problem, I think, is that the zone
>>>>> boundaries can be used to control the other devices, even when the event
>>>>> is not enabled (or even an irq line not configured). That is, I find it
>>>>> a bit awkward that the event thresholds also defines the zones (a sort of
>>>>> discrete scaling factor).
>>>> That is indeed awkward. I'm not sure how we handle this either.  If we
>>>> need to control these from the other devices (e.g. the back light
>>>> driver) then we'll have to get them into chan_spec and use the
>>>> inkernel interfaces to do it.  Not infeasible but I was hoping to
>>>> avoid that until we have had a few months to see what similar devices
>>>> show up (on basis nothing in this world is a one off for long ;)
>>>
>>> I don't think the control bits can or should be generalised at this
>>> point. The same ALS-target values may be used to control more than one
>>> device, so they need to be set from the als rather from the controlled
>>> device (otherwise, changing the target value of led1 could change that
>>> of the other three leds without the user realising that this can be a
>>> side effect).
>> Good point.  Nasty little device to write an interface for :)
>
> Indeed. Thanks for appreciating that. ;)
>
>>>>> Perhaps simply keeping the attributes outside of events (e.g. named
>>>>> boundary[n]_{low,high}) and having a custom event enabled (e.g.
>>>>> in_illuminance_zone_change_en) is the best solution?
>>>> Maybe, but it's ugly and as you have said, they do correspond pretty well to
>>>> thresholds so I'd rather you went with that.
>>>> The core stuff for registering events clearly needs a rethink.... For
>>>> now doing it as you describe above (with the addition fo hysteresis
>>>> attributes) should be fine.  Just document the 'quirks'.
>>>
>>> Ok, I'll keep the event/zone interface as it stands for now and we'll
>>> see if it can be generalised later. [ See my comment on the hysteresis
>>> above: there are only the rising/falling thresholds (low/high
>>> boundaries) and no boundary or hysteresis settings. ]
>> On that, just to reiterate, to have anything generalizable, userspace
>> needs to know that hysterisis exists on the individual thresholds
>> (though it is clearly a function of the neighbouring one).
>
> See my comments above.
>
> Thanks,
> Johan


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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
@ 2012-05-16 14:21                   ` Jonathan Cameron
  0 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-16 14:21 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

On 5/16/2012 2:05 PM, Johan Hovold wrote:
> On Tue, May 15, 2012 at 09:00:46PM +0100, Jonathan Cameron wrote:
>> On 05/15/2012 05:44 PM, Johan Hovold wrote:
>>> On Tue, May 08, 2012 at 02:47:19PM +0100, Jonathan Cameron wrote:
>>>> On 5/3/2012 5:36 PM, Johan Hovold wrote:
>>>>> On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
>>>>>> On 5/3/2012 11:26 AM, Johan Hovold wrote:
>>>>>>> Add sub-driver for the ambient light sensor interface on National
>>>>>>> Semiconductor / TI LM3533 lighting power chips.
>>>>>>>
>>>>>>> The sensor interface can be used to control the LEDs and backlights of
>>>>>>> the chip through defining five light zones and three sets of
>>>>>>> corresponding brightness target levels.
>>>>>>>
>>>>>>> The driver provides raw and mean adc readings along with the current
>>>>>>> light zone through sysfs. A threshold event can be generated on zone
>>>>>>> changes.
>>>>>> Code is fine.  Pretty much all my comments are to do with the interface.
>>>>> [...]
>>>>>
>>>>>>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>>>>>> new file mode 100644
>>>>>>> index 0000000..9849d14
>>>>>>> --- /dev/null
>>>>>>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>>>>>> @@ -0,0 +1,62 @@
>>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
>>>>>>> +Date:		April 2012
>>>>>>> +KernelVersion:	3.5
>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>> +Description:
>>>>>>> +		Set the ALS gain-resistor setting (0..127) for analog input
>>>>>>> +		mode, where
>>>>>>> +
>>>>>>> +		0000000 - ALS input is high impedance
>>>>>>> +		0000001 - 200kOhm (10uA at 2V full-scale)
>>>>>>> +		0000010 - 100kOhm (20uA at 2V full-scale)
>>>>>>> +		...
>>>>>>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
>>>>>>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
>>>>>>> +
>>>>>>> +		R_als = 2V / (10uA * gain)	(gain>    0)
>>>>>> Firstly, no magic numbers.  These are definitely magic.
>>>>> Not that magic as they're clearly documented (in code and public
>>>>> datasheets), right? What would you prefer instead?
>>>> The numbers on the right of the - look good to me though then this isn't
>>>> a gain.  (200kohm) and the infinite element is annoying.  Why not
>>>> compute the actual gains?
>>>> Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
>>>> a bit of fixed point maths in the driver but the advantage is you'll
>>>> have real values that are standardizable across multiple devices
>>>> and hence allow your device to be operated by generic userspace
>>>> code.  Welcome to standardising interfaces - my favourite occupation ;)
>>>>
>>>>>> Secondly see in_illuminance0_scale for a suitable existing attribute.
>>>>> I didn't consider scale to be appropriate given the following
>>>>> documentation (e.g, for in_voltageY_scale):
>>>> sorry  I just did this to someone else in another review (so I'm
>>>> consistently wrong!)
>>>>
>>>> in_voltageY_calibscale is what I should have said.  That one applies a
>>>> scaling before the raw reading is generated (so in hardware).
>>>
>>> Ok, then calibscale is the appropriate attribute for the resistor
>>> setting. But as this is a device-specific hardware-calibration setting
>>> I would suggest using the following interface:
>>>
>>> What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance_calibscale
>>> Description:
>>> 		Set the ALS calibration scale (internal resistors) for
>>> 		analog input mode, where the scale factor is the current in uA
>>> 		at 2V full-scale (10..1270, 10uA step), that is,
>>>
>>> 		R_als = 2V / in_illuminance_calibscale
>>>
>>> 		This setting is ignored in PWM mode.
>> This is a generic element that really ought to just fit in with the
>> equivalent in sysfs-bus-iio for calibscan.  It's a ratio, so it should
>> be unit free for starters.
>
> I'm starting to doubt that calibscale is really appropriate in this case.
>
> For starters, the description in sysfs-bus-iio doesn't really apply:
>
> 	"Hardware applied calibration scale factor. (assumed to fix
> 	 production inaccuracies)."
Hmm.. if you really don't like this, Michael Hennerich had a case
where this made even less sense, so we now have hardwaregain.
Use that if you like...
>
> The resistor setting of the lm3533 is about fitting an external analog
> light sensor to the lm3533 als interface (which is basically just an adc
> with some extra logic), that is, it is used to match the output current
> of the chosen sensor so that the ADC measures 2V at full LUX.
>
> It's not a setting to calibrate "inaccuracies", but rather an
> integration parameter that is set once when the characteristics of the
> light sensor is known. (Sure, it could be used later to increase
> sensitivity as well, but the main purpose is to fit a new light sensor
> to a generic input interface.)
>
>>> [...]
>>>
>>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
>>>>>>> +Date:		April 2012
>>>>>>> +KernelVersion:	3.5
>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>> +Description:
>>>>>>> +		Set the target brightness for ALS-mapper m in light zone n
>>>>>>> +		(0..255), where m in 1..3 and n in 0..4.
>>>>>> Don't suppose you could do a quick summary of what these zones are
>>>>>> and why there are 3 ALS-mappers?  I'm not getting terribly far on a
>>>>>> quick look at the datasheet!
>>>>> Of course. The average adc readings are mapped to five light zones using
>>>>> eight zone boundary registers (4 boundaries with hysteresis) and a set
>>>>> of rules.
>>>> This is going to be fun.  We'll need the boundaries and attached
>>>> hysteresis attributes to fully specify these (nothing would indicate
>>>> that hysterisis is involved otherwise).
>>>
>>> You can't define the hysteresis explicitly with the lm3533 register
>>> interface, rather it's is defined implicitly in case threshY_falling is
>>> less than threshY_rasising.
>>>
>>> So the raising/falling attributes should be enough, right?
>> Nope, because they don't tell a general userspace application what is
>> going on.  Without hysterisis attributes it has no way of knowing there
>> is hysterisis present.
>
> Well an application could simply look at the difference between raising
> and falling to determine the hysteresis?
Only if it knows it has your sensor.  For other sensors it could be 
completely separate or not present.  If the parameter is missing 
assumption is that there is no hysterisis.
>
> It gets more complicated as the lm3533 allow the raising threshold to
> be lower than the falling. It appears the device is using whichever
> register is lower for the falling threshold. I guess I should compensate
> for this in the driver.
That's nasty.
>
> Furthermore, you can define threshold 1 to be lower than threshold 0,
> effectively preventing zone 1 to be reached. In this case, dropping
> below thres1_falling gives zone 0, and raising above thres1_raising gives
> zone 2. In particular, no threshold event is generated when
> thres0_{falling/raising} is passed in either direction. But perhaps this
> should just be documented as a feature/quirk of the device.
Seems sensible...
>
>> Feel free to make them read only though.
>
> So you're suggesting something like:
>
> 	events/in_illuminance0_threshY_falling_value
> 	events/in_illuminance0_threshY_raising_value
> 	events/in_illuminance0_threshY_hysteresis
>
> where hysteresis is a read-only attribute whose value is
> 	
> 	threshY_raising_value - threshY_falling_value
yes.  Annoying it may be but it matches existing interface.
>
>>>>> To simplify somewhat (by ignoring some of the rules): If the average
>>>>> adc input drops below boundary0_low, the zone register reads 0; if it
>>>>> drops below boundary1_low, it reads 1, and so on. If the input it
>>>>> increases over boundary3_high, the zone register return 4; if it
>>>>> increases passed boundary2_high, it returns zone 3, etc.
>>>>>
>>>>> That is, roughly something like (we get 8-bits of input from the ADC):
>>>>>
>>>>> 	zone 0
>>>>>
>>>>> boundary0_low	51
>>>>> boundary0_high	53
>>>>>
>>>>> 	zone 1
>>>>>
>>>>> boundary1_low	102
>>>>> boundary1_high	106
>>>>>
>>>>> 	zone 2
>>>>>
>>>>> boundary2_low	153
>>>>> boundary2_high	161
>>>>>
>>>>> 	zone 3
>>>>>
>>>>> boundary3_low	204
>>>>> boundary3_high	220
>>>>>
>>>>> 	zone 4
>>>>>
>>>>> [ Figure 6 on page 20 in the datasheets should make it clear. ]
>>>>>
>>>>> The ALS interface and it's zone concept can then be used to control the
>>>>> LEDs and backlights of the chip, by determining the target brightness for
>>>>> each zone, e.g., set brightness to 52 when in zone 0.
>>>>>
>>>>> To complicate things further (and it is complicated), there are three
>>>>> such sets of target brightness values: ALSM1, ALSM2, ALSM3.
>>>>>
>>>>> So for each LED or backlight you can set ALS-input control mode, by
>>>>> saying that the device should get it's brightness levels from target set
>>>>> 1, 2, or 3.
>>>>>
>>>>> [ And it gets even more complicated, as ALSM1 can only control
>>>>>     backlight0, where as ALSM2 and ALSM3 can control any of the remaining
>>>>>     devices, but that's irrelevant here. ]
>>>>>
>>>>> Initially, I thought this interface to be too esoteric to be worth
>>>>> generalising, but it sort of fits with event thresholds so I gave it a
>>>>> try.
>>>> Glad you did and it pretty much fits, be it with a few extensions being
>>>> necessary.
>>>>> The biggest conceptual problem, I think, is that the zone
>>>>> boundaries can be used to control the other devices, even when the event
>>>>> is not enabled (or even an irq line not configured). That is, I find it
>>>>> a bit awkward that the event thresholds also defines the zones (a sort of
>>>>> discrete scaling factor).
>>>> That is indeed awkward. I'm not sure how we handle this either.  If we
>>>> need to control these from the other devices (e.g. the back light
>>>> driver) then we'll have to get them into chan_spec and use the
>>>> inkernel interfaces to do it.  Not infeasible but I was hoping to
>>>> avoid that until we have had a few months to see what similar devices
>>>> show up (on basis nothing in this world is a one off for long ;)
>>>
>>> I don't think the control bits can or should be generalised at this
>>> point. The same ALS-target values may be used to control more than one
>>> device, so they need to be set from the als rather from the controlled
>>> device (otherwise, changing the target value of led1 could change that
>>> of the other three leds without the user realising that this can be a
>>> side effect).
>> Good point.  Nasty little device to write an interface for :)
>
> Indeed. Thanks for appreciating that. ;)
>
>>>>> Perhaps simply keeping the attributes outside of events (e.g. named
>>>>> boundary[n]_{low,high}) and having a custom event enabled (e.g.
>>>>> in_illuminance_zone_change_en) is the best solution?
>>>> Maybe, but it's ugly and as you have said, they do correspond pretty well to
>>>> thresholds so I'd rather you went with that.
>>>> The core stuff for registering events clearly needs a rethink.... For
>>>> now doing it as you describe above (with the addition fo hysteresis
>>>> attributes) should be fine.  Just document the 'quirks'.
>>>
>>> Ok, I'll keep the event/zone interface as it stands for now and we'll
>>> see if it can be generalised later. [ See my comment on the hysteresis
>>> above: there are only the rising/falling thresholds (low/high
>>> boundaries) and no boundary or hysteresis settings. ]
>> On that, just to reiterate, to have anything generalizable, userspace
>> needs to know that hysterisis exists on the individual thresholds
>> (though it is clearly a function of the neighbouring one).
>
> See my comments above.
>
> Thanks,
> Johan


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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
  2012-05-16 14:21                   ` Jonathan Cameron
@ 2012-05-18 12:27                     ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-18 12:27 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Johan Hovold, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev

On Wed, May 16, 2012 at 03:21:14PM +0100, Jonathan Cameron wrote:
> On 5/16/2012 2:05 PM, Johan Hovold wrote:
> > On Tue, May 15, 2012 at 09:00:46PM +0100, Jonathan Cameron wrote:
> >> On 05/15/2012 05:44 PM, Johan Hovold wrote:
> >>> On Tue, May 08, 2012 at 02:47:19PM +0100, Jonathan Cameron wrote:
> >>>> On 5/3/2012 5:36 PM, Johan Hovold wrote:
> >>>>> On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
> >>>>>> On 5/3/2012 11:26 AM, Johan Hovold wrote:
> >>>>>>> Add sub-driver for the ambient light sensor interface on National
> >>>>>>> Semiconductor / TI LM3533 lighting power chips.
> >>>>>>>
> >>>>>>> The sensor interface can be used to control the LEDs and backlights of
> >>>>>>> the chip through defining five light zones and three sets of
> >>>>>>> corresponding brightness target levels.
> >>>>>>>
> >>>>>>> The driver provides raw and mean adc readings along with the current
> >>>>>>> light zone through sysfs. A threshold event can be generated on zone
> >>>>>>> changes.
> >>>>>> Code is fine.  Pretty much all my comments are to do with the interface.
> >>>>> [...]
> >>>>>
> >>>>>>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> >>>>>>> new file mode 100644
> >>>>>>> index 0000000..9849d14
> >>>>>>> --- /dev/null
> >>>>>>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> >>>>>>> @@ -0,0 +1,62 @@
> >>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
> >>>>>>> +Date:		April 2012
> >>>>>>> +KernelVersion:	3.5
> >>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>>>> +Description:
> >>>>>>> +		Set the ALS gain-resistor setting (0..127) for analog input
> >>>>>>> +		mode, where
> >>>>>>> +
> >>>>>>> +		0000000 - ALS input is high impedance
> >>>>>>> +		0000001 - 200kOhm (10uA at 2V full-scale)
> >>>>>>> +		0000010 - 100kOhm (20uA at 2V full-scale)
> >>>>>>> +		...
> >>>>>>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
> >>>>>>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
> >>>>>>> +
> >>>>>>> +		R_als = 2V / (10uA * gain)	(gain>    0)
> >>>>>> Firstly, no magic numbers.  These are definitely magic.
> >>>>> Not that magic as they're clearly documented (in code and public
> >>>>> datasheets), right? What would you prefer instead?
> >>>> The numbers on the right of the - look good to me though then this isn't
> >>>> a gain.  (200kohm) and the infinite element is annoying.  Why not
> >>>> compute the actual gains?
> >>>> Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
> >>>> a bit of fixed point maths in the driver but the advantage is you'll
> >>>> have real values that are standardizable across multiple devices
> >>>> and hence allow your device to be operated by generic userspace
> >>>> code.  Welcome to standardising interfaces - my favourite occupation ;)
> >>>>
> >>>>>> Secondly see in_illuminance0_scale for a suitable existing attribute.
> >>>>> I didn't consider scale to be appropriate given the following
> >>>>> documentation (e.g, for in_voltageY_scale):
> >>>> sorry  I just did this to someone else in another review (so I'm
> >>>> consistently wrong!)
> >>>>
> >>>> in_voltageY_calibscale is what I should have said.  That one applies a
> >>>> scaling before the raw reading is generated (so in hardware).
> >>>
> >>> Ok, then calibscale is the appropriate attribute for the resistor
> >>> setting. But as this is a device-specific hardware-calibration setting
> >>> I would suggest using the following interface:
> >>>
> >>> What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance_calibscale
> >>> Description:
> >>> 		Set the ALS calibration scale (internal resistors) for
> >>> 		analog input mode, where the scale factor is the current in uA
> >>> 		at 2V full-scale (10..1270, 10uA step), that is,
> >>>
> >>> 		R_als = 2V / in_illuminance_calibscale
> >>>
> >>> 		This setting is ignored in PWM mode.
> >> This is a generic element that really ought to just fit in with the
> >> equivalent in sysfs-bus-iio for calibscan.  It's a ratio, so it should
> >> be unit free for starters.
> >
> > I'm starting to doubt that calibscale is really appropriate in this case.
> >
> > For starters, the description in sysfs-bus-iio doesn't really apply:
> >
> > 	"Hardware applied calibration scale factor. (assumed to fix
> > 	 production inaccuracies)."
> Hmm.. if you really don't like this, Michael Hennerich had a case
> where this made even less sense, so we now have hardwaregain.
> Use that if you like...

I really think that this should remain a device specific attribute as I
originally suggested. It's an integration parameter that needs to be set
precisely depending on the actual hardware setup (which analog light
sensor and other external components). 

The lm3533 also supports two types of light sensors: pwm- and analog-
output ones. The resistor select settings only applies when in analog
mode as the input is always high impedance otherwise. Thus a generic
attribute (such as calibscale or hardware gain) shouldn't be used as it
will have no effect whatsoever in PWM-mode.

I'm thus back at my original proposal, albeit with a different name (I
think a lot of this discussion could have been avoided had I not
misnamed the parameter "gain"): 

What:		/sys/bus/iio/devices/iio:deviceX/r_select
Description:
		Set the ALS internal pull-down resistor for analog input mode
		(1..127), such that,

		R_als = 200000 / r_select	(ohm)

		This setting is ignored in PWM-mode (input is always high
		impedance in PWM-mode).

I don't think much is gained from using ohm as the unit: it just adds
complexity and the selected resistor setting will likely not match the
input value anyway. It's better that the chip integrators have full
control over which resistor setting is actually used so that it matches
external components.


> > The resistor setting of the lm3533 is about fitting an external analog
> > light sensor to the lm3533 als interface (which is basically just an adc
> > with some extra logic), that is, it is used to match the output current
> > of the chosen sensor so that the ADC measures 2V at full LUX.
> >
> > It's not a setting to calibrate "inaccuracies", but rather an
> > integration parameter that is set once when the characteristics of the
> > light sensor is known. (Sure, it could be used later to increase
> > sensitivity as well, but the main purpose is to fit a new light sensor
> > to a generic input interface.)
> >
> >>> [...]
> >>>
> >>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
> >>>>>>> +Date:		April 2012
> >>>>>>> +KernelVersion:	3.5
> >>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>>>> +Description:
> >>>>>>> +		Set the target brightness for ALS-mapper m in light zone n
> >>>>>>> +		(0..255), where m in 1..3 and n in 0..4.
> >>>>>> Don't suppose you could do a quick summary of what these zones are
> >>>>>> and why there are 3 ALS-mappers?  I'm not getting terribly far on a
> >>>>>> quick look at the datasheet!
> >>>>> Of course. The average adc readings are mapped to five light zones using
> >>>>> eight zone boundary registers (4 boundaries with hysteresis) and a set
> >>>>> of rules.
> >>>> This is going to be fun.  We'll need the boundaries and attached
> >>>> hysteresis attributes to fully specify these (nothing would indicate
> >>>> that hysterisis is involved otherwise).
> >>>
> >>> You can't define the hysteresis explicitly with the lm3533 register
> >>> interface, rather it's is defined implicitly in case threshY_falling is
> >>> less than threshY_rasising.
> >>>
> >>> So the raising/falling attributes should be enough, right?
> >> Nope, because they don't tell a general userspace application what is
> >> going on.  Without hysterisis attributes it has no way of knowing there
> >> is hysterisis present.
> >
> > Well an application could simply look at the difference between raising
> > and falling to determine the hysteresis?
> Only if it knows it has your sensor.  For other sensors it could be 
> completely separate or not present.  If the parameter is missing 
> assumption is that there is no hysterisis.
> >
> > It gets more complicated as the lm3533 allow the raising threshold to
> > be lower than the falling. It appears the device is using whichever
> > register is lower for the falling threshold. I guess I should compensate
> > for this in the driver.
> That's nasty.
> >
> > Furthermore, you can define threshold 1 to be lower than threshold 0,
> > effectively preventing zone 1 to be reached. In this case, dropping
> > below thres1_falling gives zone 0, and raising above thres1_raising gives
> > zone 2. In particular, no threshold event is generated when
> > thres0_{falling/raising} is passed in either direction. But perhaps this
> > should just be documented as a feature/quirk of the device.
> Seems sensible...
> >
> >> Feel free to make them read only though.
> >
> > So you're suggesting something like:
> >
> > 	events/in_illuminance0_threshY_falling_value
> > 	events/in_illuminance0_threshY_raising_value
> > 	events/in_illuminance0_threshY_hysteresis
> >
> > where hysteresis is a read-only attribute whose value is
> > 	
> > 	threshY_raising_value - threshY_falling_value
> yes.  Annoying it may be but it matches existing interface.

I'm posting a v4 which includes the above proposal for resistor select.
I've also added the hysteresis attributes as requested and fixed the
device threshold quirkiness mentioned above (the device is using
whichever register value is smaller as the falling threshold).

Thanks,
Johan

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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
@ 2012-05-18 12:27                     ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-18 12:27 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Johan Hovold, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev

On Wed, May 16, 2012 at 03:21:14PM +0100, Jonathan Cameron wrote:
> On 5/16/2012 2:05 PM, Johan Hovold wrote:
> > On Tue, May 15, 2012 at 09:00:46PM +0100, Jonathan Cameron wrote:
> >> On 05/15/2012 05:44 PM, Johan Hovold wrote:
> >>> On Tue, May 08, 2012 at 02:47:19PM +0100, Jonathan Cameron wrote:
> >>>> On 5/3/2012 5:36 PM, Johan Hovold wrote:
> >>>>> On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
> >>>>>> On 5/3/2012 11:26 AM, Johan Hovold wrote:
> >>>>>>> Add sub-driver for the ambient light sensor interface on National
> >>>>>>> Semiconductor / TI LM3533 lighting power chips.
> >>>>>>>
> >>>>>>> The sensor interface can be used to control the LEDs and backlights of
> >>>>>>> the chip through defining five light zones and three sets of
> >>>>>>> corresponding brightness target levels.
> >>>>>>>
> >>>>>>> The driver provides raw and mean adc readings along with the current
> >>>>>>> light zone through sysfs. A threshold event can be generated on zone
> >>>>>>> changes.
> >>>>>> Code is fine.  Pretty much all my comments are to do with the interface.
> >>>>> [...]
> >>>>>
> >>>>>>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> >>>>>>> new file mode 100644
> >>>>>>> index 0000000..9849d14
> >>>>>>> --- /dev/null
> >>>>>>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
> >>>>>>> @@ -0,0 +1,62 @@
> >>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
> >>>>>>> +Date:		April 2012
> >>>>>>> +KernelVersion:	3.5
> >>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>>>> +Description:
> >>>>>>> +		Set the ALS gain-resistor setting (0..127) for analog input
> >>>>>>> +		mode, where
> >>>>>>> +
> >>>>>>> +		0000000 - ALS input is high impedance
> >>>>>>> +		0000001 - 200kOhm (10uA at 2V full-scale)
> >>>>>>> +		0000010 - 100kOhm (20uA at 2V full-scale)
> >>>>>>> +		...
> >>>>>>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
> >>>>>>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
> >>>>>>> +
> >>>>>>> +		R_als = 2V / (10uA * gain)	(gain>    0)
> >>>>>> Firstly, no magic numbers.  These are definitely magic.
> >>>>> Not that magic as they're clearly documented (in code and public
> >>>>> datasheets), right? What would you prefer instead?
> >>>> The numbers on the right of the - look good to me though then this isn't
> >>>> a gain.  (200kohm) and the infinite element is annoying.  Why not
> >>>> compute the actual gains?
> >>>> Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
> >>>> a bit of fixed point maths in the driver but the advantage is you'll
> >>>> have real values that are standardizable across multiple devices
> >>>> and hence allow your device to be operated by generic userspace
> >>>> code.  Welcome to standardising interfaces - my favourite occupation ;)
> >>>>
> >>>>>> Secondly see in_illuminance0_scale for a suitable existing attribute.
> >>>>> I didn't consider scale to be appropriate given the following
> >>>>> documentation (e.g, for in_voltageY_scale):
> >>>> sorry  I just did this to someone else in another review (so I'm
> >>>> consistently wrong!)
> >>>>
> >>>> in_voltageY_calibscale is what I should have said.  That one applies a
> >>>> scaling before the raw reading is generated (so in hardware).
> >>>
> >>> Ok, then calibscale is the appropriate attribute for the resistor
> >>> setting. But as this is a device-specific hardware-calibration setting
> >>> I would suggest using the following interface:
> >>>
> >>> What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance_calibscale
> >>> Description:
> >>> 		Set the ALS calibration scale (internal resistors) for
> >>> 		analog input mode, where the scale factor is the current in uA
> >>> 		at 2V full-scale (10..1270, 10uA step), that is,
> >>>
> >>> 		R_als = 2V / in_illuminance_calibscale
> >>>
> >>> 		This setting is ignored in PWM mode.
> >> This is a generic element that really ought to just fit in with the
> >> equivalent in sysfs-bus-iio for calibscan.  It's a ratio, so it should
> >> be unit free for starters.
> >
> > I'm starting to doubt that calibscale is really appropriate in this case.
> >
> > For starters, the description in sysfs-bus-iio doesn't really apply:
> >
> > 	"Hardware applied calibration scale factor. (assumed to fix
> > 	 production inaccuracies)."
> Hmm.. if you really don't like this, Michael Hennerich had a case
> where this made even less sense, so we now have hardwaregain.
> Use that if you like...

I really think that this should remain a device specific attribute as I
originally suggested. It's an integration parameter that needs to be set
precisely depending on the actual hardware setup (which analog light
sensor and other external components). 

The lm3533 also supports two types of light sensors: pwm- and analog-
output ones. The resistor select settings only applies when in analog
mode as the input is always high impedance otherwise. Thus a generic
attribute (such as calibscale or hardware gain) shouldn't be used as it
will have no effect whatsoever in PWM-mode.

I'm thus back at my original proposal, albeit with a different name (I
think a lot of this discussion could have been avoided had I not
misnamed the parameter "gain"): 

What:		/sys/bus/iio/devices/iio:deviceX/r_select
Description:
		Set the ALS internal pull-down resistor for analog input mode
		(1..127), such that,

		R_als = 200000 / r_select	(ohm)

		This setting is ignored in PWM-mode (input is always high
		impedance in PWM-mode).

I don't think much is gained from using ohm as the unit: it just adds
complexity and the selected resistor setting will likely not match the
input value anyway. It's better that the chip integrators have full
control over which resistor setting is actually used so that it matches
external components.


> > The resistor setting of the lm3533 is about fitting an external analog
> > light sensor to the lm3533 als interface (which is basically just an adc
> > with some extra logic), that is, it is used to match the output current
> > of the chosen sensor so that the ADC measures 2V at full LUX.
> >
> > It's not a setting to calibrate "inaccuracies", but rather an
> > integration parameter that is set once when the characteristics of the
> > light sensor is known. (Sure, it could be used later to increase
> > sensitivity as well, but the main purpose is to fit a new light sensor
> > to a generic input interface.)
> >
> >>> [...]
> >>>
> >>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
> >>>>>>> +Date:		April 2012
> >>>>>>> +KernelVersion:	3.5
> >>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>>>> +Description:
> >>>>>>> +		Set the target brightness for ALS-mapper m in light zone n
> >>>>>>> +		(0..255), where m in 1..3 and n in 0..4.
> >>>>>> Don't suppose you could do a quick summary of what these zones are
> >>>>>> and why there are 3 ALS-mappers?  I'm not getting terribly far on a
> >>>>>> quick look at the datasheet!
> >>>>> Of course. The average adc readings are mapped to five light zones using
> >>>>> eight zone boundary registers (4 boundaries with hysteresis) and a set
> >>>>> of rules.
> >>>> This is going to be fun.  We'll need the boundaries and attached
> >>>> hysteresis attributes to fully specify these (nothing would indicate
> >>>> that hysterisis is involved otherwise).
> >>>
> >>> You can't define the hysteresis explicitly with the lm3533 register
> >>> interface, rather it's is defined implicitly in case threshY_falling is
> >>> less than threshY_rasising.
> >>>
> >>> So the raising/falling attributes should be enough, right?
> >> Nope, because they don't tell a general userspace application what is
> >> going on.  Without hysterisis attributes it has no way of knowing there
> >> is hysterisis present.
> >
> > Well an application could simply look at the difference between raising
> > and falling to determine the hysteresis?
> Only if it knows it has your sensor.  For other sensors it could be 
> completely separate or not present.  If the parameter is missing 
> assumption is that there is no hysterisis.
> >
> > It gets more complicated as the lm3533 allow the raising threshold to
> > be lower than the falling. It appears the device is using whichever
> > register is lower for the falling threshold. I guess I should compensate
> > for this in the driver.
> That's nasty.
> >
> > Furthermore, you can define threshold 1 to be lower than threshold 0,
> > effectively preventing zone 1 to be reached. In this case, dropping
> > below thres1_falling gives zone 0, and raising above thres1_raising gives
> > zone 2. In particular, no threshold event is generated when
> > thres0_{falling/raising} is passed in either direction. But perhaps this
> > should just be documented as a feature/quirk of the device.
> Seems sensible...
> >
> >> Feel free to make them read only though.
> >
> > So you're suggesting something like:
> >
> > 	events/in_illuminance0_threshY_falling_value
> > 	events/in_illuminance0_threshY_raising_value
> > 	events/in_illuminance0_threshY_hysteresis
> >
> > where hysteresis is a read-only attribute whose value is
> > 	
> > 	threshY_raising_value - threshY_falling_value
> yes.  Annoying it may be but it matches existing interface.

I'm posting a v4 which includes the above proposal for resistor select.
I've also added the hysteresis attributes as requested and fixed the
device threshold quirkiness mentioned above (the device is using
whichever register value is smaller as the falling threshold).

Thanks,
Johan

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

* [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-15 16:46     ` [PATCH v3] iio: add LM3533 ambient-light-sensor driver Johan Hovold
  2012-05-15 19:27       ` Andrew Morton
@ 2012-05-18 13:07       ` Johan Hovold
  2012-05-19  8:48         ` Jonathan Cameron
  2012-05-21 12:18         ` [PATCH v5] " Johan Hovold
  1 sibling, 2 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-18 13:07 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Arnd Bergmann, Andrew Morton,
	Mark Brown, linux-doc, linux-kernel, linux-iio, Johan Hovold

Add sub-driver for the ambient-light-sensor interface on National
Semiconductor / TI LM3533 lighting power chips.

The sensor interface can be used to control the LEDs and backlights of
the chip through defining five light zones and three sets of
corresponding brightness target levels.

The driver provides raw and mean adc readings along with the current
light zone through sysfs. A threshold event can be generated on zone
changes.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

Note that addition of r_select to the platform data probably needs to go
in via mfd.


v2:
 - reimplement using iio
 - add sysfs-ABI documentation
v3
 - use indexed channel
 - fix sysfs-ABI documentation typo and style
 - replace gain attribute with in_illuminance0_calibscale
 - add calibscale to platform data
 - fix adc register definitions
 - replace to_lm3533_dev_attr macro with inline function
 - fix device used for error reporting at irq allocation
 - use iio device for error reporting during setup/enable
 - rebase on staging-next
   - fix header include paths
   - use dev_to_iio_dev
   - add IIO_CHAN_INFO_RAW to info mask
   - use iio_device_{alloc,free}
v4
 - move to driver/iio/light
 - add events/in_illuminance0_threshY_hysteresis attributes
 - fix device zone-boundary quirkiness
 - clean up attribute handling
 - replace calibscale with device-specific r_select attribute


 .../ABI/testing/sysfs-bus-iio-light-lm3533-als     |   64 ++
 drivers/iio/Kconfig                                |    1 +
 drivers/iio/Makefile                               |    1 +
 drivers/iio/light/Kconfig                          |   22 +
 drivers/iio/light/Makefile                         |    5 +
 drivers/iio/light/lm3533-als.c                     |  941 ++++++++++++++++++++
 include/linux/mfd/lm3533.h                         |    1 +
 7 files changed, 1035 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
 create mode 100644 drivers/iio/light/Kconfig
 create mode 100644 drivers/iio/light/Makefile
 create mode 100644 drivers/iio/light/lm3533-als.c

diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
new file mode 100644
index 0000000..7ea1770
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
@@ -0,0 +1,64 @@
+What:		/sys/bus/iio/devices/iio:deviceX/r_select
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the ALS internal pull-down resistor for analog input mode
+		(1..127), such that,
+
+		R_als = 200000 / r_select	(ohm)
+
+		This setting is ignored in PWM-mode (input is always high
+		impedance in PWM-mode).
+
+What:		/sys/.../events/in_illuminance0_thresh_either_en
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Event generated when channel passes one of the four thresholds
+		in each direction (rising|falling) and a zone change occurs.
+		The corresponding light zone can be read from
+		in_illuminance0_zone.
+
+What:		/sys/.../events/in_illuminance0_threshY_hysteresis
+Date:		May 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the hysteresis for thresholds Y, that is,
+
+		threshY_hysteresis = threshY_raising - threshY_falling
+
+What:		/sys/.../events/illuminance_threshY_falling_value
+What:		/sys/.../events/illuminance_threshY_raising_value
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Specifies the value of threshold that the device is
+		comparing against for the events enabled by
+		in_illuminance0_thresh_either_en, where Y in 0..3.
+
+		Note that threshY_falling must be less than or equal to
+		threshY_raising.
+
+		These thresholds correspond to the eight zone-boundary
+		registers (boundaryY_{low,high}) and defines the five light
+		zones.
+
+What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the current light zone (0..4) as defined by the
+		in_illuminance0_threshY_{falling,rising} thresholds.
+
+What:		/sys/bus/iio/devices/iio:deviceX/targetY_Z
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the target brightness for ALS-mapper Y in light zone Z
+		(0..255), where Y in 1..3 and Z in 0..4.
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 56eecef..cacc74d 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -50,5 +50,6 @@ config IIO_CONSUMERS_PER_TRIGGER
 
 source "drivers/iio/adc/Kconfig"
 source "drivers/iio/amplifiers/Kconfig"
+source "drivers/iio/light/Kconfig"
 
 endif # IIO
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index e425afd..060b674 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o
 
 obj-y += adc/
 obj-y += amplifiers/
+obj-y += light/
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
new file mode 100644
index 0000000..7738a58
--- /dev/null
+++ b/drivers/iio/light/Kconfig
@@ -0,0 +1,22 @@
+#
+# Light sensors
+#
+menu "Light sensors"
+
+config SENSORS_LM3533
+	tristate "LM3533 ambient light sensor"
+	depends on MFD_LM3533
+	help
+	  If you say yes here you get support for the ambient light sensor
+	  interface on National Semiconductor / TI LM3533 Lighting Power
+	  chips.
+
+	  The sensor interface can be used to control the LEDs and backlights
+	  of the chip through defining five light zones and three sets of
+	  corresponding brightness target levels.
+
+	  The driver provides raw and mean adc readings along with the current
+	  light zone through sysfs. A threshold event can be generated on zone
+	  changes.
+
+endmenu
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
new file mode 100644
index 0000000..c1c23a0
--- /dev/null
+++ b/drivers/iio/light/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for IIO Light sensors
+#
+
+obj-$(CONFIG_SENSORS_LM3533)	+= lm3533-als.o
diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
new file mode 100644
index 0000000..5944ac1
--- /dev/null
+++ b/drivers/iio/light/lm3533-als.c
@@ -0,0 +1,941 @@
+/*
+ * lm3533-als.c -- LM3533 Ambient Light Sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/atomic.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_ALS_RESISTOR_MIN			1
+#define LM3533_ALS_RESISTOR_MAX			127
+#define LM3533_ALS_MAPPER_MIN			1
+#define LM3533_ALS_MAPPER_MAX			3
+#define LM3533_ALS_THRESH_MAX			3
+#define LM3533_ALS_ZONE_MAX			4
+
+#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
+#define LM3533_REG_ALS_CONF			0x31
+#define LM3533_REG_ALS_ZONE_INFO		0x34
+#define LM3533_REG_ALS_READ_ADC_RAW		0x37
+#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x38
+#define LM3533_REG_ALS_BOUNDARY_BASE		0x50
+#define LM3533_REG_ALS_TARGET_BASE		0x60
+
+#define LM3533_ALS_ENABLE_MASK			0x01
+#define LM3533_ALS_INPUT_MODE_MASK		0x02
+#define LM3533_ALS_INT_ENABLE_MASK		0x01
+
+#define LM3533_ALS_ZONE_SHIFT			2
+#define LM3533_ALS_ZONE_MASK			0x1c
+
+#define LM3533_ALS_FLAG_INT_ENABLED		1
+
+
+struct lm3533_als {
+	struct lm3533 *lm3533;
+
+	unsigned long flags;
+	int irq;
+
+	int pwm_mode:1;
+
+	atomic_t zone;
+	struct mutex thresh_mutex;
+};
+
+
+static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
+								int *adc)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	u8 val;
+	int ret;
+
+	if (average)
+		reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
+	else
+		reg = LM3533_REG_ALS_READ_ADC_RAW;
+
+	ret = lm3533_read(als->lm3533, reg, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to read adc\n");
+		return ret;
+	}
+
+	*adc = val;
+
+	return 0;
+}
+
+static int lm3533_als_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	int ret;
+
+	switch (mask) {
+	case 0:
+		ret = lm3533_als_get_adc(indio_dev, false, val);
+		break;
+	case IIO_CHAN_INFO_AVERAGE_RAW:
+		ret = lm3533_als_get_adc(indio_dev, true, val);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (ret)
+		return ret;
+
+	return IIO_VAL_INT;
+}
+
+static const struct iio_chan_spec lm3533_als_channels[] = {
+	{
+		.type		= IIO_LIGHT,
+		.channel	= 0,
+		.indexed	= 1,
+		.info_mask	= (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
+				   IIO_CHAN_INFO_RAW_SEPARATE_BIT),
+	}
+};
+
+static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to read zone\n");
+		return ret;
+	}
+
+	val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
+	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
+
+	return 0;
+}
+
+static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
+{
+
+	struct iio_dev *indio_dev = dev_id;
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 zone;
+	int ret;
+
+	/* Clear interrupt by reading the ALS zone register. */
+	ret = lm3533_als_get_zone(indio_dev, &zone);
+	if (ret)
+		goto out;
+
+	atomic_set(&als->zone, zone);
+
+	iio_push_event(indio_dev,
+		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+					    0,
+					    IIO_EV_TYPE_THRESH,
+					    IIO_EV_DIR_EITHER),
+		       iio_get_time_ns());
+out:
+	return IRQ_HANDLED;
+}
+
+static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+	int ret;
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
+								enable);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to get int mode\n");
+		return ret;
+	}
+
+	*enable = !!(val & mask);
+
+	return 0;
+}
+
+static int lm3533_als_get_resistor(struct iio_dev *indio_dev, u8 *val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	int ret;
+
+	if (als->pwm_mode) {
+		*val = 0;		/* always high impedance */
+		return 0;
+	}
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to get resistor\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int lm3533_als_set_resistor(struct iio_dev *indio_dev, u8 val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	int ret;
+
+	if (als->pwm_mode)
+		return -EPERM;		/* always high impedance */
+
+	if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX)
+		return -EINVAL;
+
+	ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to set resistor\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * nr   -- als mapper 1..3
+ * zone -- als zone 0..4
+ */
+static inline u8 lm3533_als_get_target_reg(unsigned nr, unsigned zone)
+{
+	return LM3533_REG_ALS_TARGET_BASE + 5 * (nr - 1) + zone;
+}
+
+static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned nr,
+							unsigned zone, u8 *val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	int ret;
+
+	if (nr < LM3533_ALS_MAPPER_MIN || nr > LM3533_ALS_MAPPER_MAX)
+		return -EINVAL;
+
+	if (zone > LM3533_ALS_ZONE_MAX)
+		return -EINVAL;
+
+	reg = lm3533_als_get_target_reg(nr, zone);
+	ret = lm3533_read(als->lm3533, reg, val);
+	if (ret)
+		dev_err(&indio_dev->dev, "failed to get target brightness\n");
+
+	return ret;
+}
+
+static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned nr,
+							unsigned zone, u8 val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	int ret;
+
+	if (nr < LM3533_ALS_MAPPER_MIN || nr > LM3533_ALS_MAPPER_MAX)
+		return -EINVAL;
+
+	if (zone > LM3533_ALS_ZONE_MAX)
+		return -EINVAL;
+
+	reg = lm3533_als_get_target_reg(nr, zone);
+	ret = lm3533_write(als->lm3533, reg, val);
+	if (ret)
+		dev_err(&indio_dev->dev, "failed to set target brightness\n");
+
+	return ret;
+}
+
+static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
+{
+	u8 offset;
+
+	if (raising)
+		offset = 0;
+	else
+		offset = 1;
+
+	return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
+}
+
+static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
+							bool raising, u8 *val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	int ret;
+
+	if (nr > LM3533_ALS_THRESH_MAX)
+		return -EINVAL;
+
+	reg = lm3533_als_get_threshold_reg(nr, raising);
+	ret = lm3533_read(als->lm3533, reg, val);
+	if (ret)
+		dev_err(&indio_dev->dev, "failed to get threshold\n");
+
+	return ret;
+}
+
+static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
+							bool raising, u8 val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 val2;
+	u8 reg;
+	u8 reg2;
+	int ret;
+
+	if (nr > LM3533_ALS_THRESH_MAX)
+		return -EINVAL;
+
+	reg = lm3533_als_get_threshold_reg(nr, raising);
+	reg2 = lm3533_als_get_threshold_reg(nr, !raising);
+
+	mutex_lock(&als->thresh_mutex);
+	ret = lm3533_read(als->lm3533, reg2, &val2);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to get threshold\n");
+		goto out;
+	}
+	/*
+	 * This device does not allow negative hysteresis (in fact, it uses
+	 * whichever value is smaller as the lower bound) so we need to make
+	 * sure that thresh_falling <= thresh_raising.
+	 */
+	if ((raising && (val < val2)) || (!raising && (val > val2))) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = lm3533_write(als->lm3533, reg, val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to set threshold\n");
+		goto out;
+	}
+out:
+	mutex_unlock(&als->thresh_mutex);
+
+	return ret;
+}
+
+static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr,
+								u8 *val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 falling;
+	u8 raising;
+	int ret;
+
+	if (nr > LM3533_ALS_THRESH_MAX)
+		return -EINVAL;
+
+	mutex_lock(&als->thresh_mutex);
+	ret = lm3533_als_get_threshold(indio_dev, nr, false, &falling);
+	if (ret)
+		goto out;
+	ret = lm3533_als_get_threshold(indio_dev, nr, true, &raising);
+	if (ret)
+		goto out;
+
+	*val = raising - falling;
+out:
+	mutex_unlock(&als->thresh_mutex);
+
+	return ret;
+}
+
+static int show_thresh_either_en(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	int enable;
+	int ret;
+
+	if (als->irq) {
+		ret = lm3533_als_get_int_mode(indio_dev, &enable);
+		if (ret)
+			return ret;
+	} else {
+		enable = 0;
+	}
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
+}
+
+static int store_thresh_either_en(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	unsigned long enable;
+	bool int_enabled;
+	u8 zone;
+	int ret;
+
+	if (!als->irq)
+		return -EBUSY;
+
+	if (kstrtoul(buf, 0, &enable))
+		return -EINVAL;
+
+	int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+	if (enable && !int_enabled) {
+		ret = lm3533_als_get_zone(indio_dev, &zone);
+		if (ret)
+			return ret;
+
+		atomic_set(&als->zone, zone);
+
+		set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+	}
+
+	ret = lm3533_als_set_int_mode(indio_dev, enable);
+	if (ret) {
+		if (!int_enabled)
+			clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+		return ret;
+	}
+
+	if (!enable)
+		clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+	return len;
+}
+
+static ssize_t show_zone(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 zone;
+	int ret;
+
+	if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) {
+		zone = atomic_read(&als->zone);
+	} else {
+		ret = lm3533_als_get_zone(indio_dev, &zone);
+		if (ret)
+			return ret;
+	}
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
+}
+
+static ssize_t show_r_select(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	u8 r_select;
+	int ret;
+
+	ret = lm3533_als_get_resistor(indio_dev, &r_select);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", r_select);
+}
+
+static int store_r_select(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	unsigned long r_select;
+	int ret;
+
+	if (kstrtoul(buf, 0, &r_select))
+		return -EINVAL;
+
+	ret = lm3533_als_set_resistor(indio_dev, r_select);
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+enum lm3533_als_attribute_type {
+	LM3533_ATTR_TYPE_HYSTERESIS,
+	LM3533_ATTR_TYPE_TARGET,
+	LM3533_ATTR_TYPE_THRESH_FALLING,
+	LM3533_ATTR_TYPE_THRESH_RAISING,
+};
+
+struct lm3533_als_attribute {
+	struct device_attribute dev_attr;
+	enum lm3533_als_attribute_type type;
+	u8 val1;
+	u8 val2;
+};
+
+static inline struct lm3533_als_attribute *
+to_lm3533_als_attr(struct device_attribute *attr)
+{
+	return container_of(attr, struct lm3533_als_attribute, dev_attr);
+}
+
+static ssize_t show_als_attr(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
+	u8 val;
+	int ret;
+
+	switch (als_attr->type) {
+	case LM3533_ATTR_TYPE_HYSTERESIS:
+		ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1,
+									&val);
+		break;
+	case LM3533_ATTR_TYPE_TARGET:
+		ret = lm3533_als_get_target(indio_dev, als_attr->val1,
+							als_attr->val2, &val);
+		break;
+	case LM3533_ATTR_TYPE_THRESH_FALLING:
+		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
+								false, &val);
+		break;
+	case LM3533_ATTR_TYPE_THRESH_RAISING:
+		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
+								true, &val);
+		break;
+	default:
+		WARN(1, "%s - bad attribute type %d\n", __func__,
+							als_attr->type);
+		ret = -ENXIO;
+	}
+
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_als_attr(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	switch (als_attr->type) {
+	case LM3533_ATTR_TYPE_TARGET:
+		ret = lm3533_als_set_target(indio_dev, als_attr->val1,
+							als_attr->val2, val);
+		break;
+	case LM3533_ATTR_TYPE_THRESH_FALLING:
+		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
+								false, val);
+		break;
+	case LM3533_ATTR_TYPE_THRESH_RAISING:
+		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
+								true, val);
+		break;
+	default:
+		WARN(1, "%s - bad attribute type %d\n", __func__,
+							als_attr->type);
+		ret = -ENXIO;
+	}
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)	\
+	{ .dev_attr	= __ATTR(_name, _mode, _show, _store),		\
+	  .type		= _type,					\
+	  .val1		= _val1,					\
+	  .val2		= _val2 }
+
+#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
+	struct lm3533_als_attribute lm3533_als_attr_##_name =		  \
+		ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)
+
+#define ALS_TARGET_ATTR_RW(_nr, _zone)					\
+	LM3533_ALS_ATTR(target##_nr##_##_zone, S_IRUGO | S_IWUSR,	\
+				show_als_attr, store_als_attr,		\
+				LM3533_ATTR_TYPE_TARGET, _nr, _zone)
+/*
+ * ALS Mapper targets
+ *
+ * target[1-3]_[0-4]		0-255
+ */
+static ALS_TARGET_ATTR_RW(1, 0);
+static ALS_TARGET_ATTR_RW(1, 1);
+static ALS_TARGET_ATTR_RW(1, 2);
+static ALS_TARGET_ATTR_RW(1, 3);
+static ALS_TARGET_ATTR_RW(1, 4);
+
+static ALS_TARGET_ATTR_RW(2, 0);
+static ALS_TARGET_ATTR_RW(2, 1);
+static ALS_TARGET_ATTR_RW(2, 2);
+static ALS_TARGET_ATTR_RW(2, 3);
+static ALS_TARGET_ATTR_RW(2, 4);
+
+static ALS_TARGET_ATTR_RW(3, 0);
+static ALS_TARGET_ATTR_RW(3, 1);
+static ALS_TARGET_ATTR_RW(3, 2);
+static ALS_TARGET_ATTR_RW(3, 3);
+static ALS_TARGET_ATTR_RW(3, 4);
+
+#define ALS_THRESH_FALLING_ATTR_RW(_nr)					\
+	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value,	\
+			S_IRUGO | S_IWUSR,				\
+			show_als_attr, store_als_attr,		\
+			LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0)
+
+#define ALS_THRESH_RAISING_ATTR_RW(_nr)					\
+	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value,	\
+			S_IRUGO | S_IWUSR,				\
+			show_als_attr, store_als_attr,			\
+			LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0)
+/*
+ * ALS Zone thresholds (boundaries)
+ *
+ * in_illuminance0_thresh[0-3]_falling_value	0-255
+ * in_illuminance0_thresh[0-3]_raising_value	0-255
+ */
+static ALS_THRESH_FALLING_ATTR_RW(0);
+static ALS_THRESH_FALLING_ATTR_RW(1);
+static ALS_THRESH_FALLING_ATTR_RW(2);
+static ALS_THRESH_FALLING_ATTR_RW(3);
+
+static ALS_THRESH_RAISING_ATTR_RW(0);
+static ALS_THRESH_RAISING_ATTR_RW(1);
+static ALS_THRESH_RAISING_ATTR_RW(2);
+static ALS_THRESH_RAISING_ATTR_RW(3);
+
+#define ALS_HYSTERESIS_ATTR_RO(_nr)					\
+	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis,	\
+			S_IRUGO, show_als_attr, NULL,			\
+			LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0)
+/*
+ * ALS Zone threshold hysteresis
+ *
+ * threshY_hysteresis = threshY_raising - threshY_falling
+ *
+ * in_illuminance0_thresh[0-3]_hysteresis	0-255
+ * in_illuminance0_thresh[0-3]_hysteresis	0-255
+ */
+static ALS_HYSTERESIS_ATTR_RO(0);
+static ALS_HYSTERESIS_ATTR_RO(1);
+static ALS_HYSTERESIS_ATTR_RO(2);
+static ALS_HYSTERESIS_ATTR_RO(3);
+
+#define ILLUMINANCE_ATTR_RO(_name) \
+	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
+#define ILLUMINANCE_ATTR_RW(_name) \
+	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
+						show_##_name, store_##_name)
+/*
+ * ALS Zone threshold-event enable
+ *
+ * in_illuminance0_thresh_either_en		0,1
+ */
+static ILLUMINANCE_ATTR_RW(thresh_either_en);
+
+/*
+ * ALS Current Zone
+ *
+ * in_illuminance0_zone		0-4
+ */
+static ILLUMINANCE_ATTR_RO(zone);
+
+/*
+ * ALS internal pull-down resistor select (analog mode)
+ *
+ * r_select	1-127
+ *
+ * R_als = 200000 / r_select (ohm)
+ */
+static LM3533_ATTR_RW(r_select);
+
+static struct attribute *lm3533_als_event_attributes[] = {
+	&dev_attr_in_illuminance0_thresh_either_en.attr,
+	&lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group lm3533_als_event_attribute_group = {
+	.attrs = lm3533_als_event_attributes
+};
+
+static struct attribute *lm3533_als_attributes[] = {
+	&dev_attr_r_select.attr,
+	&lm3533_als_attr_target1_0.dev_attr.attr,
+	&lm3533_als_attr_target1_1.dev_attr.attr,
+	&lm3533_als_attr_target1_2.dev_attr.attr,
+	&lm3533_als_attr_target1_3.dev_attr.attr,
+	&lm3533_als_attr_target1_4.dev_attr.attr,
+	&lm3533_als_attr_target2_0.dev_attr.attr,
+	&lm3533_als_attr_target2_1.dev_attr.attr,
+	&lm3533_als_attr_target2_2.dev_attr.attr,
+	&lm3533_als_attr_target2_3.dev_attr.attr,
+	&lm3533_als_attr_target2_4.dev_attr.attr,
+	&lm3533_als_attr_target3_0.dev_attr.attr,
+	&lm3533_als_attr_target3_1.dev_attr.attr,
+	&lm3533_als_attr_target3_2.dev_attr.attr,
+	&lm3533_als_attr_target3_3.dev_attr.attr,
+	&lm3533_als_attr_target3_4.dev_attr.attr,
+	&dev_attr_in_illuminance0_zone.attr,
+	NULL
+};
+
+static struct attribute_group lm3533_als_attribute_group = {
+	.attrs = lm3533_als_attributes
+};
+
+static int __devinit lm3533_als_set_input_mode(struct iio_dev *indio_dev,
+								int pwm_mode)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
+	u8 val;
+	int ret;
+
+	if (pwm_mode)
+		val = mask;	/* pwm input */
+	else
+		val = 0;	/* analog input */
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret) {
+		dev_err(&indio_dev->dev,
+				"failed to set input mode %d\n", pwm_mode);
+	}
+
+	return ret;
+}
+
+static int __devinit lm3533_als_setup(struct iio_dev *indio_dev,
+					struct lm3533_als_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_als_set_input_mode(indio_dev, pdata->pwm_mode);
+	if (ret)
+		return ret;
+
+	/* ALS input is always high impedance in PWM-mode. */
+	if (!pdata->pwm_mode) {
+		ret = lm3533_als_set_resistor(indio_dev, pdata->r_select);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int __devinit lm3533_als_enable(struct iio_dev *indio_dev)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret)
+		dev_err(&indio_dev->dev, "failed to enable ALS\n");
+
+	return ret;
+}
+
+static int __devexit lm3533_als_disable(struct iio_dev *indio_dev)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
+	if (ret)
+		dev_err(&indio_dev->dev, "failed to disable ALS\n");
+
+	return ret;
+}
+
+static const struct iio_info lm3533_als_info = {
+	.attrs		= &lm3533_als_attribute_group,
+	.event_attrs	= &lm3533_als_event_attribute_group,
+	.driver_module	= THIS_MODULE,
+	.read_raw	= &lm3533_als_read_raw,
+};
+
+static int __devinit lm3533_als_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_als_platform_data *pdata;
+	struct lm3533_als *als;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	indio_dev = iio_device_alloc(sizeof(*als));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	indio_dev->info = &lm3533_als_info;
+	indio_dev->channels = lm3533_als_channels;
+	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
+	indio_dev->name = "lm3533-als";
+	indio_dev->dev.parent = pdev->dev.parent;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	als = iio_priv(indio_dev);
+	als->lm3533 = lm3533;
+	als->irq = lm3533->irq;
+	als->pwm_mode = pdata->pwm_mode;
+	atomic_set(&als->zone, 0);
+	mutex_init(&als->thresh_mutex);
+
+	if (als->irq) {
+		ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
+						IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+						indio_dev->name, indio_dev);
+		if (ret) {
+			dev_err(&pdev->dev, "failed to request irq %d\n",
+								als->irq);
+			goto err_free_dev;
+		}
+	}
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	ret = iio_device_register(indio_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register ALS\n");
+		goto err_free_irq;
+	}
+
+	ret = lm3533_als_setup(indio_dev, pdata);
+	if (ret)
+		goto err_unregister;
+
+	ret = lm3533_als_enable(indio_dev);
+	if (ret)
+		goto err_unregister;
+
+	return 0;
+
+err_unregister:
+	iio_device_unregister(indio_dev);
+err_free_irq:
+	if (als->irq)
+		free_irq(als->irq, indio_dev);
+err_free_dev:
+	iio_device_free(indio_dev);
+
+	return ret;
+}
+
+static int __devexit lm3533_als_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	lm3533_als_disable(indio_dev);
+	iio_device_unregister(indio_dev);
+	if (als->irq)
+		free_irq(als->irq, indio_dev);
+	iio_device_free(indio_dev);
+
+	return 0;
+}
+
+static struct platform_driver lm3533_als_driver = {
+	.driver	= {
+		.name	= "lm3533-als",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= lm3533_als_probe,
+	.remove		= __devexit_p(lm3533_als_remove),
+};
+module_platform_driver(lm3533_als_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
index 9660feb..594bc59 100644
--- a/include/linux/mfd/lm3533.h
+++ b/include/linux/mfd/lm3533.h
@@ -43,6 +43,7 @@ struct lm3533_ctrlbank {
 
 struct lm3533_als_platform_data {
 	unsigned pwm_mode:1;		/* PWM input mode (default analog) */
+	u8 r_select;			/* 1 - 127 (ignored in PWM-mode) */
 };
 
 struct lm3533_bl_platform_data {
-- 
1.7.8.5


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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
  2012-05-18 12:27                     ` Johan Hovold
@ 2012-05-18 17:34                       ` Jonathan Cameron
  -1 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-18 17:34 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

On 05/18/2012 01:27 PM, Johan Hovold wrote:
> On Wed, May 16, 2012 at 03:21:14PM +0100, Jonathan Cameron wrote:
>> On 5/16/2012 2:05 PM, Johan Hovold wrote:
>>> On Tue, May 15, 2012 at 09:00:46PM +0100, Jonathan Cameron wrote:
>>>> On 05/15/2012 05:44 PM, Johan Hovold wrote:
>>>>> On Tue, May 08, 2012 at 02:47:19PM +0100, Jonathan Cameron wrote:
>>>>>> On 5/3/2012 5:36 PM, Johan Hovold wrote:
>>>>>>> On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
>>>>>>>> On 5/3/2012 11:26 AM, Johan Hovold wrote:
>>>>>>>>> Add sub-driver for the ambient light sensor interface on National
>>>>>>>>> Semiconductor / TI LM3533 lighting power chips.
>>>>>>>>>
>>>>>>>>> The sensor interface can be used to control the LEDs and backlights of
>>>>>>>>> the chip through defining five light zones and three sets of
>>>>>>>>> corresponding brightness target levels.
>>>>>>>>>
>>>>>>>>> The driver provides raw and mean adc readings along with the current
>>>>>>>>> light zone through sysfs. A threshold event can be generated on zone
>>>>>>>>> changes.
>>>>>>>> Code is fine.  Pretty much all my comments are to do with the interface.
>>>>>>> [...]
>>>>>>>
>>>>>>>>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>>>>>>>> new file mode 100644
>>>>>>>>> index 0000000..9849d14
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>>>>>>>> @@ -0,0 +1,62 @@
>>>>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
>>>>>>>>> +Date:		April 2012
>>>>>>>>> +KernelVersion:	3.5
>>>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>>>> +Description:
>>>>>>>>> +		Set the ALS gain-resistor setting (0..127) for analog input
>>>>>>>>> +		mode, where
>>>>>>>>> +
>>>>>>>>> +		0000000 - ALS input is high impedance
>>>>>>>>> +		0000001 - 200kOhm (10uA at 2V full-scale)
>>>>>>>>> +		0000010 - 100kOhm (20uA at 2V full-scale)
>>>>>>>>> +		...
>>>>>>>>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
>>>>>>>>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
>>>>>>>>> +
>>>>>>>>> +		R_als = 2V / (10uA * gain)	(gain>    0)
>>>>>>>> Firstly, no magic numbers.  These are definitely magic.
>>>>>>> Not that magic as they're clearly documented (in code and public
>>>>>>> datasheets), right? What would you prefer instead?
>>>>>> The numbers on the right of the - look good to me though then this isn't
>>>>>> a gain.  (200kohm) and the infinite element is annoying.  Why not
>>>>>> compute the actual gains?
>>>>>> Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
>>>>>> a bit of fixed point maths in the driver but the advantage is you'll
>>>>>> have real values that are standardizable across multiple devices
>>>>>> and hence allow your device to be operated by generic userspace
>>>>>> code.  Welcome to standardising interfaces - my favourite occupation ;)
>>>>>>
>>>>>>>> Secondly see in_illuminance0_scale for a suitable existing attribute.
>>>>>>> I didn't consider scale to be appropriate given the following
>>>>>>> documentation (e.g, for in_voltageY_scale):
>>>>>> sorry  I just did this to someone else in another review (so I'm
>>>>>> consistently wrong!)
>>>>>>
>>>>>> in_voltageY_calibscale is what I should have said.  That one applies a
>>>>>> scaling before the raw reading is generated (so in hardware).
>>>>>
>>>>> Ok, then calibscale is the appropriate attribute for the resistor
>>>>> setting. But as this is a device-specific hardware-calibration setting
>>>>> I would suggest using the following interface:
>>>>>
>>>>> What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance_calibscale
>>>>> Description:
>>>>> 		Set the ALS calibration scale (internal resistors) for
>>>>> 		analog input mode, where the scale factor is the current in uA
>>>>> 		at 2V full-scale (10..1270, 10uA step), that is,
>>>>>
>>>>> 		R_als = 2V / in_illuminance_calibscale
>>>>>
>>>>> 		This setting is ignored in PWM mode.
>>>> This is a generic element that really ought to just fit in with the
>>>> equivalent in sysfs-bus-iio for calibscan.  It's a ratio, so it should
>>>> be unit free for starters.
>>>
>>> I'm starting to doubt that calibscale is really appropriate in this case.
>>>
>>> For starters, the description in sysfs-bus-iio doesn't really apply:
>>>
>>> 	"Hardware applied calibration scale factor. (assumed to fix
>>> 	 production inaccuracies)."
>> Hmm.. if you really don't like this, Michael Hennerich had a case
>> where this made even less sense, so we now have hardwaregain.
>> Use that if you like...
> 
> I really think that this should remain a device specific attribute as I
> originally suggested. It's an integration parameter that needs to be set
> precisely depending on the actual hardware setup (which analog light
> sensor and other external components). 
Then it shouldn't be exposed to userspace.  If there is reason to vary
it from userspace then it is a calibration parameter and should be
treated like the other ones we have, if not it should be done from
dt or platform data.
> 
> The lm3533 also supports two types of light sensors: pwm- and analog-
> output ones. The resistor select settings only applies when in analog
> mode as the input is always high impedance otherwise. Thus a generic
> attribute (such as calibscale or hardware gain) shouldn't be used as it
> will have no effect whatsoever in PWM-mode.
> 
> I'm thus back at my original proposal, albeit with a different name (I
> think a lot of this discussion could have been avoided had I not
> misnamed the parameter "gain"): 
> 
> What:		/sys/bus/iio/devices/iio:deviceX/r_select
> Description:
> 		Set the ALS internal pull-down resistor for analog input mode
> 		(1..127), such that,
> 
> 		R_als = 200000 / r_select	(ohm)
> 
> 		This setting is ignored in PWM-mode (input is always high
> 		impedance in PWM-mode).
> 
> I don't think much is gained from using ohm as the unit: it just adds
> complexity and the selected resistor setting will likely not match the
> input value anyway. It's better that the chip integrators have full
> control over which resistor setting is actually used so that it matches
> external components.
This smacks of something that should never be exposed to users.
I'd hide it away in platform data.
> 
> 
>>> The resistor setting of the lm3533 is about fitting an external analog
>>> light sensor to the lm3533 als interface (which is basically just an adc
>>> with some extra logic), that is, it is used to match the output current
>>> of the chosen sensor so that the ADC measures 2V at full LUX.
>>>
>>> It's not a setting to calibrate "inaccuracies", but rather an
>>> integration parameter that is set once when the characteristics of the
>>> light sensor is known. (Sure, it could be used later to increase
>>> sensitivity as well, but the main purpose is to fit a new light sensor
>>> to a generic input interface.)
>>>
>>>>> [...]
>>>>>
>>>>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
>>>>>>>>> +Date:		April 2012
>>>>>>>>> +KernelVersion:	3.5
>>>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>>>> +Description:
>>>>>>>>> +		Set the target brightness for ALS-mapper m in light zone n
>>>>>>>>> +		(0..255), where m in 1..3 and n in 0..4.
>>>>>>>> Don't suppose you could do a quick summary of what these zones are
>>>>>>>> and why there are 3 ALS-mappers?  I'm not getting terribly far on a
>>>>>>>> quick look at the datasheet!
>>>>>>> Of course. The average adc readings are mapped to five light zones using
>>>>>>> eight zone boundary registers (4 boundaries with hysteresis) and a set
>>>>>>> of rules.
>>>>>> This is going to be fun.  We'll need the boundaries and attached
>>>>>> hysteresis attributes to fully specify these (nothing would indicate
>>>>>> that hysterisis is involved otherwise).
>>>>>
>>>>> You can't define the hysteresis explicitly with the lm3533 register
>>>>> interface, rather it's is defined implicitly in case threshY_falling is
>>>>> less than threshY_rasising.
>>>>>
>>>>> So the raising/falling attributes should be enough, right?
>>>> Nope, because they don't tell a general userspace application what is
>>>> going on.  Without hysterisis attributes it has no way of knowing there
>>>> is hysterisis present.
>>>
>>> Well an application could simply look at the difference between raising
>>> and falling to determine the hysteresis?
>> Only if it knows it has your sensor.  For other sensors it could be 
>> completely separate or not present.  If the parameter is missing 
>> assumption is that there is no hysterisis.
>>>
>>> It gets more complicated as the lm3533 allow the raising threshold to
>>> be lower than the falling. It appears the device is using whichever
>>> register is lower for the falling threshold. I guess I should compensate
>>> for this in the driver.
>> That's nasty.
>>>
>>> Furthermore, you can define threshold 1 to be lower than threshold 0,
>>> effectively preventing zone 1 to be reached. In this case, dropping
>>> below thres1_falling gives zone 0, and raising above thres1_raising gives
>>> zone 2. In particular, no threshold event is generated when
>>> thres0_{falling/raising} is passed in either direction. But perhaps this
>>> should just be documented as a feature/quirk of the device.
>> Seems sensible...
>>>
>>>> Feel free to make them read only though.
>>>
>>> So you're suggesting something like:
>>>
>>> 	events/in_illuminance0_threshY_falling_value
>>> 	events/in_illuminance0_threshY_raising_value
>>> 	events/in_illuminance0_threshY_hysteresis
>>>
>>> where hysteresis is a read-only attribute whose value is
>>> 	
>>> 	threshY_raising_value - threshY_falling_value
>> yes.  Annoying it may be but it matches existing interface.
> 
> I'm posting a v4 which includes the above proposal for resistor select.
> I've also added the hysteresis attributes as requested and fixed the
> device threshold quirkiness mentioned above (the device is using
> whichever register value is smaller as the falling threshold).
cool.  I'll take a look soon.
> 
> Thanks,
> Johan


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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
@ 2012-05-18 17:34                       ` Jonathan Cameron
  0 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-18 17:34 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

On 05/18/2012 01:27 PM, Johan Hovold wrote:
> On Wed, May 16, 2012 at 03:21:14PM +0100, Jonathan Cameron wrote:
>> On 5/16/2012 2:05 PM, Johan Hovold wrote:
>>> On Tue, May 15, 2012 at 09:00:46PM +0100, Jonathan Cameron wrote:
>>>> On 05/15/2012 05:44 PM, Johan Hovold wrote:
>>>>> On Tue, May 08, 2012 at 02:47:19PM +0100, Jonathan Cameron wrote:
>>>>>> On 5/3/2012 5:36 PM, Johan Hovold wrote:
>>>>>>> On Thu, May 03, 2012 at 12:40:10PM +0100, Jonathan Cameron wrote:
>>>>>>>> On 5/3/2012 11:26 AM, Johan Hovold wrote:
>>>>>>>>> Add sub-driver for the ambient light sensor interface on National
>>>>>>>>> Semiconductor / TI LM3533 lighting power chips.
>>>>>>>>>
>>>>>>>>> The sensor interface can be used to control the LEDs and backlights of
>>>>>>>>> the chip through defining five light zones and three sets of
>>>>>>>>> corresponding brightness target levels.
>>>>>>>>>
>>>>>>>>> The driver provides raw and mean adc readings along with the current
>>>>>>>>> light zone through sysfs. A threshold event can be generated on zone
>>>>>>>>> changes.
>>>>>>>> Code is fine.  Pretty much all my comments are to do with the interface.
>>>>>>> [...]
>>>>>>>
>>>>>>>>> diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>>>>>>>> new file mode 100644
>>>>>>>>> index 0000000..9849d14
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
>>>>>>>>> @@ -0,0 +1,62 @@
>>>>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/gain
>>>>>>>>> +Date:		April 2012
>>>>>>>>> +KernelVersion:	3.5
>>>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>>>> +Description:
>>>>>>>>> +		Set the ALS gain-resistor setting (0..127) for analog input
>>>>>>>>> +		mode, where
>>>>>>>>> +
>>>>>>>>> +		0000000 - ALS input is high impedance
>>>>>>>>> +		0000001 - 200kOhm (10uA at 2V full-scale)
>>>>>>>>> +		0000010 - 100kOhm (20uA at 2V full-scale)
>>>>>>>>> +		...
>>>>>>>>> +		1111110 - 1.587kOhm (1.26mA at 2V full-scale)
>>>>>>>>> +		1111111 - 1.575kOhm (1.27mA at 2V full-scale)
>>>>>>>>> +
>>>>>>>>> +		R_als = 2V / (10uA * gain)	(gain>    0)
>>>>>>>> Firstly, no magic numbers.  These are definitely magic.
>>>>>>> Not that magic as they're clearly documented (in code and public
>>>>>>> datasheets), right? What would you prefer instead?
>>>>>> The numbers on the right of the - look good to me though then this isn't
>>>>>> a gain.  (200kohm) and the infinite element is annoying.  Why not
>>>>>> compute the actual gains?
>>>>>> Gain = (Rals*10e-6)/2  and use those values?  Yes you will have to do
>>>>>> a bit of fixed point maths in the driver but the advantage is you'll
>>>>>> have real values that are standardizable across multiple devices
>>>>>> and hence allow your device to be operated by generic userspace
>>>>>> code.  Welcome to standardising interfaces - my favourite occupation ;)
>>>>>>
>>>>>>>> Secondly see in_illuminance0_scale for a suitable existing attribute.
>>>>>>> I didn't consider scale to be appropriate given the following
>>>>>>> documentation (e.g, for in_voltageY_scale):
>>>>>> sorry  I just did this to someone else in another review (so I'm
>>>>>> consistently wrong!)
>>>>>>
>>>>>> in_voltageY_calibscale is what I should have said.  That one applies a
>>>>>> scaling before the raw reading is generated (so in hardware).
>>>>>
>>>>> Ok, then calibscale is the appropriate attribute for the resistor
>>>>> setting. But as this is a device-specific hardware-calibration setting
>>>>> I would suggest using the following interface:
>>>>>
>>>>> What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance_calibscale
>>>>> Description:
>>>>> 		Set the ALS calibration scale (internal resistors) for
>>>>> 		analog input mode, where the scale factor is the current in uA
>>>>> 		at 2V full-scale (10..1270, 10uA step), that is,
>>>>>
>>>>> 		R_als = 2V / in_illuminance_calibscale
>>>>>
>>>>> 		This setting is ignored in PWM mode.
>>>> This is a generic element that really ought to just fit in with the
>>>> equivalent in sysfs-bus-iio for calibscan.  It's a ratio, so it should
>>>> be unit free for starters.
>>>
>>> I'm starting to doubt that calibscale is really appropriate in this case.
>>>
>>> For starters, the description in sysfs-bus-iio doesn't really apply:
>>>
>>> 	"Hardware applied calibration scale factor. (assumed to fix
>>> 	 production inaccuracies)."
>> Hmm.. if you really don't like this, Michael Hennerich had a case
>> where this made even less sense, so we now have hardwaregain.
>> Use that if you like...
> 
> I really think that this should remain a device specific attribute as I
> originally suggested. It's an integration parameter that needs to be set
> precisely depending on the actual hardware setup (which analog light
> sensor and other external components). 
Then it shouldn't be exposed to userspace.  If there is reason to vary
it from userspace then it is a calibration parameter and should be
treated like the other ones we have, if not it should be done from
dt or platform data.
> 
> The lm3533 also supports two types of light sensors: pwm- and analog-
> output ones. The resistor select settings only applies when in analog
> mode as the input is always high impedance otherwise. Thus a generic
> attribute (such as calibscale or hardware gain) shouldn't be used as it
> will have no effect whatsoever in PWM-mode.
> 
> I'm thus back at my original proposal, albeit with a different name (I
> think a lot of this discussion could have been avoided had I not
> misnamed the parameter "gain"): 
> 
> What:		/sys/bus/iio/devices/iio:deviceX/r_select
> Description:
> 		Set the ALS internal pull-down resistor for analog input mode
> 		(1..127), such that,
> 
> 		R_als = 200000 / r_select	(ohm)
> 
> 		This setting is ignored in PWM-mode (input is always high
> 		impedance in PWM-mode).
> 
> I don't think much is gained from using ohm as the unit: it just adds
> complexity and the selected resistor setting will likely not match the
> input value anyway. It's better that the chip integrators have full
> control over which resistor setting is actually used so that it matches
> external components.
This smacks of something that should never be exposed to users.
I'd hide it away in platform data.
> 
> 
>>> The resistor setting of the lm3533 is about fitting an external analog
>>> light sensor to the lm3533 als interface (which is basically just an adc
>>> with some extra logic), that is, it is used to match the output current
>>> of the chosen sensor so that the ADC measures 2V at full LUX.
>>>
>>> It's not a setting to calibrate "inaccuracies", but rather an
>>> integration parameter that is set once when the characteristics of the
>>> light sensor is known. (Sure, it could be used later to increase
>>> sensitivity as well, but the main purpose is to fit a new light sensor
>>> to a generic input interface.)
>>>
>>>>> [...]
>>>>>
>>>>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/target[m]_[n]
>>>>>>>>> +Date:		April 2012
>>>>>>>>> +KernelVersion:	3.5
>>>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>>>> +Description:
>>>>>>>>> +		Set the target brightness for ALS-mapper m in light zone n
>>>>>>>>> +		(0..255), where m in 1..3 and n in 0..4.
>>>>>>>> Don't suppose you could do a quick summary of what these zones are
>>>>>>>> and why there are 3 ALS-mappers?  I'm not getting terribly far on a
>>>>>>>> quick look at the datasheet!
>>>>>>> Of course. The average adc readings are mapped to five light zones using
>>>>>>> eight zone boundary registers (4 boundaries with hysteresis) and a set
>>>>>>> of rules.
>>>>>> This is going to be fun.  We'll need the boundaries and attached
>>>>>> hysteresis attributes to fully specify these (nothing would indicate
>>>>>> that hysterisis is involved otherwise).
>>>>>
>>>>> You can't define the hysteresis explicitly with the lm3533 register
>>>>> interface, rather it's is defined implicitly in case threshY_falling is
>>>>> less than threshY_rasising.
>>>>>
>>>>> So the raising/falling attributes should be enough, right?
>>>> Nope, because they don't tell a general userspace application what is
>>>> going on.  Without hysterisis attributes it has no way of knowing there
>>>> is hysterisis present.
>>>
>>> Well an application could simply look at the difference between raising
>>> and falling to determine the hysteresis?
>> Only if it knows it has your sensor.  For other sensors it could be 
>> completely separate or not present.  If the parameter is missing 
>> assumption is that there is no hysterisis.
>>>
>>> It gets more complicated as the lm3533 allow the raising threshold to
>>> be lower than the falling. It appears the device is using whichever
>>> register is lower for the falling threshold. I guess I should compensate
>>> for this in the driver.
>> That's nasty.
>>>
>>> Furthermore, you can define threshold 1 to be lower than threshold 0,
>>> effectively preventing zone 1 to be reached. In this case, dropping
>>> below thres1_falling gives zone 0, and raising above thres1_raising gives
>>> zone 2. In particular, no threshold event is generated when
>>> thres0_{falling/raising} is passed in either direction. But perhaps this
>>> should just be documented as a feature/quirk of the device.
>> Seems sensible...
>>>
>>>> Feel free to make them read only though.
>>>
>>> So you're suggesting something like:
>>>
>>> 	events/in_illuminance0_threshY_falling_value
>>> 	events/in_illuminance0_threshY_raising_value
>>> 	events/in_illuminance0_threshY_hysteresis
>>>
>>> where hysteresis is a read-only attribute whose value is
>>> 	
>>> 	threshY_raising_value - threshY_falling_value
>> yes.  Annoying it may be but it matches existing interface.
> 
> I'm posting a v4 which includes the above proposal for resistor select.
> I've also added the hysteresis attributes as requested and fixed the
> device threshold quirkiness mentioned above (the device is using
> whichever register value is smaller as the falling threshold).
cool.  I'll take a look soon.
> 
> Thanks,
> Johan


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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
  2012-05-18 17:34                       ` Jonathan Cameron
@ 2012-05-18 17:57                         ` Johan Hovold
  -1 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-18 17:57 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Johan Hovold, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev

On Fri, May 18, 2012 at 06:34:01PM +0100, Jonathan Cameron wrote:
> On 05/18/2012 01:27 PM, Johan Hovold wrote:

[...]

> > I really think that this should remain a device specific attribute as I
> > originally suggested. It's an integration parameter that needs to be set
> > precisely depending on the actual hardware setup (which analog light
> > sensor and other external components). 
> Then it shouldn't be exposed to userspace.  If there is reason to vary
> it from userspace then it is a calibration parameter and should be
> treated like the other ones we have, if not it should be done from
> dt or platform data.
> > 
> > The lm3533 also supports two types of light sensors: pwm- and analog-
> > output ones. The resistor select settings only applies when in analog
> > mode as the input is always high impedance otherwise. Thus a generic
> > attribute (such as calibscale or hardware gain) shouldn't be used as it
> > will have no effect whatsoever in PWM-mode.
> > 
> > I'm thus back at my original proposal, albeit with a different name (I
> > think a lot of this discussion could have been avoided had I not
> > misnamed the parameter "gain"): 
> > 
> > What:		/sys/bus/iio/devices/iio:deviceX/r_select
> > Description:
> > 		Set the ALS internal pull-down resistor for analog input mode
> > 		(1..127), such that,
> > 
> > 		R_als = 200000 / r_select	(ohm)
> > 
> > 		This setting is ignored in PWM-mode (input is always high
> > 		impedance in PWM-mode).
> > 
> > I don't think much is gained from using ohm as the unit: it just adds
> > complexity and the selected resistor setting will likely not match the
> > input value anyway. It's better that the chip integrators have full
> > control over which resistor setting is actually used so that it matches
> > external components.
> This smacks of something that should never be exposed to users.
> I'd hide it away in platform data.

Fair enough. I'll drop the sysfs param and submit a patch for mfd-next
which adds r_select to the platform data.

Thanks,
Johan

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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
@ 2012-05-18 17:57                         ` Johan Hovold
  0 siblings, 0 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-18 17:57 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Johan Hovold, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, devel, linux-fbdev

On Fri, May 18, 2012 at 06:34:01PM +0100, Jonathan Cameron wrote:
> On 05/18/2012 01:27 PM, Johan Hovold wrote:

[...]

> > I really think that this should remain a device specific attribute as I
> > originally suggested. It's an integration parameter that needs to be set
> > precisely depending on the actual hardware setup (which analog light
> > sensor and other external components). 
> Then it shouldn't be exposed to userspace.  If there is reason to vary
> it from userspace then it is a calibration parameter and should be
> treated like the other ones we have, if not it should be done from
> dt or platform data.
> > 
> > The lm3533 also supports two types of light sensors: pwm- and analog-
> > output ones. The resistor select settings only applies when in analog
> > mode as the input is always high impedance otherwise. Thus a generic
> > attribute (such as calibscale or hardware gain) shouldn't be used as it
> > will have no effect whatsoever in PWM-mode.
> > 
> > I'm thus back at my original proposal, albeit with a different name (I
> > think a lot of this discussion could have been avoided had I not
> > misnamed the parameter "gain"): 
> > 
> > What:		/sys/bus/iio/devices/iio:deviceX/r_select
> > Description:
> > 		Set the ALS internal pull-down resistor for analog input mode
> > 		(1..127), such that,
> > 
> > 		R_als = 200000 / r_select	(ohm)
> > 
> > 		This setting is ignored in PWM-mode (input is always high
> > 		impedance in PWM-mode).
> > 
> > I don't think much is gained from using ohm as the unit: it just adds
> > complexity and the selected resistor setting will likely not match the
> > input value anyway. It's better that the chip integrators have full
> > control over which resistor setting is actually used so that it matches
> > external components.
> This smacks of something that should never be exposed to users.
> I'd hide it away in platform data.

Fair enough. I'll drop the sysfs param and submit a patch for mfd-next
which adds r_select to the platform data.

Thanks,
Johan

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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
  2012-05-18 17:57                         ` Johan Hovold
@ 2012-05-19  8:04                           ` Jonathan Cameron
  -1 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-19  8:04 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

On 05/18/2012 06:57 PM, Johan Hovold wrote:
> On Fri, May 18, 2012 at 06:34:01PM +0100, Jonathan Cameron wrote:
>> On 05/18/2012 01:27 PM, Johan Hovold wrote:
> 
> [...]
> 
>>> I really think that this should remain a device specific attribute as I
>>> originally suggested. It's an integration parameter that needs to be set
>>> precisely depending on the actual hardware setup (which analog light
>>> sensor and other external components). 
>> Then it shouldn't be exposed to userspace.  If there is reason to vary
>> it from userspace then it is a calibration parameter and should be
>> treated like the other ones we have, if not it should be done from
>> dt or platform data.
>>>
>>> The lm3533 also supports two types of light sensors: pwm- and analog-
>>> output ones. The resistor select settings only applies when in analog
>>> mode as the input is always high impedance otherwise. Thus a generic
>>> attribute (such as calibscale or hardware gain) shouldn't be used as it
>>> will have no effect whatsoever in PWM-mode.
>>>
>>> I'm thus back at my original proposal, albeit with a different name (I
>>> think a lot of this discussion could have been avoided had I not
>>> misnamed the parameter "gain"): 
>>>
>>> What:		/sys/bus/iio/devices/iio:deviceX/r_select
>>> Description:
>>> 		Set the ALS internal pull-down resistor for analog input mode
>>> 		(1..127), such that,
>>>
>>> 		R_als = 200000 / r_select	(ohm)
>>>
>>> 		This setting is ignored in PWM-mode (input is always high
>>> 		impedance in PWM-mode).
>>>
>>> I don't think much is gained from using ohm as the unit: it just adds
>>> complexity and the selected resistor setting will likely not match the
>>> input value anyway. It's better that the chip integrators have full
>>> control over which resistor setting is actually used so that it matches
>>> external components.
>> This smacks of something that should never be exposed to users.
>> I'd hide it away in platform data.
> 
> Fair enough. I'll drop the sysfs param and submit a patch for mfd-next
> which adds r_select to the platform data.
> 
cool. I'll review the rest of the patch with the assumption you'll do this.

Jonathan

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

* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
@ 2012-05-19  8:04                           ` Jonathan Cameron
  0 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-19  8:04 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	devel, linux-fbdev

On 05/18/2012 06:57 PM, Johan Hovold wrote:
> On Fri, May 18, 2012 at 06:34:01PM +0100, Jonathan Cameron wrote:
>> On 05/18/2012 01:27 PM, Johan Hovold wrote:
> 
> [...]
> 
>>> I really think that this should remain a device specific attribute as I
>>> originally suggested. It's an integration parameter that needs to be set
>>> precisely depending on the actual hardware setup (which analog light
>>> sensor and other external components). 
>> Then it shouldn't be exposed to userspace.  If there is reason to vary
>> it from userspace then it is a calibration parameter and should be
>> treated like the other ones we have, if not it should be done from
>> dt or platform data.
>>>
>>> The lm3533 also supports two types of light sensors: pwm- and analog-
>>> output ones. The resistor select settings only applies when in analog
>>> mode as the input is always high impedance otherwise. Thus a generic
>>> attribute (such as calibscale or hardware gain) shouldn't be used as it
>>> will have no effect whatsoever in PWM-mode.
>>>
>>> I'm thus back at my original proposal, albeit with a different name (I
>>> think a lot of this discussion could have been avoided had I not
>>> misnamed the parameter "gain"): 
>>>
>>> What:		/sys/bus/iio/devices/iio:deviceX/r_select
>>> Description:
>>> 		Set the ALS internal pull-down resistor for analog input mode
>>> 		(1..127), such that,
>>>
>>> 		R_als = 200000 / r_select	(ohm)
>>>
>>> 		This setting is ignored in PWM-mode (input is always high
>>> 		impedance in PWM-mode).
>>>
>>> I don't think much is gained from using ohm as the unit: it just adds
>>> complexity and the selected resistor setting will likely not match the
>>> input value anyway. It's better that the chip integrators have full
>>> control over which resistor setting is actually used so that it matches
>>> external components.
>> This smacks of something that should never be exposed to users.
>> I'd hide it away in platform data.
> 
> Fair enough. I'll drop the sysfs param and submit a patch for mfd-next
> which adds r_select to the platform data.
> 
cool. I'll review the rest of the patch with the assumption you'll do this.

Jonathan

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

* Re: [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-18 13:07       ` [PATCH v4] " Johan Hovold
@ 2012-05-19  8:48         ` Jonathan Cameron
  2012-05-19 16:30           ` Johan Hovold
  2012-05-21  9:50           ` Johan Hovold
  2012-05-21 12:18         ` [PATCH v5] " Johan Hovold
  1 sibling, 2 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-19  8:48 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio

On 05/18/2012 02:07 PM, Johan Hovold wrote:
> Add sub-driver for the ambient-light-sensor interface on National
> Semiconductor / TI LM3533 lighting power chips.
> 
> The sensor interface can be used to control the LEDs and backlights of
> the chip through defining five light zones and three sets of
> corresponding brightness target levels.
> 
> The driver provides raw and mean adc readings along with the current
> light zone through sysfs. A threshold event can be generated on zone
> changes.

Hi Johan,

I hate to be a pain with this one, but it's a complex beast and I'd
really like to get the interface right first time - particularly as
you are going in after the move out of staging.


Queries for you.
1) Ordering in the probe function. Normally expect iio_device_register
to be the last call. Why not here?
2) Worth combining enable / disable into one as very similar functions?
3) Suspicious code in als_set_input_mode

Naming of the target values.  I think we can make the naming of these
fit in much better with the normal abi which is going to be all for the
good in the long run.  They are basically current output channels
with a controllable set of steps (where we don't have direct control
of which one we are in).  This is very similar to the frequency controls
on some of Analog's dds that we have a well defined interface for.

More detail below, but in summary.

out_currentX_currentY_raw for channel X value for zone Y.

Jonathan
> 
> Signed-off-by: Johan Hovold <jhovold@gmail.com>
> ---
> 
> Note that addition of r_select to the platform data probably needs to go
> in via mfd.
> 
> 
> v2:
>  - reimplement using iio
>  - add sysfs-ABI documentation
> v3
>  - use indexed channel
>  - fix sysfs-ABI documentation typo and style
>  - replace gain attribute with in_illuminance0_calibscale
>  - add calibscale to platform data
>  - fix adc register definitions
>  - replace to_lm3533_dev_attr macro with inline function
>  - fix device used for error reporting at irq allocation
>  - use iio device for error reporting during setup/enable
>  - rebase on staging-next
>    - fix header include paths
>    - use dev_to_iio_dev
>    - add IIO_CHAN_INFO_RAW to info mask
>    - use iio_device_{alloc,free}
> v4
>  - move to driver/iio/light
>  - add events/in_illuminance0_threshY_hysteresis attributes
>  - fix device zone-boundary quirkiness
>  - clean up attribute handling
>  - replace calibscale with device-specific r_select attribute
> 
> 
>  .../ABI/testing/sysfs-bus-iio-light-lm3533-als     |   64 ++
>  drivers/iio/Kconfig                                |    1 +
>  drivers/iio/Makefile                               |    1 +
>  drivers/iio/light/Kconfig                          |   22 +
>  drivers/iio/light/Makefile                         |    5 +
>  drivers/iio/light/lm3533-als.c                     |  941 ++++++++++++++++++++
>  include/linux/mfd/lm3533.h                         |    1 +
>  7 files changed, 1035 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>  create mode 100644 drivers/iio/light/Kconfig
>  create mode 100644 drivers/iio/light/Makefile
>  create mode 100644 drivers/iio/light/lm3533-als.c
> 
> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> new file mode 100644
> index 0000000..7ea1770
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> @@ -0,0 +1,64 @@
> +What:		/sys/bus/iio/devices/iio:deviceX/r_select
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold <jhovold@gmail.com>
> +Description:
> +		Set the ALS internal pull-down resistor for analog input mode
> +		(1..127), such that,
> +
> +		R_als = 200000 / r_select	(ohm)
> +
> +		This setting is ignored in PWM-mode (input is always high
> +		impedance in PWM-mode).
> +
> +What:		/sys/.../events/in_illuminance0_thresh_either_en
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold <jhovold@gmail.com>
> +Description:
> +		Event generated when channel passes one of the four thresholds
> +		in each direction (rising|falling) and a zone change occurs.
> +		The corresponding light zone can be read from
> +		in_illuminance0_zone.
> +
> +What:		/sys/.../events/in_illuminance0_threshY_hysteresis
> +Date:		May 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold <jhovold@gmail.com>
> +Description:
> +		Get the hysteresis for thresholds Y, that is,
> +
> +		threshY_hysteresis = threshY_raising - threshY_falling
> +
> +What:		/sys/.../events/illuminance_threshY_falling_value
> +What:		/sys/.../events/illuminance_threshY_raising_value
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold <jhovold@gmail.com>
> +Description:
> +		Specifies the value of threshold that the device is
> +		comparing against for the events enabled by
> +		in_illuminance0_thresh_either_en, where Y in 0..3.
> +
> +		Note that threshY_falling must be less than or equal to
> +		threshY_raising.
> +
> +		These thresholds correspond to the eight zone-boundary
> +		registers (boundaryY_{low,high}) and defines the five light
> +		zones.
> +
> +What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold <jhovold@gmail.com>
> +Description:
> +		Get the current light zone (0..4) as defined by the
> +		in_illuminance0_threshY_{falling,rising} thresholds.
> +
> +What:		/sys/bus/iio/devices/iio:deviceX/targetY_Z
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold <jhovold@gmail.com>
> +Description:
> +		Set the target brightness for ALS-mapper Y in light zone Z
> +		(0..255), where Y in 1..3 and Z in 0..4.

What are the units of this?  Also arguably is it not the als that this
is related to, but rather the light source?  A quick datasheet browse says
that these are current targets? If so I wonder if we can make that
explicit...  Could treat them as 3 output channels and have indexed values
like we do for frequencies in dds devices (where external hardware is
controlling them.

Hmm. lets see.

out_currentX_currentY_raw
(the double naming is a bit clunky but corresponds to frequency devices
where we have
out_altvoltageX_frequencyY_raw

Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
as indexed values they can take 0,1,2,3,4
out_currentX_raw is not read only and gives you the current for whichever
zone the device is currently in.

This may seem convoluted but I'd really rather have something generalizable
for this if we possibly can.  We'd still need documentation to say what is
controlling these, but at least they'd fit within our more general abi.

What do you think?

> diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
> index 56eecef..cacc74d 100644
> --- a/drivers/iio/Kconfig
> +++ b/drivers/iio/Kconfig
> @@ -50,5 +50,6 @@ config IIO_CONSUMERS_PER_TRIGGER
>  
>  source "drivers/iio/adc/Kconfig"
>  source "drivers/iio/amplifiers/Kconfig"
> +source "drivers/iio/light/Kconfig"
>  
>  endif # IIO
> diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
> index e425afd..060b674 100644
> --- a/drivers/iio/Makefile
> +++ b/drivers/iio/Makefile
> @@ -11,3 +11,4 @@ obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o
>  
>  obj-y += adc/
>  obj-y += amplifiers/
> +obj-y += light/
> diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> new file mode 100644
> index 0000000..7738a58
> --- /dev/null
> +++ b/drivers/iio/light/Kconfig
> @@ -0,0 +1,22 @@
> +#
> +# Light sensors
> +#
> +menu "Light sensors"
> +
> +config SENSORS_LM3533
> +	tristate "LM3533 ambient light sensor"
> +	depends on MFD_LM3533
> +	help
> +	  If you say yes here you get support for the ambient light sensor
> +	  interface on National Semiconductor / TI LM3533 Lighting Power
> +	  chips.
> +
> +	  The sensor interface can be used to control the LEDs and backlights
> +	  of the chip through defining five light zones and three sets of
> +	  corresponding brightness target levels.
> +
> +	  The driver provides raw and mean adc readings along with the current
> +	  light zone through sysfs. A threshold event can be generated on zone
> +	  changes.
> +
> +endmenu
> diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
> new file mode 100644
> index 0000000..c1c23a0
> --- /dev/null
> +++ b/drivers/iio/light/Makefile
> @@ -0,0 +1,5 @@
> +#
> +# Makefile for IIO Light sensors
> +#
> +
> +obj-$(CONFIG_SENSORS_LM3533)	+= lm3533-als.o
> diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
> new file mode 100644
> index 0000000..5944ac1
> --- /dev/null
> +++ b/drivers/iio/light/lm3533-als.c
> @@ -0,0 +1,941 @@
> +/*
> + * lm3533-als.c -- LM3533 Ambient Light Sensor driver
> + *
> + * Copyright (C) 2011-2012 Texas Instruments
> + *
> + * Author: Johan Hovold <jhovold@gmail.com>
> + *
> + * 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.
> + */
> +
> +#include <linux/atomic.h>
> +#include <linux/fs.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/iio/events.h>
> +#include <linux/iio/iio.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/mfd/core.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +
> +#include <linux/mfd/lm3533.h>
> +
> +
> +#define LM3533_ALS_RESISTOR_MIN			1
> +#define LM3533_ALS_RESISTOR_MAX			127
> +#define LM3533_ALS_MAPPER_MIN			1
> +#define LM3533_ALS_MAPPER_MAX			3
> +#define LM3533_ALS_THRESH_MAX			3
> +#define LM3533_ALS_ZONE_MAX			4
> +
> +#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
> +#define LM3533_REG_ALS_CONF			0x31
> +#define LM3533_REG_ALS_ZONE_INFO		0x34
> +#define LM3533_REG_ALS_READ_ADC_RAW		0x37
> +#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x38
> +#define LM3533_REG_ALS_BOUNDARY_BASE		0x50
> +#define LM3533_REG_ALS_TARGET_BASE		0x60
> +
> +#define LM3533_ALS_ENABLE_MASK			0x01
> +#define LM3533_ALS_INPUT_MODE_MASK		0x02
> +#define LM3533_ALS_INT_ENABLE_MASK		0x01
> +
> +#define LM3533_ALS_ZONE_SHIFT			2
> +#define LM3533_ALS_ZONE_MASK			0x1c
> +
> +#define LM3533_ALS_FLAG_INT_ENABLED		1
> +
> +
> +struct lm3533_als {
> +	struct lm3533 *lm3533;
> +
> +	unsigned long flags;
> +	int irq;
> +
Boolean might be better as it's not a though this will save
space!
> +	int pwm_mode:1;
> +
> +	atomic_t zone;
> +	struct mutex thresh_mutex;
> +};
Rarely a reason for more than one blank line in my opinion...
> +
> +
May be roll this into it's one call site. will make for marginally
less code I think..
> +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
> +								int *adc)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 reg;
> +	u8 val;
> +	int ret;
> +
> +	if (average)
> +		reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
> +	else
> +		reg = LM3533_REG_ALS_READ_ADC_RAW;
> +
> +	ret = lm3533_read(als->lm3533, reg, &val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to read adc\n");
> +		return ret;
> +	}
> +
> +	*adc = val;
> +
> +	return 0;
> +}
> +
> +static int lm3533_als_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val, int *val2, long mask)
> +{
> +	int ret;
> +
> +	switch (mask) {
> +	case 0:
> +		ret = lm3533_als_get_adc(indio_dev, false, val);
> +		break;
> +	case IIO_CHAN_INFO_AVERAGE_RAW:
> +		ret = lm3533_als_get_adc(indio_dev, true, val);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	return IIO_VAL_INT;
> +}
> +
Why have an array?  Just use the address and set the num_channels = 1;
> +static const struct iio_chan_spec lm3533_als_channels[] = {
> +	{
> +		.type		= IIO_LIGHT,
> +		.channel	= 0,
> +		.indexed	= 1,
> +		.info_mask	= (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
> +				   IIO_CHAN_INFO_RAW_SEPARATE_BIT),
> +	}
> +};
> +
> +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 val;
> +	int ret;
> +
> +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to read zone\n");
> +		return ret;
> +	}
> +
> +	val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
> +	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
> +
> +	return 0;
> +}
> +
> +static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
> +{
> +
> +	struct iio_dev *indio_dev = dev_id;
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 zone;
> +	int ret;
> +
> +	/* Clear interrupt by reading the ALS zone register. */
> +	ret = lm3533_als_get_zone(indio_dev, &zone);
> +	if (ret)
> +		goto out;
> +
> +	atomic_set(&als->zone, zone);
> +
> +	iio_push_event(indio_dev,
> +		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
> +					    0,
> +					    IIO_EV_TYPE_THRESH,
> +					    IIO_EV_DIR_EITHER),
> +		       iio_get_time_ns());
> +out:
> +	return IRQ_HANDLED;
> +}
> +
could just roll this into the one call point, it's not exactly complex.
> +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> +	u8 val;
> +	int ret;
> +
> +	if (enable)
> +		val = mask;
> +	else
> +		val = 0;
> +
> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
> +								enable);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> +	u8 val;
> +	int ret;
> +
> +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to get int mode\n");
> +		return ret;
> +	}
> +
> +	*enable = !!(val & mask);
> +
> +	return 0;
> +}
> +
Given only accessed from one place, why not just roll it in there?
> +static int lm3533_als_get_resistor(struct iio_dev *indio_dev, u8 *val)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	int ret;
> +
> +	if (als->pwm_mode) {
> +		*val = 0;		/* always high impedance */
> +		return 0;
> +	}
> +
> +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to get resistor\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int lm3533_als_set_resistor(struct iio_dev *indio_dev, u8 val)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	int ret;
> +
> +	if (als->pwm_mode)
> +		return -EPERM;		/* always high impedance */
> +
> +	if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX)
> +		return -EINVAL;
> +
> +	ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to set resistor\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * nr   -- als mapper 1..3
> + * zone -- als zone 0..4
> + */
> +static inline u8 lm3533_als_get_target_reg(unsigned nr, unsigned zone)
> +{
> +	return LM3533_REG_ALS_TARGET_BASE + 5 * (nr - 1) + zone;
> +}
> +
> +static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned nr,
> +							unsigned zone, u8 *val)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 reg;
> +	int ret;
> +
> +	if (nr < LM3533_ALS_MAPPER_MIN || nr > LM3533_ALS_MAPPER_MAX)
> +		return -EINVAL;
> +
> +	if (zone > LM3533_ALS_ZONE_MAX)
> +		return -EINVAL;
> +
> +	reg = lm3533_als_get_target_reg(nr, zone);
> +	ret = lm3533_read(als->lm3533, reg, val);
> +	if (ret)
> +		dev_err(&indio_dev->dev, "failed to get target brightness\n");
> +
> +	return ret;
> +}
> +
> +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned nr,
> +							unsigned zone, u8 val)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 reg;
> +	int ret;
> +
> +	if (nr < LM3533_ALS_MAPPER_MIN || nr > LM3533_ALS_MAPPER_MAX)
> +		return -EINVAL;
> +
> +	if (zone > LM3533_ALS_ZONE_MAX)
> +		return -EINVAL;
> +
> +	reg = lm3533_als_get_target_reg(nr, zone);
> +	ret = lm3533_write(als->lm3533, reg, val);
I wouldn't bother with the intermediate  but up to you...

ret = lm3533_write(als->lm3533, lm3533_als_get_target_reg(nr, zone), val);

> +	if (ret)
> +		dev_err(&indio_dev->dev, "failed to set target brightness\n");
> +
> +	return ret;
> +}
> +
> +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
> +{
> +	u8 offset;
> +
offset = !raising;

> +	if (raising)
> +		offset = 0;
> +	else
> +		offset = 1;
> +
> +	return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
> +}
> +
> +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
> +							bool raising, u8 *val)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 reg;
> +	int ret;
> +
> +	if (nr > LM3533_ALS_THRESH_MAX)
> +		return -EINVAL;
> +
> +	reg = lm3533_als_get_threshold_reg(nr, raising);
> +	ret = lm3533_read(als->lm3533, reg, val);
> +	if (ret)
> +		dev_err(&indio_dev->dev, "failed to get threshold\n");
> +
> +	return ret;
> +}
> +
> +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
> +							bool raising, u8 val)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 val2;
> +	u8 reg;
> +	u8 reg2;
u8 val2, reg, reg2; Shorter and still obvious.
> +	int ret;
> +
> +	if (nr > LM3533_ALS_THRESH_MAX)
> +		return -EINVAL;
> +
> +	reg = lm3533_als_get_threshold_reg(nr, raising);
> +	reg2 = lm3533_als_get_threshold_reg(nr, !raising);
> +
> +	mutex_lock(&als->thresh_mutex);
> +	ret = lm3533_read(als->lm3533, reg2, &val2);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to get threshold\n");
> +		goto out;
> +	}
> +	/*
> +	 * This device does not allow negative hysteresis (in fact, it uses
> +	 * whichever value is smaller as the lower bound) so we need to make
> +	 * sure that thresh_falling <= thresh_raising.
> +	 */
> +	if ((raising && (val < val2)) || (!raising && (val > val2))) {
> +		ret = -EINVAL;
> +		goto out;
> +	}
> +
> +	ret = lm3533_write(als->lm3533, reg, val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to set threshold\n");
> +		goto out;
> +	}
> +out:
> +	mutex_unlock(&als->thresh_mutex);
> +
> +	return ret;
> +}
> +
> +static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr,
> +								u8 *val)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 falling;
> +	u8 raising;
> +	int ret;
> +
> +	if (nr > LM3533_ALS_THRESH_MAX)
> +		return -EINVAL;
> +
> +	mutex_lock(&als->thresh_mutex);
> +	ret = lm3533_als_get_threshold(indio_dev, nr, false, &falling);
> +	if (ret)
> +		goto out;
> +	ret = lm3533_als_get_threshold(indio_dev, nr, true, &raising);
> +	if (ret)
> +		goto out;
> +
> +	*val = raising - falling;
> +out:
> +	mutex_unlock(&als->thresh_mutex);
> +
> +	return ret;
> +}
> +
> +static int show_thresh_either_en(struct device *dev,
> +					struct device_attribute *attr,
> +					char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	int enable;
> +	int ret;
> +
> +	if (als->irq) {
> +		ret = lm3533_als_get_int_mode(indio_dev, &enable);
> +		if (ret)
> +			return ret;
> +	} else {
> +		enable = 0;
> +	}
> +
> +	return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
> +}
> +
> +static int store_thresh_either_en(struct device *dev,
> +					struct device_attribute *attr,
> +					const char *buf, size_t len)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	unsigned long enable;
> +	bool int_enabled;
> +	u8 zone;
> +	int ret;
> +
> +	if (!als->irq)
> +		return -EBUSY;
> +
> +	if (kstrtoul(buf, 0, &enable))
> +		return -EINVAL;
> +
> +	int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
> +
> +	if (enable && !int_enabled) {
> +		ret = lm3533_als_get_zone(indio_dev, &zone);
> +		if (ret)
> +			return ret;
> +
> +		atomic_set(&als->zone, zone);
> +
> +		set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
> +	}
> +
> +	ret = lm3533_als_set_int_mode(indio_dev, enable);
> +	if (ret) {
> +		if (!int_enabled)
> +			clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
> +
> +		return ret;
> +	}
> +
> +	if (!enable)
> +		clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
> +
> +	return len;
> +}
> +
> +static ssize_t show_zone(struct device *dev,
> +				struct device_attribute *attr, char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 zone;
> +	int ret;
> +
> +	if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) {
> +		zone = atomic_read(&als->zone);
> +	} else {
> +		ret = lm3533_als_get_zone(indio_dev, &zone);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
> +}
> +
> +static ssize_t show_r_select(struct device *dev,
> +				struct device_attribute *attr, char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	u8 r_select;
> +	int ret;
> +
> +	ret = lm3533_als_get_resistor(indio_dev, &r_select);
> +	if (ret)
> +		return ret;
> +
> +	return scnprintf(buf, PAGE_SIZE, "%u\n", r_select);
> +}
> +
> +static int store_r_select(struct device *dev,
> +					struct device_attribute *attr,
> +					const char *buf, size_t len)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	unsigned long r_select;
> +	int ret;
> +
> +	if (kstrtoul(buf, 0, &r_select))
> +		return -EINVAL;
> +
> +	ret = lm3533_als_set_resistor(indio_dev, r_select);
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +enum lm3533_als_attribute_type {
> +	LM3533_ATTR_TYPE_HYSTERESIS,
> +	LM3533_ATTR_TYPE_TARGET,
> +	LM3533_ATTR_TYPE_THRESH_FALLING,
> +	LM3533_ATTR_TYPE_THRESH_RAISING,
> +};
> +
> +struct lm3533_als_attribute {
> +	struct device_attribute dev_attr;
> +	enum lm3533_als_attribute_type type;
> +	u8 val1;
> +	u8 val2;
> +};
> +
> +static inline struct lm3533_als_attribute *
> +to_lm3533_als_attr(struct device_attribute *attr)
> +{
> +	return container_of(attr, struct lm3533_als_attribute, dev_attr);
> +}
> +
> +static ssize_t show_als_attr(struct device *dev,
> +					struct device_attribute *attr,
> +					char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> +	u8 val;
> +	int ret;
> +
> +	switch (als_attr->type) {
> +	case LM3533_ATTR_TYPE_HYSTERESIS:
> +		ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1,
> +									&val);
> +		break;
> +	case LM3533_ATTR_TYPE_TARGET:
> +		ret = lm3533_als_get_target(indio_dev, als_attr->val1,
> +							als_attr->val2, &val);
> +		break;
> +	case LM3533_ATTR_TYPE_THRESH_FALLING:
> +		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
> +								false, &val);
> +		break;
> +	case LM3533_ATTR_TYPE_THRESH_RAISING:
> +		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
> +								true, &val);
> +		break;
> +	default:
> +		WARN(1, "%s - bad attribute type %d\n", __func__,
> +							als_attr->type);
> +		ret = -ENXIO;
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
> +}
> +
> +static ssize_t store_als_attr(struct device *dev,
> +					struct device_attribute *attr,
> +					const char *buf, size_t len)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> +	u8 val;
> +	int ret;
> +
> +	if (kstrtou8(buf, 0, &val))
> +		return -EINVAL;
> +
> +	switch (als_attr->type) {
> +	case LM3533_ATTR_TYPE_TARGET:
> +		ret = lm3533_als_set_target(indio_dev, als_attr->val1,
> +							als_attr->val2, val);
> +		break;
> +	case LM3533_ATTR_TYPE_THRESH_FALLING:
> +		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> +								false, val);
> +		break;
> +	case LM3533_ATTR_TYPE_THRESH_RAISING:
> +		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> +								true, val);
> +		break;
> +	default:
I'd be tempted to drop this. It is easy to verify whether it will occur.
> +		WARN(1, "%s - bad attribute type %d\n", __func__,
> +							als_attr->type);
> +		ret = -ENXIO;
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)	\
> +	{ .dev_attr	= __ATTR(_name, _mode, _show, _store),		\
> +	  .type		= _type,					\
> +	  .val1		= _val1,					\
> +	  .val2		= _val2 }
> +
> +#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
> +	struct lm3533_als_attribute lm3533_als_attr_##_name =		  \
> +		ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)
> +
> +#define ALS_TARGET_ATTR_RW(_nr, _zone)					\
> +	LM3533_ALS_ATTR(target##_nr##_##_zone, S_IRUGO | S_IWUSR,	\
> +				show_als_attr, store_als_attr,		\
> +				LM3533_ATTR_TYPE_TARGET, _nr, _zone)
> +/*
> + * ALS Mapper targets
> + *
> + * target[1-3]_[0-4]		0-255
> + */
> +static ALS_TARGET_ATTR_RW(1, 0);
> +static ALS_TARGET_ATTR_RW(1, 1);
> +static ALS_TARGET_ATTR_RW(1, 2);
> +static ALS_TARGET_ATTR_RW(1, 3);
> +static ALS_TARGET_ATTR_RW(1, 4);
> +
> +static ALS_TARGET_ATTR_RW(2, 0);
> +static ALS_TARGET_ATTR_RW(2, 1);
> +static ALS_TARGET_ATTR_RW(2, 2);
> +static ALS_TARGET_ATTR_RW(2, 3);
> +static ALS_TARGET_ATTR_RW(2, 4);
> +
> +static ALS_TARGET_ATTR_RW(3, 0);
> +static ALS_TARGET_ATTR_RW(3, 1);
> +static ALS_TARGET_ATTR_RW(3, 2);
> +static ALS_TARGET_ATTR_RW(3, 3);
> +static ALS_TARGET_ATTR_RW(3, 4);
> +
> +#define ALS_THRESH_FALLING_ATTR_RW(_nr)					\
> +	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value,	\
> +			S_IRUGO | S_IWUSR,				\
> +			show_als_attr, store_als_attr,		\
> +			LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0)
> +
> +#define ALS_THRESH_RAISING_ATTR_RW(_nr)					\
> +	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value,	\
> +			S_IRUGO | S_IWUSR,				\
> +			show_als_attr, store_als_attr,			\
> +			LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0)
> +/*
> + * ALS Zone thresholds (boundaries)
> + *
> + * in_illuminance0_thresh[0-3]_falling_value	0-255
> + * in_illuminance0_thresh[0-3]_raising_value	0-255
> + */
> +static ALS_THRESH_FALLING_ATTR_RW(0);
> +static ALS_THRESH_FALLING_ATTR_RW(1);
> +static ALS_THRESH_FALLING_ATTR_RW(2);
> +static ALS_THRESH_FALLING_ATTR_RW(3);
> +
> +static ALS_THRESH_RAISING_ATTR_RW(0);
> +static ALS_THRESH_RAISING_ATTR_RW(1);
> +static ALS_THRESH_RAISING_ATTR_RW(2);
> +static ALS_THRESH_RAISING_ATTR_RW(3);
> +
> +#define ALS_HYSTERESIS_ATTR_RO(_nr)					\
> +	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis,	\
> +			S_IRUGO, show_als_attr, NULL,			\
> +			LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0)
> +/*
> + * ALS Zone threshold hysteresis
> + *
> + * threshY_hysteresis = threshY_raising - threshY_falling
> + *
> + * in_illuminance0_thresh[0-3]_hysteresis	0-255
> + * in_illuminance0_thresh[0-3]_hysteresis	0-255
> + */
> +static ALS_HYSTERESIS_ATTR_RO(0);
> +static ALS_HYSTERESIS_ATTR_RO(1);
> +static ALS_HYSTERESIS_ATTR_RO(2);
> +static ALS_HYSTERESIS_ATTR_RO(3);
> +
> +#define ILLUMINANCE_ATTR_RO(_name) \
> +	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
> +#define ILLUMINANCE_ATTR_RW(_name) \
> +	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
> +						show_##_name, store_##_name)
> +/*
> + * ALS Zone threshold-event enable
> + *
> + * in_illuminance0_thresh_either_en		0,1
> + */
> +static ILLUMINANCE_ATTR_RW(thresh_either_en);
> +
> +/*
> + * ALS Current Zone
> + *
> + * in_illuminance0_zone		0-4
> + */
> +static ILLUMINANCE_ATTR_RO(zone);
> +
> +/*
> + * ALS internal pull-down resistor select (analog mode)
> + *
> + * r_select	1-127
> + *
> + * R_als = 200000 / r_select (ohm)
> + */
> +static LM3533_ATTR_RW(r_select);
> +
> +static struct attribute *lm3533_als_event_attributes[] = {
> +	&dev_attr_in_illuminance0_thresh_either_en.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
Just to verify the hysteresis applies to bother thresh0_falling and
thresh0_rising?
> +	&lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
> +	NULL
> +};
> +
> +static struct attribute_group lm3533_als_event_attribute_group = {
> +	.attrs = lm3533_als_event_attributes
> +};
> +
> +static struct attribute *lm3533_als_attributes[] = {
> +	&dev_attr_r_select.attr,
Just to keep info in one place. We agreed in other branch
of thread that r_select would be done with platform data.

I wonder if we can make the naming a little clearer for these.. hmm.
> +	&lm3533_als_attr_target1_0.dev_attr.attr,
> +	&lm3533_als_attr_target1_1.dev_attr.attr,
> +	&lm3533_als_attr_target1_2.dev_attr.attr,
> +	&lm3533_als_attr_target1_3.dev_attr.attr,
> +	&lm3533_als_attr_target1_4.dev_attr.attr,
> +	&lm3533_als_attr_target2_0.dev_attr.attr,
> +	&lm3533_als_attr_target2_1.dev_attr.attr,
> +	&lm3533_als_attr_target2_2.dev_attr.attr,
> +	&lm3533_als_attr_target2_3.dev_attr.attr,
> +	&lm3533_als_attr_target2_4.dev_attr.attr,
> +	&lm3533_als_attr_target3_0.dev_attr.attr,
> +	&lm3533_als_attr_target3_1.dev_attr.attr,
> +	&lm3533_als_attr_target3_2.dev_attr.attr,
> +	&lm3533_als_attr_target3_3.dev_attr.attr,
> +	&lm3533_als_attr_target3_4.dev_attr.attr,
> +	&dev_attr_in_illuminance0_zone.attr,
> +	NULL
> +};
> +
> +static struct attribute_group lm3533_als_attribute_group = {
> +	.attrs = lm3533_als_attributes
> +};
> +
> +static int __devinit lm3533_als_set_input_mode(struct iio_dev *indio_dev,
> +								int pwm_mode)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
> +	u8 val;
Just use mask directly, why introduce val as well
(particularly as you don't use it ;)
> +	int ret;
> +
> +	if (pwm_mode)
> +		val = mask;	/* pwm input */
> +	else
> +		val = 0;	/* analog input */
> +
Why have val?
> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
> +	if (ret) {
> +		dev_err(&indio_dev->dev,
> +				"failed to set input mode %d\n", pwm_mode);
> +	}
> +
> +	return ret;
> +}
> +
> +static int __devinit lm3533_als_setup(struct iio_dev *indio_dev,
> +					struct lm3533_als_platform_data *pdata)
> +{
> +	int ret;
> +
> +	ret = lm3533_als_set_input_mode(indio_dev, pdata->pwm_mode);
> +	if (ret)
> +		return ret;
> +
> +	/* ALS input is always high impedance in PWM-mode. */
> +	if (!pdata->pwm_mode) {
> +		ret = lm3533_als_set_resistor(indio_dev, pdata->r_select);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
This enable / disable pair does rather look like it could
be combined into one function and save a few lines of repeated
code
> +static int __devinit lm3533_als_enable(struct iio_dev *indio_dev)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 mask = LM3533_ALS_ENABLE_MASK;
> +	int ret;
> +
> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
> +	if (ret)
> +		dev_err(&indio_dev->dev, "failed to enable ALS\n");
> +
> +	return ret;
> +}
> +
> +static int __devexit lm3533_als_disable(struct iio_dev *indio_dev)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 mask = LM3533_ALS_ENABLE_MASK;
> +	int ret;
> +
> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
> +	if (ret)
> +		dev_err(&indio_dev->dev, "failed to disable ALS\n");
> +
> +	return ret;
> +}
> +
> +static const struct iio_info lm3533_als_info = {
> +	.attrs		= &lm3533_als_attribute_group,
> +	.event_attrs	= &lm3533_als_event_attribute_group,
> +	.driver_module	= THIS_MODULE,
> +	.read_raw	= &lm3533_als_read_raw,
> +};
> +
> +static int __devinit lm3533_als_probe(struct platform_device *pdev)
> +{
> +	struct lm3533 *lm3533;
> +	struct lm3533_als_platform_data *pdata;
> +	struct lm3533_als *als;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +
> +	dev_dbg(&pdev->dev, "%s\n", __func__);
> +
> +	lm3533 = dev_get_drvdata(pdev->dev.parent);
> +	if (!lm3533)
> +		return -EINVAL;
> +
> +	pdata = pdev->dev.platform_data;
> +	if (!pdata) {
> +		dev_err(&pdev->dev, "no platform data\n");
> +		return -EINVAL;
> +	}
> +
> +	indio_dev = iio_device_alloc(sizeof(*als));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	indio_dev->info = &lm3533_als_info;
> +	indio_dev->channels = lm3533_als_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
> +	indio_dev->name = "lm3533-als";
> +	indio_dev->dev.parent = pdev->dev.parent;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	als = iio_priv(indio_dev);
> +	als->lm3533 = lm3533;
> +	als->irq = lm3533->irq;
> +	als->pwm_mode = pdata->pwm_mode;
> +	atomic_set(&als->zone, 0);
> +	mutex_init(&als->thresh_mutex);
> +
> +	if (als->irq) {
> +		ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
> +						IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> +						indio_dev->name, indio_dev);
> +		if (ret) {
> +			dev_err(&pdev->dev, "failed to request irq %d\n",
> +								als->irq);
> +			goto err_free_dev;
> +		}
> +	}
> +
> +	platform_set_drvdata(pdev, indio_dev);
> +
amongst other things this register does the creation of the
sysfs attributes.  Is there a race here if a read on one of
those occurs before the setup stuff below?

Normally I'd expect this call to the last one in the probe
function.  Why did you chose this ordering?
> +	ret = iio_device_register(indio_dev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to register ALS\n");
> +		goto err_free_irq;
> +	}
> +
> +	ret = lm3533_als_setup(indio_dev, pdata);
> +	if (ret)
> +		goto err_unregister;
> +
> +	ret = lm3533_als_enable(indio_dev);
> +	if (ret)
> +		goto err_unregister;
> +
> +	return 0;
> +
> +err_unregister:
> +	iio_device_unregister(indio_dev);
> +err_free_irq:
> +	if (als->irq)
> +		free_irq(als->irq, indio_dev);
> +err_free_dev:
> +	iio_device_free(indio_dev);
> +
> +	return ret;
> +}
> +
> +static int __devexit lm3533_als_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +
> +	dev_dbg(&pdev->dev, "%s\n", __func__);
> +
> +	lm3533_als_disable(indio_dev);
> +	iio_device_unregister(indio_dev);
> +	if (als->irq)
> +		free_irq(als->irq, indio_dev);
> +	iio_device_free(indio_dev);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver lm3533_als_driver = {
> +	.driver	= {
> +		.name	= "lm3533-als",
> +		.owner	= THIS_MODULE,
> +	},
> +	.probe		= lm3533_als_probe,
> +	.remove		= __devexit_p(lm3533_als_remove),
> +};
> +module_platform_driver(lm3533_als_driver);
> +
> +MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
> +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:lm3533-als");
> diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
> index 9660feb..594bc59 100644
> --- a/include/linux/mfd/lm3533.h
> +++ b/include/linux/mfd/lm3533.h
> @@ -43,6 +43,7 @@ struct lm3533_ctrlbank {
>  
>  struct lm3533_als_platform_data {
>  	unsigned pwm_mode:1;		/* PWM input mode (default analog) */
> +	u8 r_select;			/* 1 - 127 (ignored in PWM-mode) */
>  };
>  
>  struct lm3533_bl_platform_data {


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

* Re: [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-19 16:30           ` Johan Hovold
@ 2012-05-19 13:26             ` Jonathan Cameron
  0 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-19 13:26 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio

On 05/19/2012 05:30 PM, Johan Hovold wrote:
> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>> Add sub-driver for the ambient-light-sensor interface on National
>>> Semiconductor / TI LM3533 lighting power chips.
>>>
>>> The sensor interface can be used to control the LEDs and backlights of
>>> the chip through defining five light zones and three sets of
>>> corresponding brightness target levels.
>>>
>>> The driver provides raw and mean adc readings along with the current
>>> light zone through sysfs. A threshold event can be generated on zone
>>> changes.
>>
>> Hi Johan,
>>
>> I hate to be a pain with this one, but it's a complex beast and I'd
>> really like to get the interface right first time - particularly as
>> you are going in after the move out of staging.
>>
>>
>> Queries for you.
>> 1) Ordering in the probe function. Normally expect iio_device_register
>> to be the last call. Why not here?
>> 2) Worth combining enable / disable into one as very similar functions?
>> 3) Suspicious code in als_set_input_mode
> 
> Good points. See my answers inline below.
>  
>> Naming of the target values.  I think we can make the naming of these
>> fit in much better with the normal abi which is going to be all for the
>> good in the long run.  They are basically current output channels
>> with a controllable set of steps (where we don't have direct control
>> of which one we are in).  This is very similar to the frequency controls
>> on some of Analog's dds that we have a well defined interface for.
>>
>> More detail below, but in summary.
>>
>> out_currentX_currentY_raw for channel X value for zone Y.
> 
> Interesting. I'll have to think this through and get back to you.
> 
> Thanks for looking at this so quickly!
> 
> Johan
> 
>> Jonathan
>>>
>>> Signed-off-by: Johan Hovold <jhovold@gmail.com>
>>> ---
>>>
>>> Note that addition of r_select to the platform data probably needs to go
>>> in via mfd.
>>>
>>>
>>> v2:
>>>  - reimplement using iio
>>>  - add sysfs-ABI documentation
>>> v3
>>>  - use indexed channel
>>>  - fix sysfs-ABI documentation typo and style
>>>  - replace gain attribute with in_illuminance0_calibscale
>>>  - add calibscale to platform data
>>>  - fix adc register definitions
>>>  - replace to_lm3533_dev_attr macro with inline function
>>>  - fix device used for error reporting at irq allocation
>>>  - use iio device for error reporting during setup/enable
>>>  - rebase on staging-next
>>>    - fix header include paths
>>>    - use dev_to_iio_dev
>>>    - add IIO_CHAN_INFO_RAW to info mask
>>>    - use iio_device_{alloc,free}
>>> v4
>>>  - move to driver/iio/light
>>>  - add events/in_illuminance0_threshY_hysteresis attributes
>>>  - fix device zone-boundary quirkiness
>>>  - clean up attribute handling
>>>  - replace calibscale with device-specific r_select attribute
>>>
>>>
>>>  .../ABI/testing/sysfs-bus-iio-light-lm3533-als     |   64 ++
>>>  drivers/iio/Kconfig                                |    1 +
>>>  drivers/iio/Makefile                               |    1 +
>>>  drivers/iio/light/Kconfig                          |   22 +
>>>  drivers/iio/light/Makefile                         |    5 +
>>>  drivers/iio/light/lm3533-als.c                     |  941 ++++++++++++++++++++
>>>  include/linux/mfd/lm3533.h                         |    1 +
>>>  7 files changed, 1035 insertions(+), 0 deletions(-)
>>>  create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>  create mode 100644 drivers/iio/light/Kconfig
>>>  create mode 100644 drivers/iio/light/Makefile
>>>  create mode 100644 drivers/iio/light/lm3533-als.c
>>>
>>> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>> new file mode 100644
>>> index 0000000..7ea1770
>>> --- /dev/null
>>> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>> @@ -0,0 +1,64 @@
>>> +What:		/sys/bus/iio/devices/iio:deviceX/r_select
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold <jhovold@gmail.com>
>>> +Description:
>>> +		Set the ALS internal pull-down resistor for analog input mode
>>> +		(1..127), such that,
>>> +
>>> +		R_als = 200000 / r_select	(ohm)
>>> +
>>> +		This setting is ignored in PWM-mode (input is always high
>>> +		impedance in PWM-mode).
>>> +
>>> +What:		/sys/.../events/in_illuminance0_thresh_either_en
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold <jhovold@gmail.com>
>>> +Description:
>>> +		Event generated when channel passes one of the four thresholds
>>> +		in each direction (rising|falling) and a zone change occurs.
>>> +		The corresponding light zone can be read from
>>> +		in_illuminance0_zone.
>>> +
>>> +What:		/sys/.../events/in_illuminance0_threshY_hysteresis
>>> +Date:		May 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold <jhovold@gmail.com>
>>> +Description:
>>> +		Get the hysteresis for thresholds Y, that is,
>>> +
>>> +		threshY_hysteresis = threshY_raising - threshY_falling
>>> +
>>> +What:		/sys/.../events/illuminance_threshY_falling_value
>>> +What:		/sys/.../events/illuminance_threshY_raising_value
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold <jhovold@gmail.com>
>>> +Description:
>>> +		Specifies the value of threshold that the device is
>>> +		comparing against for the events enabled by
>>> +		in_illuminance0_thresh_either_en, where Y in 0..3.
>>> +
>>> +		Note that threshY_falling must be less than or equal to
>>> +		threshY_raising.
>>> +
>>> +		These thresholds correspond to the eight zone-boundary
>>> +		registers (boundaryY_{low,high}) and defines the five light
>>> +		zones.
>>> +
>>> +What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold <jhovold@gmail.com>
>>> +Description:
>>> +		Get the current light zone (0..4) as defined by the
>>> +		in_illuminance0_threshY_{falling,rising} thresholds.
>>> +
>>> +What:		/sys/bus/iio/devices/iio:deviceX/targetY_Z
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold <jhovold@gmail.com>
>>> +Description:
>>> +		Set the target brightness for ALS-mapper Y in light zone Z
>>> +		(0..255), where Y in 1..3 and Z in 0..4.
>>
>> What are the units of this?  Also arguably is it not the als that this
>> is related to, but rather the light source?  A quick datasheet browse says
>> that these are current targets? If so I wonder if we can make that
>> explicit...  Could treat them as 3 output channels and have indexed values
>> like we do for frequencies in dds devices (where external hardware is
>> controlling them.
>>
>> Hmm. lets see.
>>
>> out_currentX_currentY_raw
>> (the double naming is a bit clunky but corresponds to frequency devices
>> where we have
>> out_altvoltageX_frequencyY_raw
>>
>> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
>> as indexed values they can take 0,1,2,3,4
>> out_currentX_raw is not read only and gives you the current for whichever
>> zone the device is currently in.
>>
>> This may seem convoluted but I'd really rather have something generalizable
>> for this if we possibly can.  We'd still need documentation to say what is
>> controlling these, but at least they'd fit within our more general abi.
>>
>> What do you think?
> 
> I'll get back to you on this shortly.
>  
> [...]
> 
>>> +struct lm3533_als {
>>> +	struct lm3533 *lm3533;
>>> +
>>> +	unsigned long flags;
>>> +	int irq;
>>> +
>> Boolean might be better as it's not a though this will save
>> space!
> 
> I've used bit fields consistently for such flags in lm3533, although the
> type below should have been unsigned. The space required is the same
> either way in this case. I'll go with bool.
indeed it should be unsigned. It's fine if you want to keep it
as a bitfield for consistency (as good an arguement as any!)
> 
>>> +	int pwm_mode:1;
>>> +
>>> +	atomic_t zone;
>>> +	struct mutex thresh_mutex;
>>> +};
>> Rarely a reason for more than one blank line in my opinion...
> 
> Now you're picky. :)
> 
> Separating defines, declarations and definitions using an additional
> blank line should be pretty uncontroversial. But again, if you insist,
> I'll drop it.
I'm not insisting (hence opinion bit ;)
> 
>>> +
>>> +
>> May be roll this into it's one call site. will make for marginally
>> less code I think..
> 
> I did before, but separated it out when I added calibscale. I prefer to
> keep it separate for readability reasons (especially if we're going to
> add output channels).
fair enough.
> 
>>> +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
>>> +								int *adc)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	u8 reg;
>>> +	u8 val;
>>> +	int ret;
>>> +
>>> +	if (average)
>>> +		reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
>>> +	else
>>> +		reg = LM3533_REG_ALS_READ_ADC_RAW;
>>> +
>>> +	ret = lm3533_read(als->lm3533, reg, &val);
>>> +	if (ret) {
>>> +		dev_err(&indio_dev->dev, "failed to read adc\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	*adc = val;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int lm3533_als_read_raw(struct iio_dev *indio_dev,
>>> +				struct iio_chan_spec const *chan,
>>> +				int *val, int *val2, long mask)
>>> +{
>>> +	int ret;
>>> +
>>> +	switch (mask) {
>>> +	case 0:
>>> +		ret = lm3533_als_get_adc(indio_dev, false, val);
>>> +		break;
>>> +	case IIO_CHAN_INFO_AVERAGE_RAW:
>>> +		ret = lm3533_als_get_adc(indio_dev, true, val);
>>> +		break;
>>> +	default:
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	return IIO_VAL_INT;
>>> +}
>>> +
>> Why have an array?  Just use the address and set the num_channels = 1;
> 
> For consistency. If you ever add channels you'd need to turn it into an
> array again anyway (and we are considering output channels now).
> 
> This is a pretty common practise for example in board files.
fair enough
> 
>>> +static const struct iio_chan_spec lm3533_als_channels[] = {
>>> +	{
>>> +		.type		= IIO_LIGHT,
>>> +		.channel	= 0,
>>> +		.indexed	= 1,
>>> +		.info_mask	= (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
>>> +				   IIO_CHAN_INFO_RAW_SEPARATE_BIT),
>>> +	}
>>> +};
>>> +
>>> +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	u8 val;
>>> +	int ret;
>>> +
>>> +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
>>> +	if (ret) {
>>> +		dev_err(&indio_dev->dev, "failed to read zone\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
>>> +	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
>>> +{
>>> +
>>> +	struct iio_dev *indio_dev = dev_id;
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	u8 zone;
>>> +	int ret;
>>> +
>>> +	/* Clear interrupt by reading the ALS zone register. */
>>> +	ret = lm3533_als_get_zone(indio_dev, &zone);
>>> +	if (ret)
>>> +		goto out;
>>> +
>>> +	atomic_set(&als->zone, zone);
>>> +
>>> +	iio_push_event(indio_dev,
>>> +		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
>>> +					    0,
>>> +					    IIO_EV_TYPE_THRESH,
>>> +					    IIO_EV_DIR_EITHER),
>>> +		       iio_get_time_ns());
>>> +out:
>>> +	return IRQ_HANDLED;
>>> +}
>>> +
>> could just roll this into the one call point, it's not exactly complex.
> 
> For readability and consistency reasons I'd like to keep it separate. If
> you look at the call point in store_thresh_either_en it really makes
> sense to break this one out. The consistency argument is that most
> register accesses have their dedicated set/get functions.
> 
> [ The reordering of probe / remove discussed below will also introduce
>   a second call point. ]
> 
>>> +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
>>> +	u8 val;
>>> +	int ret;
>>> +
>>> +	if (enable)
>>> +		val = mask;
>>> +	else
>>> +		val = 0;
>>> +
>>> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
>>> +	if (ret) {
>>> +		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
>>> +								enable);
>>> +		return ret;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
>>> +	u8 val;
>>> +	int ret;
>>> +
>>> +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
>>> +	if (ret) {
>>> +		dev_err(&indio_dev->dev, "failed to get int mode\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	*enable = !!(val & mask);
>>> +
>>> +	return 0;
>>> +}
>>> +
>> Given only accessed from one place, why not just roll it in there?
> 
> I've dropped this one completely along with the r_select sysfs attribute
> as it is now write only (from platform data).
> 
>>> +static int lm3533_als_get_resistor(struct iio_dev *indio_dev, u8 *val)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	int ret;
>>> +
>>> +	if (als->pwm_mode) {
>>> +		*val = 0;		/* always high impedance */
>>> +		return 0;
>>> +	}
>>> +
>>> +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
>>> +	if (ret) {
>>> +		dev_err(&indio_dev->dev, "failed to get resistor\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int lm3533_als_set_resistor(struct iio_dev *indio_dev, u8 val)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	int ret;
>>> +
>>> +	if (als->pwm_mode)
>>> +		return -EPERM;		/* always high impedance */
>>> +
>>> +	if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX)
>>> +		return -EINVAL;
>>> +
>>> +	ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
>>> +	if (ret) {
>>> +		dev_err(&indio_dev->dev, "failed to set resistor\n");
>>> +		return ret;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
> 
> [...]
> 
>>> +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned nr,
>>> +							unsigned zone, u8 val)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	u8 reg;
>>> +	int ret;
>>> +
>>> +	if (nr < LM3533_ALS_MAPPER_MIN || nr > LM3533_ALS_MAPPER_MAX)
>>> +		return -EINVAL;
>>> +
>>> +	if (zone > LM3533_ALS_ZONE_MAX)
>>> +		return -EINVAL;
>>> +
>>> +	reg = lm3533_als_get_target_reg(nr, zone);
>>> +	ret = lm3533_write(als->lm3533, reg, val);
>> I wouldn't bother with the intermediate  but up to you...
>>
>> ret = lm3533_write(als->lm3533, lm3533_als_get_target_reg(nr, zone), val);
> 
> I prefer the expanded form.
> 
>>> +	if (ret)
>>> +		dev_err(&indio_dev->dev, "failed to set target brightness\n");
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
>>> +{
>>> +	u8 offset;
>>> +
>> offset = !raising;
> 
> Ok.
> 
>>> +	if (raising)
>>> +		offset = 0;
>>> +	else
>>> +		offset = 1;
>>> +
>>> +	return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
>>> +}
>>> +
>>> +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
>>> +							bool raising, u8 *val)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	u8 reg;
>>> +	int ret;
>>> +
>>> +	if (nr > LM3533_ALS_THRESH_MAX)
>>> +		return -EINVAL;
>>> +
>>> +	reg = lm3533_als_get_threshold_reg(nr, raising);
>>> +	ret = lm3533_read(als->lm3533, reg, val);
>>> +	if (ret)
>>> +		dev_err(&indio_dev->dev, "failed to get threshold\n");
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
>>> +							bool raising, u8 val)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	u8 val2;
>>> +	u8 reg;
>>> +	u8 reg2;
>> u8 val2, reg, reg2; Shorter and still obvious.
> 
> I try to avoid doing multiple declarations on a single line, but in this
> case, perhaps
> 
> 	u8 val2;
> 	u8 reg, reg2;
> 
> would be consistent with the rest of the code.
> 
>>> +	int ret;
>>> +
>>> +	if (nr > LM3533_ALS_THRESH_MAX)
>>> +		return -EINVAL;
>>> +
>>> +	reg = lm3533_als_get_threshold_reg(nr, raising);
>>> +	reg2 = lm3533_als_get_threshold_reg(nr, !raising);
>>> +
>>> +	mutex_lock(&als->thresh_mutex);
>>> +	ret = lm3533_read(als->lm3533, reg2, &val2);
>>> +	if (ret) {
>>> +		dev_err(&indio_dev->dev, "failed to get threshold\n");
>>> +		goto out;
>>> +	}
>>> +	/*
>>> +	 * This device does not allow negative hysteresis (in fact, it uses
>>> +	 * whichever value is smaller as the lower bound) so we need to make
>>> +	 * sure that thresh_falling <= thresh_raising.
>>> +	 */
>>> +	if ((raising && (val < val2)) || (!raising && (val > val2))) {
>>> +		ret = -EINVAL;
>>> +		goto out;
>>> +	}
>>> +
>>> +	ret = lm3533_write(als->lm3533, reg, val);
>>> +	if (ret) {
>>> +		dev_err(&indio_dev->dev, "failed to set threshold\n");
>>> +		goto out;
>>> +	}
>>> +out:
>>> +	mutex_unlock(&als->thresh_mutex);
>>> +
>>> +	return ret;
>>> +}
> 
> [...]
> 
>>> +static ssize_t store_als_attr(struct device *dev,
>>> +					struct device_attribute *attr,
>>> +					const char *buf, size_t len)
>>> +{
>>> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
>>> +	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
>>> +	u8 val;
>>> +	int ret;
>>> +
>>> +	if (kstrtou8(buf, 0, &val))
>>> +		return -EINVAL;
>>> +
>>> +	switch (als_attr->type) {
>>> +	case LM3533_ATTR_TYPE_TARGET:
>>> +		ret = lm3533_als_set_target(indio_dev, als_attr->val1,
>>> +							als_attr->val2, val);
>>> +		break;
>>> +	case LM3533_ATTR_TYPE_THRESH_FALLING:
>>> +		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
>>> +								false, val);
>>> +		break;
>>> +	case LM3533_ATTR_TYPE_THRESH_RAISING:
>>> +		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
>>> +								true, val);
>>> +		break;
>>> +	default:
>> I'd be tempted to drop this. It is easy to verify whether it will occur.
> 
> Drop the WARN and simply return -ENXIO, you mean?
yup
> 
>>> +		WARN(1, "%s - bad attribute type %d\n", __func__,
>>> +							als_attr->type);
>>> +		ret = -ENXIO;
>>> +	}
>>> +
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	return len;
>>> +}
> 
> [...]
> 
>>> +static struct attribute *lm3533_als_event_attributes[] = {
>>> +	&dev_attr_in_illuminance0_thresh_either_en.attr,
>>> +	&lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
>>> +	&lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
>> Just to verify the hysteresis applies to bother thresh0_falling and
>> thresh0_rising?
> 
> Yes, we have the following situation (just to reiterate):
> 
> 	...
> 		zone 1
> 
> 	thresh0_raising		52
> 
> 	thresh0_falling		50
> 
> 		zone 0
> 
> If the ALS is in zone 0 and the (8-bit) average input raises above
> thresh0_raising (52) it enters zone 1. The ALS will not re-enter zone 0 until
> the input drops below thresh0_falling (50). The hysteresis is the
> difference between the two thresholds, that is, 52 - 50 = 2 in this
> case.
> 
>>> +	&lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
>>> +	&lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
>>> +	&lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
>>> +	&lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
>>> +	&lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
>>> +	&lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
>>> +	&lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
>>> +	&lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
>>> +	&lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
>>> +	&lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
>>> +	NULL
>>> +};
>>> +
>>> +static struct attribute_group lm3533_als_event_attribute_group = {
>>> +	.attrs = lm3533_als_event_attributes
>>> +};
>>> +
>>> +static struct attribute *lm3533_als_attributes[] = {
>>> +	&dev_attr_r_select.attr,
>> Just to keep info in one place. We agreed in other branch
>> of thread that r_select would be done with platform data.
> 
> Indeed. I've dropped it from sysfs.
> 
>> I wonder if we can make the naming a little clearer for these.. hmm.
>>> +	&lm3533_als_attr_target1_0.dev_attr.attr,
>>> +	&lm3533_als_attr_target1_1.dev_attr.attr,
>>> +	&lm3533_als_attr_target1_2.dev_attr.attr,
>>> +	&lm3533_als_attr_target1_3.dev_attr.attr,
>>> +	&lm3533_als_attr_target1_4.dev_attr.attr,
>>> +	&lm3533_als_attr_target2_0.dev_attr.attr,
>>> +	&lm3533_als_attr_target2_1.dev_attr.attr,
>>> +	&lm3533_als_attr_target2_2.dev_attr.attr,
>>> +	&lm3533_als_attr_target2_3.dev_attr.attr,
>>> +	&lm3533_als_attr_target2_4.dev_attr.attr,
>>> +	&lm3533_als_attr_target3_0.dev_attr.attr,
>>> +	&lm3533_als_attr_target3_1.dev_attr.attr,
>>> +	&lm3533_als_attr_target3_2.dev_attr.attr,
>>> +	&lm3533_als_attr_target3_3.dev_attr.attr,
>>> +	&lm3533_als_attr_target3_4.dev_attr.attr,
>>> +	&dev_attr_in_illuminance0_zone.attr,
>>> +	NULL
>>> +};
>>> +
>>> +static struct attribute_group lm3533_als_attribute_group = {
>>> +	.attrs = lm3533_als_attributes
>>> +};
>>> +
>>> +static int __devinit lm3533_als_set_input_mode(struct iio_dev *indio_dev,
>>> +								int pwm_mode)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
>>> +	u8 val;
>> Just use mask directly, why introduce val as well
>> (particularly as you don't use it ;)
>>> +	int ret;
>>> +
>>> +	if (pwm_mode)
>>> +		val = mask;	/* pwm input */
>>> +	else
>>> +		val = 0;	/* analog input */
>>> +
>> Why have val?
>>> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
> 
> This is supposed to read lm3533_update(..., val, mask). Thanks for
> catching this.
> 
>>> +	if (ret) {
>>> +		dev_err(&indio_dev->dev,
>>> +				"failed to set input mode %d\n", pwm_mode);
>>> +	}
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int __devinit lm3533_als_setup(struct iio_dev *indio_dev,
>>> +					struct lm3533_als_platform_data *pdata)
>>> +{
>>> +	int ret;
>>> +
>>> +	ret = lm3533_als_set_input_mode(indio_dev, pdata->pwm_mode);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	/* ALS input is always high impedance in PWM-mode. */
>>> +	if (!pdata->pwm_mode) {
>>> +		ret = lm3533_als_set_resistor(indio_dev, pdata->r_select);
>>> +		if (ret)
>>> +			return ret;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>> This enable / disable pair does rather look like it could
>> be combined into one function and save a few lines of repeated
>> code
> 
> They could but at the expense of some readability (als_enable(...,
> false) rather than als_disable(...)) and really not much gain in terms
> of fewer line of code as you'd need to add conditionals both for the
> register value and error message.
> 
> This is the style I've used in the other drivers so if you don't mind
> terribly, I'd suggest not merging them if only for consistency.
I don't really care. Just like to kill off code duplication.
> 
>>> +static int __devinit lm3533_als_enable(struct iio_dev *indio_dev)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	u8 mask = LM3533_ALS_ENABLE_MASK;
>>> +	int ret;
>>> +
>>> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
>>> +	if (ret)
>>> +		dev_err(&indio_dev->dev, "failed to enable ALS\n");
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int __devexit lm3533_als_disable(struct iio_dev *indio_dev)
>>> +{
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +	u8 mask = LM3533_ALS_ENABLE_MASK;
>>> +	int ret;
>>> +
>>> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
>>> +	if (ret)
>>> +		dev_err(&indio_dev->dev, "failed to disable ALS\n");
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static const struct iio_info lm3533_als_info = {
>>> +	.attrs		= &lm3533_als_attribute_group,
>>> +	.event_attrs	= &lm3533_als_event_attribute_group,
>>> +	.driver_module	= THIS_MODULE,
>>> +	.read_raw	= &lm3533_als_read_raw,
>>> +};
>>> +
>>> +static int __devinit lm3533_als_probe(struct platform_device *pdev)
>>> +{
>>> +	struct lm3533 *lm3533;
>>> +	struct lm3533_als_platform_data *pdata;
>>> +	struct lm3533_als *als;
>>> +	struct iio_dev *indio_dev;
>>> +	int ret;
>>> +
>>> +	dev_dbg(&pdev->dev, "%s\n", __func__);
>>> +
>>> +	lm3533 = dev_get_drvdata(pdev->dev.parent);
>>> +	if (!lm3533)
>>> +		return -EINVAL;
>>> +
>>> +	pdata = pdev->dev.platform_data;
>>> +	if (!pdata) {
>>> +		dev_err(&pdev->dev, "no platform data\n");
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	indio_dev = iio_device_alloc(sizeof(*als));
>>> +	if (!indio_dev)
>>> +		return -ENOMEM;
>>> +
>>> +	indio_dev->info = &lm3533_als_info;
>>> +	indio_dev->channels = lm3533_als_channels;
>>> +	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
>>> +	indio_dev->name = "lm3533-als";
>>> +	indio_dev->dev.parent = pdev->dev.parent;
>>> +	indio_dev->modes = INDIO_DIRECT_MODE;
>>> +
>>> +	als = iio_priv(indio_dev);
>>> +	als->lm3533 = lm3533;
>>> +	als->irq = lm3533->irq;
>>> +	als->pwm_mode = pdata->pwm_mode;
>>> +	atomic_set(&als->zone, 0);
>>> +	mutex_init(&als->thresh_mutex);
>>> +
>>> +	if (als->irq) {
>>> +		ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
>>> +						IRQF_TRIGGER_LOW | IRQF_ONESHOT,
>>> +						indio_dev->name, indio_dev);
>>> +		if (ret) {
>>> +			dev_err(&pdev->dev, "failed to request irq %d\n",
>>> +								als->irq);
>>> +			goto err_free_dev;
>>> +		}
>>> +	}
>>> +
>>> +	platform_set_drvdata(pdev, indio_dev);
>>> +
>> amongst other things this register does the creation of the
>> sysfs attributes.  Is there a race here if a read on one of
>> those occurs before the setup stuff below?
> 
> The enable call simply enables the adc so, for example, target or
> threshold values could be updated before. Worst case is that you get a
> zero reading from the adc before the adc is enabled. The zone algorithm
> takes some time to settle either way.
> 
>> Normally I'd expect this call to the last one in the probe
>> function.  Why did you chose this ordering?
> 
> The main reason in this case was to be able to use the iio device for
> error reporting. The setup function called set_resistor which was also
> possible to set from sysfs (and for consistency all error reporting
> after probe is done using the iio device).
fair enough on the error reporting. This is the one fiddly corner.
Personally I verge in the direction of far fewer error messages
partly because I'm forever trying to unify drivers and it's a lot
easier if you don't have lots of error messages about!
> 
> But since we've now dropped the r_select attribute, I could use the
> platform device for reporting all errors during setup and make sure the
> iio device is registered last. I'll do this.
> 
>>> +	ret = iio_device_register(indio_dev);
>>> +	if (ret) {
>>> +		dev_err(&pdev->dev, "failed to register ALS\n");
>>> +		goto err_free_irq;
>>> +	}
>>> +
>>> +	ret = lm3533_als_setup(indio_dev, pdata);
>>> +	if (ret)
>>> +		goto err_unregister;
>>> +
>>> +	ret = lm3533_als_enable(indio_dev);
>>> +	if (ret)
>>> +		goto err_unregister;
>>> +
>>> +	return 0;
>>> +
>>> +err_unregister:
>>> +	iio_device_unregister(indio_dev);
>>> +err_free_irq:
>>> +	if (als->irq)
>>> +		free_irq(als->irq, indio_dev);
>>> +err_free_dev:
>>> +	iio_device_free(indio_dev);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int __devexit lm3533_als_remove(struct platform_device *pdev)
>>> +{
>>> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>>> +	struct lm3533_als *als = iio_priv(indio_dev);
>>> +
>>> +	dev_dbg(&pdev->dev, "%s\n", __func__);
>>> +
>>> +	lm3533_als_disable(indio_dev);
>>> +	iio_device_unregister(indio_dev);
>>> +	if (als->irq)
>>> +		free_irq(als->irq, indio_dev);
>>> +	iio_device_free(indio_dev);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static struct platform_driver lm3533_als_driver = {
>>> +	.driver	= {
>>> +		.name	= "lm3533-als",
>>> +		.owner	= THIS_MODULE,
>>> +	},
>>> +	.probe		= lm3533_als_probe,
>>> +	.remove		= __devexit_p(lm3533_als_remove),
>>> +};
>>> +module_platform_driver(lm3533_als_driver);
>>> +
>>> +MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
>>> +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
>>> +MODULE_LICENSE("GPL");
>>> +MODULE_ALIAS("platform:lm3533-als");
>>> diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
>>> index 9660feb..594bc59 100644
>>> --- a/include/linux/mfd/lm3533.h
>>> +++ b/include/linux/mfd/lm3533.h
>>> @@ -43,6 +43,7 @@ struct lm3533_ctrlbank {
>>>  
>>>  struct lm3533_als_platform_data {
>>>  	unsigned pwm_mode:1;		/* PWM input mode (default analog) */
>>> +	u8 r_select;			/* 1 - 127 (ignored in PWM-mode) */
>>>  };
>>>  
>>>  struct lm3533_bl_platform_data {
>>


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

* Re: [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-19  8:48         ` Jonathan Cameron
@ 2012-05-19 16:30           ` Johan Hovold
  2012-05-19 13:26             ` Jonathan Cameron
  2012-05-21  9:50           ` Johan Hovold
  1 sibling, 1 reply; 131+ messages in thread
From: Johan Hovold @ 2012-05-19 16:30 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Johan Hovold, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio

On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
> On 05/18/2012 02:07 PM, Johan Hovold wrote:
> > Add sub-driver for the ambient-light-sensor interface on National
> > Semiconductor / TI LM3533 lighting power chips.
> > 
> > The sensor interface can be used to control the LEDs and backlights of
> > the chip through defining five light zones and three sets of
> > corresponding brightness target levels.
> > 
> > The driver provides raw and mean adc readings along with the current
> > light zone through sysfs. A threshold event can be generated on zone
> > changes.
> 
> Hi Johan,
> 
> I hate to be a pain with this one, but it's a complex beast and I'd
> really like to get the interface right first time - particularly as
> you are going in after the move out of staging.
> 
> 
> Queries for you.
> 1) Ordering in the probe function. Normally expect iio_device_register
> to be the last call. Why not here?
> 2) Worth combining enable / disable into one as very similar functions?
> 3) Suspicious code in als_set_input_mode

Good points. See my answers inline below.
 
> Naming of the target values.  I think we can make the naming of these
> fit in much better with the normal abi which is going to be all for the
> good in the long run.  They are basically current output channels
> with a controllable set of steps (where we don't have direct control
> of which one we are in).  This is very similar to the frequency controls
> on some of Analog's dds that we have a well defined interface for.
> 
> More detail below, but in summary.
> 
> out_currentX_currentY_raw for channel X value for zone Y.

Interesting. I'll have to think this through and get back to you.

Thanks for looking at this so quickly!

Johan

> Jonathan
> > 
> > Signed-off-by: Johan Hovold <jhovold@gmail.com>
> > ---
> > 
> > Note that addition of r_select to the platform data probably needs to go
> > in via mfd.
> > 
> > 
> > v2:
> >  - reimplement using iio
> >  - add sysfs-ABI documentation
> > v3
> >  - use indexed channel
> >  - fix sysfs-ABI documentation typo and style
> >  - replace gain attribute with in_illuminance0_calibscale
> >  - add calibscale to platform data
> >  - fix adc register definitions
> >  - replace to_lm3533_dev_attr macro with inline function
> >  - fix device used for error reporting at irq allocation
> >  - use iio device for error reporting during setup/enable
> >  - rebase on staging-next
> >    - fix header include paths
> >    - use dev_to_iio_dev
> >    - add IIO_CHAN_INFO_RAW to info mask
> >    - use iio_device_{alloc,free}
> > v4
> >  - move to driver/iio/light
> >  - add events/in_illuminance0_threshY_hysteresis attributes
> >  - fix device zone-boundary quirkiness
> >  - clean up attribute handling
> >  - replace calibscale with device-specific r_select attribute
> > 
> > 
> >  .../ABI/testing/sysfs-bus-iio-light-lm3533-als     |   64 ++
> >  drivers/iio/Kconfig                                |    1 +
> >  drivers/iio/Makefile                               |    1 +
> >  drivers/iio/light/Kconfig                          |   22 +
> >  drivers/iio/light/Makefile                         |    5 +
> >  drivers/iio/light/lm3533-als.c                     |  941 ++++++++++++++++++++
> >  include/linux/mfd/lm3533.h                         |    1 +
> >  7 files changed, 1035 insertions(+), 0 deletions(-)
> >  create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >  create mode 100644 drivers/iio/light/Kconfig
> >  create mode 100644 drivers/iio/light/Makefile
> >  create mode 100644 drivers/iio/light/lm3533-als.c
> > 
> > diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > new file mode 100644
> > index 0000000..7ea1770
> > --- /dev/null
> > +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > @@ -0,0 +1,64 @@
> > +What:		/sys/bus/iio/devices/iio:deviceX/r_select
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Set the ALS internal pull-down resistor for analog input mode
> > +		(1..127), such that,
> > +
> > +		R_als = 200000 / r_select	(ohm)
> > +
> > +		This setting is ignored in PWM-mode (input is always high
> > +		impedance in PWM-mode).
> > +
> > +What:		/sys/.../events/in_illuminance0_thresh_either_en
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Event generated when channel passes one of the four thresholds
> > +		in each direction (rising|falling) and a zone change occurs.
> > +		The corresponding light zone can be read from
> > +		in_illuminance0_zone.
> > +
> > +What:		/sys/.../events/in_illuminance0_threshY_hysteresis
> > +Date:		May 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Get the hysteresis for thresholds Y, that is,
> > +
> > +		threshY_hysteresis = threshY_raising - threshY_falling
> > +
> > +What:		/sys/.../events/illuminance_threshY_falling_value
> > +What:		/sys/.../events/illuminance_threshY_raising_value
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Specifies the value of threshold that the device is
> > +		comparing against for the events enabled by
> > +		in_illuminance0_thresh_either_en, where Y in 0..3.
> > +
> > +		Note that threshY_falling must be less than or equal to
> > +		threshY_raising.
> > +
> > +		These thresholds correspond to the eight zone-boundary
> > +		registers (boundaryY_{low,high}) and defines the five light
> > +		zones.
> > +
> > +What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Get the current light zone (0..4) as defined by the
> > +		in_illuminance0_threshY_{falling,rising} thresholds.
> > +
> > +What:		/sys/bus/iio/devices/iio:deviceX/targetY_Z
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Set the target brightness for ALS-mapper Y in light zone Z
> > +		(0..255), where Y in 1..3 and Z in 0..4.
> 
> What are the units of this?  Also arguably is it not the als that this
> is related to, but rather the light source?  A quick datasheet browse says
> that these are current targets? If so I wonder if we can make that
> explicit...  Could treat them as 3 output channels and have indexed values
> like we do for frequencies in dds devices (where external hardware is
> controlling them.
> 
> Hmm. lets see.
> 
> out_currentX_currentY_raw
> (the double naming is a bit clunky but corresponds to frequency devices
> where we have
> out_altvoltageX_frequencyY_raw
> 
> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
> as indexed values they can take 0,1,2,3,4
> out_currentX_raw is not read only and gives you the current for whichever
> zone the device is currently in.
> 
> This may seem convoluted but I'd really rather have something generalizable
> for this if we possibly can.  We'd still need documentation to say what is
> controlling these, but at least they'd fit within our more general abi.
> 
> What do you think?

I'll get back to you on this shortly.
 
[...]

> > +struct lm3533_als {
> > +	struct lm3533 *lm3533;
> > +
> > +	unsigned long flags;
> > +	int irq;
> > +
> Boolean might be better as it's not a though this will save
> space!

I've used bit fields consistently for such flags in lm3533, although the
type below should have been unsigned. The space required is the same
either way in this case. I'll go with bool.

> > +	int pwm_mode:1;
> > +
> > +	atomic_t zone;
> > +	struct mutex thresh_mutex;
> > +};
> Rarely a reason for more than one blank line in my opinion...

Now you're picky. :)

Separating defines, declarations and definitions using an additional
blank line should be pretty uncontroversial. But again, if you insist,
I'll drop it.

> > +
> > +
> May be roll this into it's one call site. will make for marginally
> less code I think..

I did before, but separated it out when I added calibscale. I prefer to
keep it separate for readability reasons (especially if we're going to
add output channels).

> > +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
> > +								int *adc)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 reg;
> > +	u8 val;
> > +	int ret;
> > +
> > +	if (average)
> > +		reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
> > +	else
> > +		reg = LM3533_REG_ALS_READ_ADC_RAW;
> > +
> > +	ret = lm3533_read(als->lm3533, reg, &val);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to read adc\n");
> > +		return ret;
> > +	}
> > +
> > +	*adc = val;
> > +
> > +	return 0;
> > +}
> > +
> > +static int lm3533_als_read_raw(struct iio_dev *indio_dev,
> > +				struct iio_chan_spec const *chan,
> > +				int *val, int *val2, long mask)
> > +{
> > +	int ret;
> > +
> > +	switch (mask) {
> > +	case 0:
> > +		ret = lm3533_als_get_adc(indio_dev, false, val);
> > +		break;
> > +	case IIO_CHAN_INFO_AVERAGE_RAW:
> > +		ret = lm3533_als_get_adc(indio_dev, true, val);
> > +		break;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (ret)
> > +		return ret;
> > +
> > +	return IIO_VAL_INT;
> > +}
> > +
> Why have an array?  Just use the address and set the num_channels = 1;

For consistency. If you ever add channels you'd need to turn it into an
array again anyway (and we are considering output channels now).

This is a pretty common practise for example in board files.

> > +static const struct iio_chan_spec lm3533_als_channels[] = {
> > +	{
> > +		.type		= IIO_LIGHT,
> > +		.channel	= 0,
> > +		.indexed	= 1,
> > +		.info_mask	= (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
> > +				   IIO_CHAN_INFO_RAW_SEPARATE_BIT),
> > +	}
> > +};
> > +
> > +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 val;
> > +	int ret;
> > +
> > +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to read zone\n");
> > +		return ret;
> > +	}
> > +
> > +	val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
> > +	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
> > +
> > +	return 0;
> > +}
> > +
> > +static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
> > +{
> > +
> > +	struct iio_dev *indio_dev = dev_id;
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 zone;
> > +	int ret;
> > +
> > +	/* Clear interrupt by reading the ALS zone register. */
> > +	ret = lm3533_als_get_zone(indio_dev, &zone);
> > +	if (ret)
> > +		goto out;
> > +
> > +	atomic_set(&als->zone, zone);
> > +
> > +	iio_push_event(indio_dev,
> > +		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
> > +					    0,
> > +					    IIO_EV_TYPE_THRESH,
> > +					    IIO_EV_DIR_EITHER),
> > +		       iio_get_time_ns());
> > +out:
> > +	return IRQ_HANDLED;
> > +}
> > +
> could just roll this into the one call point, it's not exactly complex.

For readability and consistency reasons I'd like to keep it separate. If
you look at the call point in store_thresh_either_en it really makes
sense to break this one out. The consistency argument is that most
register accesses have their dedicated set/get functions.

[ The reordering of probe / remove discussed below will also introduce
  a second call point. ]

> > +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> > +	u8 val;
> > +	int ret;
> > +
> > +	if (enable)
> > +		val = mask;
> > +	else
> > +		val = 0;
> > +
> > +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
> > +								enable);
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> > +	u8 val;
> > +	int ret;
> > +
> > +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to get int mode\n");
> > +		return ret;
> > +	}
> > +
> > +	*enable = !!(val & mask);
> > +
> > +	return 0;
> > +}
> > +
> Given only accessed from one place, why not just roll it in there?

I've dropped this one completely along with the r_select sysfs attribute
as it is now write only (from platform data).

> > +static int lm3533_als_get_resistor(struct iio_dev *indio_dev, u8 *val)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	int ret;
> > +
> > +	if (als->pwm_mode) {
> > +		*val = 0;		/* always high impedance */
> > +		return 0;
> > +	}
> > +
> > +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to get resistor\n");
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int lm3533_als_set_resistor(struct iio_dev *indio_dev, u8 val)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	int ret;
> > +
> > +	if (als->pwm_mode)
> > +		return -EPERM;		/* always high impedance */
> > +
> > +	if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX)
> > +		return -EINVAL;
> > +
> > +	ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to set resistor\n");
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}

[...]

> > +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned nr,
> > +							unsigned zone, u8 val)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 reg;
> > +	int ret;
> > +
> > +	if (nr < LM3533_ALS_MAPPER_MIN || nr > LM3533_ALS_MAPPER_MAX)
> > +		return -EINVAL;
> > +
> > +	if (zone > LM3533_ALS_ZONE_MAX)
> > +		return -EINVAL;
> > +
> > +	reg = lm3533_als_get_target_reg(nr, zone);
> > +	ret = lm3533_write(als->lm3533, reg, val);
> I wouldn't bother with the intermediate  but up to you...
> 
> ret = lm3533_write(als->lm3533, lm3533_als_get_target_reg(nr, zone), val);

I prefer the expanded form.

> > +	if (ret)
> > +		dev_err(&indio_dev->dev, "failed to set target brightness\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
> > +{
> > +	u8 offset;
> > +
> offset = !raising;

Ok.

> > +	if (raising)
> > +		offset = 0;
> > +	else
> > +		offset = 1;
> > +
> > +	return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
> > +}
> > +
> > +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
> > +							bool raising, u8 *val)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 reg;
> > +	int ret;
> > +
> > +	if (nr > LM3533_ALS_THRESH_MAX)
> > +		return -EINVAL;
> > +
> > +	reg = lm3533_als_get_threshold_reg(nr, raising);
> > +	ret = lm3533_read(als->lm3533, reg, val);
> > +	if (ret)
> > +		dev_err(&indio_dev->dev, "failed to get threshold\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
> > +							bool raising, u8 val)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 val2;
> > +	u8 reg;
> > +	u8 reg2;
> u8 val2, reg, reg2; Shorter and still obvious.

I try to avoid doing multiple declarations on a single line, but in this
case, perhaps

	u8 val2;
	u8 reg, reg2;

would be consistent with the rest of the code.

> > +	int ret;
> > +
> > +	if (nr > LM3533_ALS_THRESH_MAX)
> > +		return -EINVAL;
> > +
> > +	reg = lm3533_als_get_threshold_reg(nr, raising);
> > +	reg2 = lm3533_als_get_threshold_reg(nr, !raising);
> > +
> > +	mutex_lock(&als->thresh_mutex);
> > +	ret = lm3533_read(als->lm3533, reg2, &val2);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to get threshold\n");
> > +		goto out;
> > +	}
> > +	/*
> > +	 * This device does not allow negative hysteresis (in fact, it uses
> > +	 * whichever value is smaller as the lower bound) so we need to make
> > +	 * sure that thresh_falling <= thresh_raising.
> > +	 */
> > +	if ((raising && (val < val2)) || (!raising && (val > val2))) {
> > +		ret = -EINVAL;
> > +		goto out;
> > +	}
> > +
> > +	ret = lm3533_write(als->lm3533, reg, val);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to set threshold\n");
> > +		goto out;
> > +	}
> > +out:
> > +	mutex_unlock(&als->thresh_mutex);
> > +
> > +	return ret;
> > +}

[...]

> > +static ssize_t store_als_attr(struct device *dev,
> > +					struct device_attribute *attr,
> > +					const char *buf, size_t len)
> > +{
> > +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > +	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> > +	u8 val;
> > +	int ret;
> > +
> > +	if (kstrtou8(buf, 0, &val))
> > +		return -EINVAL;
> > +
> > +	switch (als_attr->type) {
> > +	case LM3533_ATTR_TYPE_TARGET:
> > +		ret = lm3533_als_set_target(indio_dev, als_attr->val1,
> > +							als_attr->val2, val);
> > +		break;
> > +	case LM3533_ATTR_TYPE_THRESH_FALLING:
> > +		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> > +								false, val);
> > +		break;
> > +	case LM3533_ATTR_TYPE_THRESH_RAISING:
> > +		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> > +								true, val);
> > +		break;
> > +	default:
> I'd be tempted to drop this. It is easy to verify whether it will occur.

Drop the WARN and simply return -ENXIO, you mean?

> > +		WARN(1, "%s - bad attribute type %d\n", __func__,
> > +							als_attr->type);
> > +		ret = -ENXIO;
> > +	}
> > +
> > +	if (ret)
> > +		return ret;
> > +
> > +	return len;
> > +}

[...]

> > +static struct attribute *lm3533_als_event_attributes[] = {
> > +	&dev_attr_in_illuminance0_thresh_either_en.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
> Just to verify the hysteresis applies to bother thresh0_falling and
> thresh0_rising?

Yes, we have the following situation (just to reiterate):

	...
		zone 1

	thresh0_raising		52

	thresh0_falling		50

		zone 0

If the ALS is in zone 0 and the (8-bit) average input raises above
thresh0_raising (52) it enters zone 1. The ALS will not re-enter zone 0 until
the input drops below thresh0_falling (50). The hysteresis is the
difference between the two thresholds, that is, 52 - 50 = 2 in this
case.

> > +	&lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
> > +	NULL
> > +};
> > +
> > +static struct attribute_group lm3533_als_event_attribute_group = {
> > +	.attrs = lm3533_als_event_attributes
> > +};
> > +
> > +static struct attribute *lm3533_als_attributes[] = {
> > +	&dev_attr_r_select.attr,
> Just to keep info in one place. We agreed in other branch
> of thread that r_select would be done with platform data.

Indeed. I've dropped it from sysfs.

> I wonder if we can make the naming a little clearer for these.. hmm.
> > +	&lm3533_als_attr_target1_0.dev_attr.attr,
> > +	&lm3533_als_attr_target1_1.dev_attr.attr,
> > +	&lm3533_als_attr_target1_2.dev_attr.attr,
> > +	&lm3533_als_attr_target1_3.dev_attr.attr,
> > +	&lm3533_als_attr_target1_4.dev_attr.attr,
> > +	&lm3533_als_attr_target2_0.dev_attr.attr,
> > +	&lm3533_als_attr_target2_1.dev_attr.attr,
> > +	&lm3533_als_attr_target2_2.dev_attr.attr,
> > +	&lm3533_als_attr_target2_3.dev_attr.attr,
> > +	&lm3533_als_attr_target2_4.dev_attr.attr,
> > +	&lm3533_als_attr_target3_0.dev_attr.attr,
> > +	&lm3533_als_attr_target3_1.dev_attr.attr,
> > +	&lm3533_als_attr_target3_2.dev_attr.attr,
> > +	&lm3533_als_attr_target3_3.dev_attr.attr,
> > +	&lm3533_als_attr_target3_4.dev_attr.attr,
> > +	&dev_attr_in_illuminance0_zone.attr,
> > +	NULL
> > +};
> > +
> > +static struct attribute_group lm3533_als_attribute_group = {
> > +	.attrs = lm3533_als_attributes
> > +};
> > +
> > +static int __devinit lm3533_als_set_input_mode(struct iio_dev *indio_dev,
> > +								int pwm_mode)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
> > +	u8 val;
> Just use mask directly, why introduce val as well
> (particularly as you don't use it ;)
> > +	int ret;
> > +
> > +	if (pwm_mode)
> > +		val = mask;	/* pwm input */
> > +	else
> > +		val = 0;	/* analog input */
> > +
> Why have val?
> > +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);

This is supposed to read lm3533_update(..., val, mask). Thanks for
catching this.

> > +	if (ret) {
> > +		dev_err(&indio_dev->dev,
> > +				"failed to set input mode %d\n", pwm_mode);
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static int __devinit lm3533_als_setup(struct iio_dev *indio_dev,
> > +					struct lm3533_als_platform_data *pdata)
> > +{
> > +	int ret;
> > +
> > +	ret = lm3533_als_set_input_mode(indio_dev, pdata->pwm_mode);
> > +	if (ret)
> > +		return ret;
> > +
> > +	/* ALS input is always high impedance in PWM-mode. */
> > +	if (!pdata->pwm_mode) {
> > +		ret = lm3533_als_set_resistor(indio_dev, pdata->r_select);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> This enable / disable pair does rather look like it could
> be combined into one function and save a few lines of repeated
> code

They could but at the expense of some readability (als_enable(...,
false) rather than als_disable(...)) and really not much gain in terms
of fewer line of code as you'd need to add conditionals both for the
register value and error message.

This is the style I've used in the other drivers so if you don't mind
terribly, I'd suggest not merging them if only for consistency.

> > +static int __devinit lm3533_als_enable(struct iio_dev *indio_dev)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 mask = LM3533_ALS_ENABLE_MASK;
> > +	int ret;
> > +
> > +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
> > +	if (ret)
> > +		dev_err(&indio_dev->dev, "failed to enable ALS\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static int __devexit lm3533_als_disable(struct iio_dev *indio_dev)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 mask = LM3533_ALS_ENABLE_MASK;
> > +	int ret;
> > +
> > +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
> > +	if (ret)
> > +		dev_err(&indio_dev->dev, "failed to disable ALS\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct iio_info lm3533_als_info = {
> > +	.attrs		= &lm3533_als_attribute_group,
> > +	.event_attrs	= &lm3533_als_event_attribute_group,
> > +	.driver_module	= THIS_MODULE,
> > +	.read_raw	= &lm3533_als_read_raw,
> > +};
> > +
> > +static int __devinit lm3533_als_probe(struct platform_device *pdev)
> > +{
> > +	struct lm3533 *lm3533;
> > +	struct lm3533_als_platform_data *pdata;
> > +	struct lm3533_als *als;
> > +	struct iio_dev *indio_dev;
> > +	int ret;
> > +
> > +	dev_dbg(&pdev->dev, "%s\n", __func__);
> > +
> > +	lm3533 = dev_get_drvdata(pdev->dev.parent);
> > +	if (!lm3533)
> > +		return -EINVAL;
> > +
> > +	pdata = pdev->dev.platform_data;
> > +	if (!pdata) {
> > +		dev_err(&pdev->dev, "no platform data\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	indio_dev = iio_device_alloc(sizeof(*als));
> > +	if (!indio_dev)
> > +		return -ENOMEM;
> > +
> > +	indio_dev->info = &lm3533_als_info;
> > +	indio_dev->channels = lm3533_als_channels;
> > +	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
> > +	indio_dev->name = "lm3533-als";
> > +	indio_dev->dev.parent = pdev->dev.parent;
> > +	indio_dev->modes = INDIO_DIRECT_MODE;
> > +
> > +	als = iio_priv(indio_dev);
> > +	als->lm3533 = lm3533;
> > +	als->irq = lm3533->irq;
> > +	als->pwm_mode = pdata->pwm_mode;
> > +	atomic_set(&als->zone, 0);
> > +	mutex_init(&als->thresh_mutex);
> > +
> > +	if (als->irq) {
> > +		ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
> > +						IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> > +						indio_dev->name, indio_dev);
> > +		if (ret) {
> > +			dev_err(&pdev->dev, "failed to request irq %d\n",
> > +								als->irq);
> > +			goto err_free_dev;
> > +		}
> > +	}
> > +
> > +	platform_set_drvdata(pdev, indio_dev);
> > +
> amongst other things this register does the creation of the
> sysfs attributes.  Is there a race here if a read on one of
> those occurs before the setup stuff below?

The enable call simply enables the adc so, for example, target or
threshold values could be updated before. Worst case is that you get a
zero reading from the adc before the adc is enabled. The zone algorithm
takes some time to settle either way.

> Normally I'd expect this call to the last one in the probe
> function.  Why did you chose this ordering?

The main reason in this case was to be able to use the iio device for
error reporting. The setup function called set_resistor which was also
possible to set from sysfs (and for consistency all error reporting
after probe is done using the iio device).

But since we've now dropped the r_select attribute, I could use the
platform device for reporting all errors during setup and make sure the
iio device is registered last. I'll do this.

> > +	ret = iio_device_register(indio_dev);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "failed to register ALS\n");
> > +		goto err_free_irq;
> > +	}
> > +
> > +	ret = lm3533_als_setup(indio_dev, pdata);
> > +	if (ret)
> > +		goto err_unregister;
> > +
> > +	ret = lm3533_als_enable(indio_dev);
> > +	if (ret)
> > +		goto err_unregister;
> > +
> > +	return 0;
> > +
> > +err_unregister:
> > +	iio_device_unregister(indio_dev);
> > +err_free_irq:
> > +	if (als->irq)
> > +		free_irq(als->irq, indio_dev);
> > +err_free_dev:
> > +	iio_device_free(indio_dev);
> > +
> > +	return ret;
> > +}
> > +
> > +static int __devexit lm3533_als_remove(struct platform_device *pdev)
> > +{
> > +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +
> > +	dev_dbg(&pdev->dev, "%s\n", __func__);
> > +
> > +	lm3533_als_disable(indio_dev);
> > +	iio_device_unregister(indio_dev);
> > +	if (als->irq)
> > +		free_irq(als->irq, indio_dev);
> > +	iio_device_free(indio_dev);
> > +
> > +	return 0;
> > +}
> > +
> > +static struct platform_driver lm3533_als_driver = {
> > +	.driver	= {
> > +		.name	= "lm3533-als",
> > +		.owner	= THIS_MODULE,
> > +	},
> > +	.probe		= lm3533_als_probe,
> > +	.remove		= __devexit_p(lm3533_als_remove),
> > +};
> > +module_platform_driver(lm3533_als_driver);
> > +
> > +MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
> > +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:lm3533-als");
> > diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
> > index 9660feb..594bc59 100644
> > --- a/include/linux/mfd/lm3533.h
> > +++ b/include/linux/mfd/lm3533.h
> > @@ -43,6 +43,7 @@ struct lm3533_ctrlbank {
> >  
> >  struct lm3533_als_platform_data {
> >  	unsigned pwm_mode:1;		/* PWM input mode (default analog) */
> > +	u8 r_select;			/* 1 - 127 (ignored in PWM-mode) */
> >  };
> >  
> >  struct lm3533_bl_platform_data {
> 

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

* Re: [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-19  8:48         ` Jonathan Cameron
  2012-05-19 16:30           ` Johan Hovold
@ 2012-05-21  9:50           ` Johan Hovold
  2012-05-21 16:37             ` Jonathan Cameron
  1 sibling, 1 reply; 131+ messages in thread
From: Johan Hovold @ 2012-05-21  9:50 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Johan Hovold, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio

On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
> On 05/18/2012 02:07 PM, Johan Hovold wrote:
> > Add sub-driver for the ambient-light-sensor interface on National
> > Semiconductor / TI LM3533 lighting power chips.
> > 
> > The sensor interface can be used to control the LEDs and backlights of
> > the chip through defining five light zones and three sets of
> > corresponding brightness target levels.
> > 
> > The driver provides raw and mean adc readings along with the current
> > light zone through sysfs. A threshold event can be generated on zone
> > changes.
> 
> Hi Johan,
> 
> I hate to be a pain with this one, but it's a complex beast and I'd
> really like to get the interface right first time - particularly as
> you are going in after the move out of staging.
> 
> 
> Queries for you.
> 1) Ordering in the probe function. Normally expect iio_device_register
> to be the last call. Why not here?
> 2) Worth combining enable / disable into one as very similar functions?
> 3) Suspicious code in als_set_input_mode
> 
> Naming of the target values.  I think we can make the naming of these
> fit in much better with the normal abi which is going to be all for the
> good in the long run.  They are basically current output channels
> with a controllable set of steps (where we don't have direct control
> of which one we are in).  This is very similar to the frequency controls
> on some of Analog's dds that we have a well defined interface for.
> 
> More detail below, but in summary.
> 
> out_currentX_currentY_raw for channel X value for zone Y.
> 
> Jonathan
> > 
> > Signed-off-by: Johan Hovold <jhovold@gmail.com>
> > ---
> > 
> > Note that addition of r_select to the platform data probably needs to go
> > in via mfd.
> > 
> > 
> > v2:
> >  - reimplement using iio
> >  - add sysfs-ABI documentation
> > v3
> >  - use indexed channel
> >  - fix sysfs-ABI documentation typo and style
> >  - replace gain attribute with in_illuminance0_calibscale
> >  - add calibscale to platform data
> >  - fix adc register definitions
> >  - replace to_lm3533_dev_attr macro with inline function
> >  - fix device used for error reporting at irq allocation
> >  - use iio device for error reporting during setup/enable
> >  - rebase on staging-next
> >    - fix header include paths
> >    - use dev_to_iio_dev
> >    - add IIO_CHAN_INFO_RAW to info mask
> >    - use iio_device_{alloc,free}
> > v4
> >  - move to driver/iio/light
> >  - add events/in_illuminance0_threshY_hysteresis attributes
> >  - fix device zone-boundary quirkiness
> >  - clean up attribute handling
> >  - replace calibscale with device-specific r_select attribute
> > 
> > 
> >  .../ABI/testing/sysfs-bus-iio-light-lm3533-als     |   64 ++
> >  drivers/iio/Kconfig                                |    1 +
> >  drivers/iio/Makefile                               |    1 +
> >  drivers/iio/light/Kconfig                          |   22 +
> >  drivers/iio/light/Makefile                         |    5 +
> >  drivers/iio/light/lm3533-als.c                     |  941 ++++++++++++++++++++
> >  include/linux/mfd/lm3533.h                         |    1 +
> >  7 files changed, 1035 insertions(+), 0 deletions(-)
> >  create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >  create mode 100644 drivers/iio/light/Kconfig
> >  create mode 100644 drivers/iio/light/Makefile
> >  create mode 100644 drivers/iio/light/lm3533-als.c
> > 
> > diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > new file mode 100644
> > index 0000000..7ea1770
> > --- /dev/null
> > +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > @@ -0,0 +1,64 @@
> > +What:		/sys/bus/iio/devices/iio:deviceX/r_select
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Set the ALS internal pull-down resistor for analog input mode
> > +		(1..127), such that,
> > +
> > +		R_als = 200000 / r_select	(ohm)
> > +
> > +		This setting is ignored in PWM-mode (input is always high
> > +		impedance in PWM-mode).
> > +
> > +What:		/sys/.../events/in_illuminance0_thresh_either_en
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Event generated when channel passes one of the four thresholds
> > +		in each direction (rising|falling) and a zone change occurs.
> > +		The corresponding light zone can be read from
> > +		in_illuminance0_zone.
> > +
> > +What:		/sys/.../events/in_illuminance0_threshY_hysteresis
> > +Date:		May 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Get the hysteresis for thresholds Y, that is,
> > +
> > +		threshY_hysteresis = threshY_raising - threshY_falling
> > +
> > +What:		/sys/.../events/illuminance_threshY_falling_value
> > +What:		/sys/.../events/illuminance_threshY_raising_value
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Specifies the value of threshold that the device is
> > +		comparing against for the events enabled by
> > +		in_illuminance0_thresh_either_en, where Y in 0..3.
> > +
> > +		Note that threshY_falling must be less than or equal to
> > +		threshY_raising.
> > +
> > +		These thresholds correspond to the eight zone-boundary
> > +		registers (boundaryY_{low,high}) and defines the five light
> > +		zones.
> > +
> > +What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Get the current light zone (0..4) as defined by the
> > +		in_illuminance0_threshY_{falling,rising} thresholds.
> > +
> > +What:		/sys/bus/iio/devices/iio:deviceX/targetY_Z
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold <jhovold@gmail.com>
> > +Description:
> > +		Set the target brightness for ALS-mapper Y in light zone Z
> > +		(0..255), where Y in 1..3 and Z in 0..4.
> 
> What are the units of this?

The datasheet reads "percent of the full-scale current" (actually depends
somewhat on whether the als is in linear or exponential mode). When the
leds or backlights are in PWM-mode (not the ALS necessarily), these
values are interpreted as a scale factor which is applied to the output
current determined by the PWM-signal.

Either way it could indeed be considered a raw output current (which
could be manipulated later by various factors).

> Also arguably is it not the als that this is related to, but rather
> the light source?

Well, it would be a raw output, mapping the measured LUX.

> A quick datasheet browse says that these are current targets? If so I
> wonder if we can make that explicit...  Could treat them as 3 output
> channels and have indexed values like we do for frequencies in dds
> devices (where external hardware is controlling them.

I think I like this.

> Hmm. lets see.
> 
> out_currentX_currentY_raw
> (the double naming is a bit clunky but corresponds to frequency devices
> where we have
> out_altvoltageX_frequencyY_raw
> 
> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
> as indexed values they can take 0,1,2,3,4
> out_currentX_raw is not read only and gives you the current for whichever
> zone the device is currently in.

I take it you mean "out_currentX_raw is read only".

> This may seem convoluted but I'd really rather have something generalizable
> for this if we possibly can.  We'd still need documentation to say what is
> controlling these, but at least they'd fit within our more general abi.
> 
> What do you think?

I like it. From a user perspective it's mainly a change of names (and
indexes). But conceptually it's perhaps more clear: the als maps it's
input to an output current, which, just like a PWM-signal, could be used
as an input to the LEDs and backlights to determine their outputs.

I'd have to modify the LED and backlight interfaces somewhat to reflect
the changed indexes and terminology (e.g. "output channel" rather
than "ALS mapper"). Something like:

	als_en		-- enable als input mode (0,1)
	als_channel	-- which out_currentX to use as input (0..2) in
			   als input mode

So to summarise, we get the following new sysfs-entries for the ALS
(where the first set replace targetX_Y):

	out_currentX_currentY_raw	r/w, (0..255), X in 0..2, Y in 0..4
	out_currentX_raw		ro (0..255), X in 0..2

Is there any support in core for the first set or should I simply
rename my target attributes?

Thanks,
Johan

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

* [PATCH v5] iio: add LM3533 ambient-light-sensor driver
  2012-05-18 13:07       ` [PATCH v4] " Johan Hovold
  2012-05-19  8:48         ` Jonathan Cameron
@ 2012-05-21 12:18         ` Johan Hovold
  2012-05-22  9:19           ` Jonathan Cameron
  1 sibling, 1 reply; 131+ messages in thread
From: Johan Hovold @ 2012-05-21 12:18 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Rob Landley, Richard Purdie, Samuel Ortiz, Greg Kroah-Hartman,
	Florian Tobias Schandinat, Arnd Bergmann, Andrew Morton,
	Mark Brown, linux-doc, linux-kernel, linux-iio, Johan Hovold

Add sub-driver for the ambient-light-sensor interface on National
Semiconductor / TI LM3533 lighting power chips.

The sensor interface can be used to control the LEDs and backlights of
the chip through defining five light zones and three sets of
corresponding output-current values.

The driver provides raw and mean adc readings along with the current
light zone through sysfs. A threshold event can be generated on zone
changes. The ALS-control output values can be set per zone for the three
current output channels.

Signed-off-by: Johan Hovold <jhovold@gmail.com>
---

v2:
 - reimplement using iio
 - add sysfs-ABI documentation
v3
 - use indexed channel
 - fix sysfs-ABI documentation typo and style
 - replace gain attribute with in_illuminance0_calibscale
 - add calibscale to platform data
 - fix adc register definitions
 - replace to_lm3533_dev_attr macro with inline function
 - fix device used for error reporting at irq allocation
 - use iio device for error reporting during setup/enable
 - rebase on staging-next
   - fix header include paths
   - use dev_to_iio_dev
   - add IIO_CHAN_INFO_RAW to info mask
   - use iio_device_{alloc,free}
v4
 - move to driver/iio/light
 - add events/in_illuminance0_threshY_hysteresis attributes
 - fix device zone-boundary quirkiness
 - clean up attribute handling
 - replace calibscale with device-specific r_select attribute
v5
 - drop r_select from sysfs ABI
 - fix set_input_mode
 - minor style changes
 - register iio device last at probe
 - use dev_name for iio_dev and irq name
 - add out_currentY channels


 .../ABI/testing/sysfs-bus-iio-light-lm3533-als     |   62 ++
 drivers/iio/Kconfig                                |    1 +
 drivers/iio/Makefile                               |    1 +
 drivers/iio/light/Kconfig                          |   22 +
 drivers/iio/light/Makefile                         |    5 +
 drivers/iio/light/lm3533-als.c                     |  932 ++++++++++++++++++++
 6 files changed, 1023 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
 create mode 100644 drivers/iio/light/Kconfig
 create mode 100644 drivers/iio/light/Makefile
 create mode 100644 drivers/iio/light/lm3533-als.c

diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
new file mode 100644
index 0000000..694a52c
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
@@ -0,0 +1,62 @@
+What:		/sys/.../events/in_illuminance0_thresh_either_en
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Event generated when channel passes one of the four thresholds
+		in each direction (rising|falling) and a zone change occurs.
+		The corresponding light zone can be read from
+		in_illuminance0_zone.
+
+What:		/sys/.../events/in_illuminance0_threshY_hysteresis
+Date:		May 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the hysteresis for thresholds Y, that is,
+
+		threshY_hysteresis = threshY_raising - threshY_falling
+
+What:		/sys/.../events/illuminance_threshY_falling_value
+What:		/sys/.../events/illuminance_threshY_raising_value
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Specifies the value of threshold that the device is comparing
+		against for the events enabled by
+		in_illuminance0_thresh_either_en (0..255), where Y in 0..3.
+
+		Note that threshY_falling must be less than or equal to
+		threshY_raising.
+
+		These thresholds correspond to the eight zone-boundary
+		registers (boundaryY_{low,high}) and defines the five light
+		zones.
+
+What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
+Date:		April 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get the current light zone (0..4) as defined by the
+		in_illuminance0_threshY_{falling,rising} thresholds.
+
+What:		/sys/bus/iio/devices/iio:deviceX/out_currentY_raw
+Date:		May 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Get output current for channel Y (0..255), that is,
+		out_currentY_currentZ_raw, where Z is the current zone.
+
+What:		/sys/bus/iio/devices/iio:deviceX/out_currentY_currentZ_raw
+Date:		May 2012
+KernelVersion:	3.5
+Contact:	Johan Hovold <jhovold@gmail.com>
+Description:
+		Set the output current for channel out_currentY when in zone
+		Z (0..255), where Y in 0..2 and Z in 0..4.
+
+		These values correspond to the ALS-mapper target registers for
+		ALS-mapper Y + 1.
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 56eecef..cacc74d 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -50,5 +50,6 @@ config IIO_CONSUMERS_PER_TRIGGER
 
 source "drivers/iio/adc/Kconfig"
 source "drivers/iio/amplifiers/Kconfig"
+source "drivers/iio/light/Kconfig"
 
 endif # IIO
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index e425afd..060b674 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o
 
 obj-y += adc/
 obj-y += amplifiers/
+obj-y += light/
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
new file mode 100644
index 0000000..db5618e
--- /dev/null
+++ b/drivers/iio/light/Kconfig
@@ -0,0 +1,22 @@
+#
+# Light sensors
+#
+menu "Light sensors"
+
+config SENSORS_LM3533
+	tristate "LM3533 ambient light sensor"
+	depends on MFD_LM3533
+	help
+	  If you say yes here you get support for the ambient light sensor
+	  interface on National Semiconductor / TI LM3533 Lighting Power
+	  chips.
+
+	  The sensor interface can be used to control the LEDs and backlights
+	  of the chip through defining five light zones and three sets of
+	  corresponding output-current values.
+
+	  The driver provides raw and mean adc readings along with the current
+	  light zone through sysfs. A threshold event can be generated on zone
+	  changes. The ALS-control output values can be set per zone for the
+	  three current output channels.
+endmenu
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
new file mode 100644
index 0000000..c1c23a0
--- /dev/null
+++ b/drivers/iio/light/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for IIO Light sensors
+#
+
+obj-$(CONFIG_SENSORS_LM3533)	+= lm3533-als.o
diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
new file mode 100644
index 0000000..c3e7bac
--- /dev/null
+++ b/drivers/iio/light/lm3533-als.c
@@ -0,0 +1,932 @@
+/*
+ * lm3533-als.c -- LM3533 Ambient Light Sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <jhovold@gmail.com>
+ *
+ * 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.
+ */
+
+#include <linux/atomic.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_ALS_RESISTOR_MIN			1
+#define LM3533_ALS_RESISTOR_MAX			127
+#define LM3533_ALS_CHANNEL_CURRENT_MAX		2
+#define LM3533_ALS_THRESH_MAX			3
+#define LM3533_ALS_ZONE_MAX			4
+
+#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
+#define LM3533_REG_ALS_CONF			0x31
+#define LM3533_REG_ALS_ZONE_INFO		0x34
+#define LM3533_REG_ALS_READ_ADC_RAW		0x37
+#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x38
+#define LM3533_REG_ALS_BOUNDARY_BASE		0x50
+#define LM3533_REG_ALS_TARGET_BASE		0x60
+
+#define LM3533_ALS_ENABLE_MASK			0x01
+#define LM3533_ALS_INPUT_MODE_MASK		0x02
+#define LM3533_ALS_INT_ENABLE_MASK		0x01
+
+#define LM3533_ALS_ZONE_SHIFT			2
+#define LM3533_ALS_ZONE_MASK			0x1c
+
+#define LM3533_ALS_FLAG_INT_ENABLED		1
+
+
+struct lm3533_als {
+	struct lm3533 *lm3533;
+	struct platform_device *pdev;
+
+	unsigned long flags;
+	int irq;
+
+	atomic_t zone;
+	struct mutex thresh_mutex;
+};
+
+
+static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
+								int *adc)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	u8 val;
+	int ret;
+
+	if (average)
+		reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
+	else
+		reg = LM3533_REG_ALS_READ_ADC_RAW;
+
+	ret = lm3533_read(als->lm3533, reg, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to read adc\n");
+		return ret;
+	}
+
+	*adc = val;
+
+	return 0;
+}
+
+static int _lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to read zone\n");
+		return ret;
+	}
+
+	val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
+	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
+
+	return 0;
+}
+
+static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	int ret;
+
+	if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) {
+		*zone = atomic_read(&als->zone);
+	} else {
+		ret = _lm3533_als_get_zone(indio_dev, zone);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * channel	output channel 0..2
+ * zone		zone 0..4
+ */
+static inline u8 lm3533_als_get_target_reg(unsigned channel, unsigned zone)
+{
+	return LM3533_REG_ALS_TARGET_BASE + 5 * channel + zone;
+}
+
+static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned channel,
+							unsigned zone, u8 *val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	int ret;
+
+	if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX)
+		return -EINVAL;
+
+	if (zone > LM3533_ALS_ZONE_MAX)
+		return -EINVAL;
+
+	reg = lm3533_als_get_target_reg(channel, zone);
+	ret = lm3533_read(als->lm3533, reg, val);
+	if (ret)
+		dev_err(&indio_dev->dev, "failed to get target current\n");
+
+	return ret;
+}
+
+static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned channel,
+							unsigned zone, u8 val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	int ret;
+
+	if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX)
+		return -EINVAL;
+
+	if (zone > LM3533_ALS_ZONE_MAX)
+		return -EINVAL;
+
+	reg = lm3533_als_get_target_reg(channel, zone);
+	ret = lm3533_write(als->lm3533, reg, val);
+	if (ret)
+		dev_err(&indio_dev->dev, "failed to set target current\n");
+
+	return ret;
+}
+
+static int lm3533_als_get_current(struct iio_dev *indio_dev, unsigned channel,
+								int *val)
+{
+	u8 zone;
+	u8 target;
+	int ret;
+
+	ret = lm3533_als_get_zone(indio_dev, &zone);
+	if (ret)
+		return ret;
+
+	ret = lm3533_als_get_target(indio_dev, channel, zone, &target);
+	if (ret)
+		return ret;
+
+	*val = target;
+
+	return 0;
+}
+
+static int lm3533_als_read_raw(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan,
+				int *val, int *val2, long mask)
+{
+	int ret;
+
+	switch (mask) {
+	case 0:
+		switch (chan->type) {
+		case IIO_LIGHT:
+			ret = lm3533_als_get_adc(indio_dev, false, val);
+			break;
+		case IIO_CURRENT:
+			ret = lm3533_als_get_current(indio_dev, chan->channel,
+									val);
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case IIO_CHAN_INFO_AVERAGE_RAW:
+		ret = lm3533_als_get_adc(indio_dev, true, val);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (ret)
+		return ret;
+
+	return IIO_VAL_INT;
+}
+
+#define CHANNEL_CURRENT(_channel)					\
+	{								\
+		.type		= IIO_CURRENT,				\
+		.channel	= _channel,				\
+		.indexed	= true,					\
+		.output		= true,					\
+		.info_mask	= IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
+	}
+
+static const struct iio_chan_spec lm3533_als_channels[] = {
+	{
+		.type		= IIO_LIGHT,
+		.channel	= 0,
+		.indexed	= true,
+		.info_mask	= (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
+				   IIO_CHAN_INFO_RAW_SEPARATE_BIT),
+	},
+	CHANNEL_CURRENT(0),
+	CHANNEL_CURRENT(1),
+	CHANNEL_CURRENT(2),
+};
+
+static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
+{
+
+	struct iio_dev *indio_dev = dev_id;
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 zone;
+	int ret;
+
+	/* Clear interrupt by reading the ALS zone register. */
+	ret = _lm3533_als_get_zone(indio_dev, &zone);
+	if (ret)
+		goto out;
+
+	atomic_set(&als->zone, zone);
+
+	iio_push_event(indio_dev,
+		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+					    0,
+					    IIO_EV_TYPE_THRESH,
+					    IIO_EV_DIR_EITHER),
+		       iio_get_time_ns());
+out:
+	return IRQ_HANDLED;
+}
+
+static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+	int ret;
+
+	if (enable)
+		val = mask;
+	else
+		val = 0;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
+								enable);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	u8 val;
+	int ret;
+
+	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to get int mode\n");
+		return ret;
+	}
+
+	*enable = !!(val & mask);
+
+	return 0;
+}
+
+static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
+{
+	u8 offset = !raising;
+
+	return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
+}
+
+static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
+							bool raising, u8 *val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 reg;
+	int ret;
+
+	if (nr > LM3533_ALS_THRESH_MAX)
+		return -EINVAL;
+
+	reg = lm3533_als_get_threshold_reg(nr, raising);
+	ret = lm3533_read(als->lm3533, reg, val);
+	if (ret)
+		dev_err(&indio_dev->dev, "failed to get threshold\n");
+
+	return ret;
+}
+
+static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
+							bool raising, u8 val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 val2;
+	u8 reg, reg2;
+	int ret;
+
+	if (nr > LM3533_ALS_THRESH_MAX)
+		return -EINVAL;
+
+	reg = lm3533_als_get_threshold_reg(nr, raising);
+	reg2 = lm3533_als_get_threshold_reg(nr, !raising);
+
+	mutex_lock(&als->thresh_mutex);
+	ret = lm3533_read(als->lm3533, reg2, &val2);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to get threshold\n");
+		goto out;
+	}
+	/*
+	 * This device does not allow negative hysteresis (in fact, it uses
+	 * whichever value is smaller as the lower bound) so we need to make
+	 * sure that thresh_falling <= thresh_raising.
+	 */
+	if ((raising && (val < val2)) || (!raising && (val > val2))) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = lm3533_write(als->lm3533, reg, val);
+	if (ret) {
+		dev_err(&indio_dev->dev, "failed to set threshold\n");
+		goto out;
+	}
+out:
+	mutex_unlock(&als->thresh_mutex);
+
+	return ret;
+}
+
+static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr,
+								u8 *val)
+{
+	struct lm3533_als *als = iio_priv(indio_dev);
+	u8 falling;
+	u8 raising;
+	int ret;
+
+	if (nr > LM3533_ALS_THRESH_MAX)
+		return -EINVAL;
+
+	mutex_lock(&als->thresh_mutex);
+	ret = lm3533_als_get_threshold(indio_dev, nr, false, &falling);
+	if (ret)
+		goto out;
+	ret = lm3533_als_get_threshold(indio_dev, nr, true, &raising);
+	if (ret)
+		goto out;
+
+	*val = raising - falling;
+out:
+	mutex_unlock(&als->thresh_mutex);
+
+	return ret;
+}
+
+static int show_thresh_either_en(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	int enable;
+	int ret;
+
+	if (als->irq) {
+		ret = lm3533_als_get_int_mode(indio_dev, &enable);
+		if (ret)
+			return ret;
+	} else {
+		enable = 0;
+	}
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
+}
+
+static int store_thresh_either_en(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+	unsigned long enable;
+	bool int_enabled;
+	u8 zone;
+	int ret;
+
+	if (!als->irq)
+		return -EBUSY;
+
+	if (kstrtoul(buf, 0, &enable))
+		return -EINVAL;
+
+	int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+	if (enable && !int_enabled) {
+		ret = lm3533_als_get_zone(indio_dev, &zone);
+		if (ret)
+			return ret;
+
+		atomic_set(&als->zone, zone);
+
+		set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+	}
+
+	ret = lm3533_als_set_int_mode(indio_dev, enable);
+	if (ret) {
+		if (!int_enabled)
+			clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+		return ret;
+	}
+
+	if (!enable)
+		clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+	return len;
+}
+
+static ssize_t show_zone(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	u8 zone;
+	int ret;
+
+	ret = lm3533_als_get_zone(indio_dev, &zone);
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
+}
+
+enum lm3533_als_attribute_type {
+	LM3533_ATTR_TYPE_HYSTERESIS,
+	LM3533_ATTR_TYPE_TARGET,
+	LM3533_ATTR_TYPE_THRESH_FALLING,
+	LM3533_ATTR_TYPE_THRESH_RAISING,
+};
+
+struct lm3533_als_attribute {
+	struct device_attribute dev_attr;
+	enum lm3533_als_attribute_type type;
+	u8 val1;
+	u8 val2;
+};
+
+static inline struct lm3533_als_attribute *
+to_lm3533_als_attr(struct device_attribute *attr)
+{
+	return container_of(attr, struct lm3533_als_attribute, dev_attr);
+}
+
+static ssize_t show_als_attr(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
+	u8 val;
+	int ret;
+
+	switch (als_attr->type) {
+	case LM3533_ATTR_TYPE_HYSTERESIS:
+		ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1,
+									&val);
+		break;
+	case LM3533_ATTR_TYPE_TARGET:
+		ret = lm3533_als_get_target(indio_dev, als_attr->val1,
+							als_attr->val2, &val);
+		break;
+	case LM3533_ATTR_TYPE_THRESH_FALLING:
+		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
+								false, &val);
+		break;
+	case LM3533_ATTR_TYPE_THRESH_RAISING:
+		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
+								true, &val);
+		break;
+	default:
+		ret = -ENXIO;
+	}
+
+	if (ret)
+		return ret;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_als_attr(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
+	u8 val;
+	int ret;
+
+	if (kstrtou8(buf, 0, &val))
+		return -EINVAL;
+
+	switch (als_attr->type) {
+	case LM3533_ATTR_TYPE_TARGET:
+		ret = lm3533_als_set_target(indio_dev, als_attr->val1,
+							als_attr->val2, val);
+		break;
+	case LM3533_ATTR_TYPE_THRESH_FALLING:
+		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
+								false, val);
+		break;
+	case LM3533_ATTR_TYPE_THRESH_RAISING:
+		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
+								true, val);
+		break;
+	default:
+		ret = -ENXIO;
+	}
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)	\
+	{ .dev_attr	= __ATTR(_name, _mode, _show, _store),		\
+	  .type		= _type,					\
+	  .val1		= _val1,					\
+	  .val2		= _val2 }
+
+#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
+	struct lm3533_als_attribute lm3533_als_attr_##_name =		  \
+		ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)
+
+#define ALS_TARGET_ATTR_RW(_channel, _zone)				\
+	LM3533_ALS_ATTR(out_current##_channel##_current##_zone##_raw,	\
+				S_IRUGO | S_IWUSR,			\
+				show_als_attr, store_als_attr,		\
+				LM3533_ATTR_TYPE_TARGET, _channel, _zone)
+/*
+ * ALS output current values (ALS mapper targets)
+ *
+ * out_current[0-2]_current[0-4]_raw		0-255
+ */
+static ALS_TARGET_ATTR_RW(0, 0);
+static ALS_TARGET_ATTR_RW(0, 1);
+static ALS_TARGET_ATTR_RW(0, 2);
+static ALS_TARGET_ATTR_RW(0, 3);
+static ALS_TARGET_ATTR_RW(0, 4);
+
+static ALS_TARGET_ATTR_RW(1, 0);
+static ALS_TARGET_ATTR_RW(1, 1);
+static ALS_TARGET_ATTR_RW(1, 2);
+static ALS_TARGET_ATTR_RW(1, 3);
+static ALS_TARGET_ATTR_RW(1, 4);
+
+static ALS_TARGET_ATTR_RW(2, 0);
+static ALS_TARGET_ATTR_RW(2, 1);
+static ALS_TARGET_ATTR_RW(2, 2);
+static ALS_TARGET_ATTR_RW(2, 3);
+static ALS_TARGET_ATTR_RW(2, 4);
+
+#define ALS_THRESH_FALLING_ATTR_RW(_nr)					\
+	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value,	\
+			S_IRUGO | S_IWUSR,				\
+			show_als_attr, store_als_attr,		\
+			LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0)
+
+#define ALS_THRESH_RAISING_ATTR_RW(_nr)					\
+	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value,	\
+			S_IRUGO | S_IWUSR,				\
+			show_als_attr, store_als_attr,			\
+			LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0)
+/*
+ * ALS Zone thresholds (boundaries)
+ *
+ * in_illuminance0_thresh[0-3]_falling_value	0-255
+ * in_illuminance0_thresh[0-3]_raising_value	0-255
+ */
+static ALS_THRESH_FALLING_ATTR_RW(0);
+static ALS_THRESH_FALLING_ATTR_RW(1);
+static ALS_THRESH_FALLING_ATTR_RW(2);
+static ALS_THRESH_FALLING_ATTR_RW(3);
+
+static ALS_THRESH_RAISING_ATTR_RW(0);
+static ALS_THRESH_RAISING_ATTR_RW(1);
+static ALS_THRESH_RAISING_ATTR_RW(2);
+static ALS_THRESH_RAISING_ATTR_RW(3);
+
+#define ALS_HYSTERESIS_ATTR_RO(_nr)					\
+	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis,	\
+			S_IRUGO, show_als_attr, NULL,			\
+			LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0)
+/*
+ * ALS Zone threshold hysteresis
+ *
+ * threshY_hysteresis = threshY_raising - threshY_falling
+ *
+ * in_illuminance0_thresh[0-3]_hysteresis	0-255
+ * in_illuminance0_thresh[0-3]_hysteresis	0-255
+ */
+static ALS_HYSTERESIS_ATTR_RO(0);
+static ALS_HYSTERESIS_ATTR_RO(1);
+static ALS_HYSTERESIS_ATTR_RO(2);
+static ALS_HYSTERESIS_ATTR_RO(3);
+
+#define ILLUMINANCE_ATTR_RO(_name) \
+	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
+#define ILLUMINANCE_ATTR_RW(_name) \
+	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
+						show_##_name, store_##_name)
+/*
+ * ALS Zone threshold-event enable
+ *
+ * in_illuminance0_thresh_either_en		0,1
+ */
+static ILLUMINANCE_ATTR_RW(thresh_either_en);
+
+/*
+ * ALS Current Zone
+ *
+ * in_illuminance0_zone		0-4
+ */
+static ILLUMINANCE_ATTR_RO(zone);
+
+static struct attribute *lm3533_als_event_attributes[] = {
+	&dev_attr_in_illuminance0_thresh_either_en.attr,
+	&lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
+	&lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group lm3533_als_event_attribute_group = {
+	.attrs = lm3533_als_event_attributes
+};
+
+static struct attribute *lm3533_als_attributes[] = {
+	&dev_attr_in_illuminance0_zone.attr,
+	&lm3533_als_attr_out_current0_current0_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current0_current1_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current0_current2_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current0_current3_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current0_current4_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current1_current0_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current1_current1_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current1_current2_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current1_current3_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current1_current4_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current2_current0_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current2_current1_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current2_current2_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current2_current3_raw.dev_attr.attr,
+	&lm3533_als_attr_out_current2_current4_raw.dev_attr.attr,
+	NULL
+};
+
+static struct attribute_group lm3533_als_attribute_group = {
+	.attrs = lm3533_als_attributes
+};
+
+static int __devinit lm3533_als_set_input_mode(struct lm3533_als *als,
+								bool pwm_mode)
+{
+	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
+	u8 val;
+	int ret;
+
+	if (pwm_mode)
+		val = mask;	/* pwm input */
+	else
+		val = 0;	/* analog input */
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, val, mask);
+	if (ret) {
+		dev_err(&als->pdev->dev, "failed to set input mode %d\n",
+								pwm_mode);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int __devinit lm3533_als_set_resistor(struct lm3533_als *als, u8 val)
+{
+	int ret;
+
+	if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX)
+		return -EINVAL;
+
+	ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
+	if (ret) {
+		dev_err(&als->pdev->dev, "failed to set resistor\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int __devinit lm3533_als_setup(struct lm3533_als *als,
+					struct lm3533_als_platform_data *pdata)
+{
+	int ret;
+
+	ret = lm3533_als_set_input_mode(als, pdata->pwm_mode);
+	if (ret)
+		return ret;
+
+	/* ALS input is always high impedance in PWM-mode. */
+	if (!pdata->pwm_mode) {
+		ret = lm3533_als_set_resistor(als, pdata->r_select);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int __devinit lm3533_als_setup_irq(struct lm3533_als *als, void *dev)
+{
+	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+	int ret;
+
+	/* Make sure interrupts are disabled. */
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, 0, mask);
+	if (ret) {
+		dev_err(&als->pdev->dev, "failed to disable interrupts\n");
+		return ret;
+	}
+
+	ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					dev_name(&als->pdev->dev), dev);
+	if (ret) {
+		dev_err(&als->pdev->dev, "failed to request irq %d\n",
+								als->irq);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int __devinit lm3533_als_enable(struct lm3533_als *als)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
+	if (ret)
+		dev_err(&als->pdev->dev, "failed to enable ALS\n");
+
+	return ret;
+}
+
+static int lm3533_als_disable(struct lm3533_als *als)
+{
+	u8 mask = LM3533_ALS_ENABLE_MASK;
+	int ret;
+
+	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
+	if (ret)
+		dev_err(&als->pdev->dev, "failed to disable ALS\n");
+
+	return ret;
+}
+
+static const struct iio_info lm3533_als_info = {
+	.attrs		= &lm3533_als_attribute_group,
+	.event_attrs	= &lm3533_als_event_attribute_group,
+	.driver_module	= THIS_MODULE,
+	.read_raw	= &lm3533_als_read_raw,
+};
+
+static int __devinit lm3533_als_probe(struct platform_device *pdev)
+{
+	struct lm3533 *lm3533;
+	struct lm3533_als_platform_data *pdata;
+	struct lm3533_als *als;
+	struct iio_dev *indio_dev;
+	int ret;
+
+	lm3533 = dev_get_drvdata(pdev->dev.parent);
+	if (!lm3533)
+		return -EINVAL;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -EINVAL;
+	}
+
+	indio_dev = iio_device_alloc(sizeof(*als));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	indio_dev->info = &lm3533_als_info;
+	indio_dev->channels = lm3533_als_channels;
+	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
+	indio_dev->name = dev_name(&pdev->dev);
+	indio_dev->dev.parent = pdev->dev.parent;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	als = iio_priv(indio_dev);
+	als->lm3533 = lm3533;
+	als->pdev = pdev;
+	als->irq = lm3533->irq;
+	atomic_set(&als->zone, 0);
+	mutex_init(&als->thresh_mutex);
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	if (als->irq) {
+		ret = lm3533_als_setup_irq(als, indio_dev);
+		if (ret)
+			goto err_free_dev;
+	}
+
+	ret = lm3533_als_setup(als, pdata);
+	if (ret)
+		goto err_free_irq;
+
+	ret = lm3533_als_enable(als);
+	if (ret)
+		goto err_free_irq;
+
+	ret = iio_device_register(indio_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to register ALS\n");
+		goto err_disable;
+	}
+
+	return 0;
+
+err_disable:
+	lm3533_als_disable(als);
+err_free_irq:
+	if (als->irq)
+		free_irq(als->irq, indio_dev);
+err_free_dev:
+	iio_device_free(indio_dev);
+
+	return ret;
+}
+
+static int __devexit lm3533_als_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+	struct lm3533_als *als = iio_priv(indio_dev);
+
+	lm3533_als_set_int_mode(indio_dev, false);
+	iio_device_unregister(indio_dev);
+	lm3533_als_disable(als);
+	if (als->irq)
+		free_irq(als->irq, indio_dev);
+	iio_device_free(indio_dev);
+
+	return 0;
+}
+
+static struct platform_driver lm3533_als_driver = {
+	.driver	= {
+		.name	= "lm3533-als",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= lm3533_als_probe,
+	.remove		= __devexit_p(lm3533_als_remove),
+};
+module_platform_driver(lm3533_als_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
-- 
1.7.8.5


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

* Re: [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-21  9:50           ` Johan Hovold
@ 2012-05-21 16:37             ` Jonathan Cameron
  2012-05-21 22:07               ` Johan Hovold
  2012-05-22  7:45               ` Michael Hennerich
  0 siblings, 2 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-21 16:37 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	Hennerich, Michael

Michael cc'd for comments on core support of some stuff that is also
in frequency drivers down the end of the email.

On 05/21/2012 10:50 AM, Johan Hovold wrote:
> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>> Add sub-driver for the ambient-light-sensor interface on National
>>> Semiconductor / TI LM3533 lighting power chips.
>>>
>>> The sensor interface can be used to control the LEDs and backlights of
>>> the chip through defining five light zones and three sets of
>>> corresponding brightness target levels.
>>>
>>> The driver provides raw and mean adc readings along with the current
>>> light zone through sysfs. A threshold event can be generated on zone
>>> changes.
>>
>> Hi Johan,
>>
>> I hate to be a pain with this one, but it's a complex beast and I'd
>> really like to get the interface right first time - particularly as
>> you are going in after the move out of staging.
>>
>>
>> Queries for you.
>> 1) Ordering in the probe function. Normally expect iio_device_register
>> to be the last call. Why not here?
>> 2) Worth combining enable / disable into one as very similar functions?
>> 3) Suspicious code in als_set_input_mode
>>
>> Naming of the target values.  I think we can make the naming of these
>> fit in much better with the normal abi which is going to be all for the
>> good in the long run.  They are basically current output channels
>> with a controllable set of steps (where we don't have direct control
>> of which one we are in).  This is very similar to the frequency controls
>> on some of Analog's dds that we have a well defined interface for.
>>
>> More detail below, but in summary.
>>
>> out_currentX_currentY_raw for channel X value for zone Y.
>>
>> Jonathan
>>>
>>> Signed-off-by: Johan Hovold <jhovold@gmail.com>
>>> ---
>>>
>>> Note that addition of r_select to the platform data probably needs to go
>>> in via mfd.
>>>
>>>
>>> v2:
>>>  - reimplement using iio
>>>  - add sysfs-ABI documentation
>>> v3
>>>  - use indexed channel
>>>  - fix sysfs-ABI documentation typo and style
>>>  - replace gain attribute with in_illuminance0_calibscale
>>>  - add calibscale to platform data
>>>  - fix adc register definitions
>>>  - replace to_lm3533_dev_attr macro with inline function
>>>  - fix device used for error reporting at irq allocation
>>>  - use iio device for error reporting during setup/enable
>>>  - rebase on staging-next
>>>    - fix header include paths
>>>    - use dev_to_iio_dev
>>>    - add IIO_CHAN_INFO_RAW to info mask
>>>    - use iio_device_{alloc,free}
>>> v4
>>>  - move to driver/iio/light
>>>  - add events/in_illuminance0_threshY_hysteresis attributes
>>>  - fix device zone-boundary quirkiness
>>>  - clean up attribute handling
>>>  - replace calibscale with device-specific r_select attribute
>>>
>>>
>>>  .../ABI/testing/sysfs-bus-iio-light-lm3533-als     |   64 ++
>>>  drivers/iio/Kconfig                                |    1 +
>>>  drivers/iio/Makefile                               |    1 +
>>>  drivers/iio/light/Kconfig                          |   22 +
>>>  drivers/iio/light/Makefile                         |    5 +
>>>  drivers/iio/light/lm3533-als.c                     |  941 ++++++++++++++++++++
>>>  include/linux/mfd/lm3533.h                         |    1 +
>>>  7 files changed, 1035 insertions(+), 0 deletions(-)
>>>  create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>  create mode 100644 drivers/iio/light/Kconfig
>>>  create mode 100644 drivers/iio/light/Makefile
>>>  create mode 100644 drivers/iio/light/lm3533-als.c
>>>
>>> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>> new file mode 100644
>>> index 0000000..7ea1770
>>> --- /dev/null
>>> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>> @@ -0,0 +1,64 @@
>>> +What:		/sys/bus/iio/devices/iio:deviceX/r_select
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold <jhovold@gmail.com>
>>> +Description:
>>> +		Set the ALS internal pull-down resistor for analog input mode
>>> +		(1..127), such that,
>>> +
>>> +		R_als = 200000 / r_select	(ohm)
>>> +
>>> +		This setting is ignored in PWM-mode (input is always high
>>> +		impedance in PWM-mode).
>>> +
>>> +What:		/sys/.../events/in_illuminance0_thresh_either_en
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold <jhovold@gmail.com>
>>> +Description:
>>> +		Event generated when channel passes one of the four thresholds
>>> +		in each direction (rising|falling) and a zone change occurs.
>>> +		The corresponding light zone can be read from
>>> +		in_illuminance0_zone.
>>> +
>>> +What:		/sys/.../events/in_illuminance0_threshY_hysteresis
>>> +Date:		May 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold <jhovold@gmail.com>
>>> +Description:
>>> +		Get the hysteresis for thresholds Y, that is,
>>> +
>>> +		threshY_hysteresis = threshY_raising - threshY_falling
>>> +
>>> +What:		/sys/.../events/illuminance_threshY_falling_value
>>> +What:		/sys/.../events/illuminance_threshY_raising_value
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold <jhovold@gmail.com>
>>> +Description:
>>> +		Specifies the value of threshold that the device is
>>> +		comparing against for the events enabled by
>>> +		in_illuminance0_thresh_either_en, where Y in 0..3.
>>> +
>>> +		Note that threshY_falling must be less than or equal to
>>> +		threshY_raising.
>>> +
>>> +		These thresholds correspond to the eight zone-boundary
>>> +		registers (boundaryY_{low,high}) and defines the five light
>>> +		zones.
>>> +
>>> +What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold <jhovold@gmail.com>
>>> +Description:
>>> +		Get the current light zone (0..4) as defined by the
>>> +		in_illuminance0_threshY_{falling,rising} thresholds.
>>> +
>>> +What:		/sys/bus/iio/devices/iio:deviceX/targetY_Z
>>> +Date:		April 2012
>>> +KernelVersion:	3.5
>>> +Contact:	Johan Hovold <jhovold@gmail.com>
>>> +Description:
>>> +		Set the target brightness for ALS-mapper Y in light zone Z
>>> +		(0..255), where Y in 1..3 and Z in 0..4.
>>
>> What are the units of this?
> 
> The datasheet reads "percent of the full-scale current" (actually depends
> somewhat on whether the als is in linear or exponential mode). When the
> leds or backlights are in PWM-mode (not the ALS necessarily), these
> values are interpreted as a scale factor which is applied to the output
> current determined by the PWM-signal.
> 
> Either way it could indeed be considered a raw output current (which
> could be manipulated later by various factors).
Fine.... Theoretically if at all possible we'd want the conversion
factors to get it to an actual current (in amps) to be available though.
(tend to relax that if there are unknowable elements or they aren't
specified by board file etc).
> 
>> Also arguably is it not the als that this is related to, but rather
>> the light source?
> 
> Well, it would be a raw output, mapping the measured LUX.
Fair enough, though I wonder if we are stepping on led / backlight
classes stuff with this.

> 
>> A quick datasheet browse says that these are current targets? If so I
>> wonder if we can make that explicit...  Could treat them as 3 output
>> channels and have indexed values like we do for frequencies in dds
>> devices (where external hardware is controlling them.
> 
> I think I like this.
> 
>> Hmm. lets see.
>>
>> out_currentX_currentY_raw
>> (the double naming is a bit clunky but corresponds to frequency devices
>> where we have
>> out_altvoltageX_frequencyY_raw
>>
>> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
>> as indexed values they can take 0,1,2,3,4
>> out_currentX_raw is not read only and gives you the current for whichever
>> zone the device is currently in.
> 
> I take it you mean "out_currentX_raw is read only".
yes. I do indeed. oops.
> 
>> This may seem convoluted but I'd really rather have something generalizable
>> for this if we possibly can.  We'd still need documentation to say what is
>> controlling these, but at least they'd fit within our more general abi.
>>
>> What do you think?
> 
> I like it. From a user perspective it's mainly a change of names (and
> indexes). But conceptually it's perhaps more clear: the als maps it's
> input to an output current, which, just like a PWM-signal, could be used
> as an input to the LEDs and backlights to determine their outputs.
> 
> I'd have to modify the LED and backlight interfaces somewhat to reflect
> the changed indexes and terminology (e.g. "output channel" rather
> than "ALS mapper"). Something like:
> 
> 	als_en		-- enable als input mode (0,1)
> 	als_channel	-- which out_currentX to use as input (0..2) in
> 			   als input mode
Not entirely sure I'm happy with this. Would rather it was done
on a per channel basis, so in_illuminance0_ *
>From point of view of sensors I don't really care if it is an als or
measuring something active (hence inherently not ambient!)

Silly question but how is the out_current related to the input in als
mode?
> 
> So to summarise, we get the following new sysfs-entries for the ALS
> (where the first set replace targetX_Y):
> 
> 	out_currentX_currentY_raw	r/w, (0..255), X in 0..2, Y in 0..4
> 	out_currentX_raw		ro (0..255), X in 0..2
> 
> Is there any support in core for the first set or should I simply
> rename my target attributes?
No support in the core yet for this sort of thing..
Michael, any thoughts on this? In a sense it's very similar to
out_altvoltageX_frequencyY_raw etc...
> 
> Thanks,
> Johan


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

* Re: [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-21 16:37             ` Jonathan Cameron
@ 2012-05-21 22:07               ` Johan Hovold
  2012-05-22  7:13                 ` Jonathan Cameron
  2012-05-22  7:45               ` Michael Hennerich
  1 sibling, 1 reply; 131+ messages in thread
From: Johan Hovold @ 2012-05-21 22:07 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Johan Hovold, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, Hennerich, Michael

On Mon, May 21, 2012 at 05:37:11PM +0100, Jonathan Cameron wrote:
> Michael cc'd for comments on core support of some stuff that is also
> in frequency drivers down the end of the email.
> 
> On 05/21/2012 10:50 AM, Johan Hovold wrote:
> > On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
> >> On 05/18/2012 02:07 PM, Johan Hovold wrote:
> >>> Add sub-driver for the ambient-light-sensor interface on National
> >>> Semiconductor / TI LM3533 lighting power chips.
> >>>
> >>> The sensor interface can be used to control the LEDs and backlights of
> >>> the chip through defining five light zones and three sets of
> >>> corresponding brightness target levels.
> >>>
> >>> The driver provides raw and mean adc readings along with the current
> >>> light zone through sysfs. A threshold event can be generated on zone
> >>> changes.
> >>
> >> Hi Johan,
> >>
> >> I hate to be a pain with this one, but it's a complex beast and I'd
> >> really like to get the interface right first time - particularly as
> >> you are going in after the move out of staging.
> >>
> >>
> >> Queries for you.
> >> 1) Ordering in the probe function. Normally expect iio_device_register
> >> to be the last call. Why not here?
> >> 2) Worth combining enable / disable into one as very similar functions?
> >> 3) Suspicious code in als_set_input_mode
> >>
> >> Naming of the target values.  I think we can make the naming of these
> >> fit in much better with the normal abi which is going to be all for the
> >> good in the long run.  They are basically current output channels
> >> with a controllable set of steps (where we don't have direct control
> >> of which one we are in).  This is very similar to the frequency controls
> >> on some of Analog's dds that we have a well defined interface for.
> >>
> >> More detail below, but in summary.
> >>
> >> out_currentX_currentY_raw for channel X value for zone Y.
> >>
> >> Jonathan
> >>>
> >>> Signed-off-by: Johan Hovold <jhovold@gmail.com>
> >>> ---
> >>>
> >>> Note that addition of r_select to the platform data probably needs to go
> >>> in via mfd.
> >>>
> >>>
> >>> v2:
> >>>  - reimplement using iio
> >>>  - add sysfs-ABI documentation
> >>> v3
> >>>  - use indexed channel
> >>>  - fix sysfs-ABI documentation typo and style
> >>>  - replace gain attribute with in_illuminance0_calibscale
> >>>  - add calibscale to platform data
> >>>  - fix adc register definitions
> >>>  - replace to_lm3533_dev_attr macro with inline function
> >>>  - fix device used for error reporting at irq allocation
> >>>  - use iio device for error reporting during setup/enable
> >>>  - rebase on staging-next
> >>>    - fix header include paths
> >>>    - use dev_to_iio_dev
> >>>    - add IIO_CHAN_INFO_RAW to info mask
> >>>    - use iio_device_{alloc,free}
> >>> v4
> >>>  - move to driver/iio/light
> >>>  - add events/in_illuminance0_threshY_hysteresis attributes
> >>>  - fix device zone-boundary quirkiness
> >>>  - clean up attribute handling
> >>>  - replace calibscale with device-specific r_select attribute
> >>>
> >>>
> >>>  .../ABI/testing/sysfs-bus-iio-light-lm3533-als     |   64 ++
> >>>  drivers/iio/Kconfig                                |    1 +
> >>>  drivers/iio/Makefile                               |    1 +
> >>>  drivers/iio/light/Kconfig                          |   22 +
> >>>  drivers/iio/light/Makefile                         |    5 +
> >>>  drivers/iio/light/lm3533-als.c                     |  941 ++++++++++++++++++++
> >>>  include/linux/mfd/lm3533.h                         |    1 +
> >>>  7 files changed, 1035 insertions(+), 0 deletions(-)
> >>>  create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >>>  create mode 100644 drivers/iio/light/Kconfig
> >>>  create mode 100644 drivers/iio/light/Makefile
> >>>  create mode 100644 drivers/iio/light/lm3533-als.c
> >>>
> >>> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >>> new file mode 100644
> >>> index 0000000..7ea1770
> >>> --- /dev/null
> >>> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >>> @@ -0,0 +1,64 @@
> >>> +What:		/sys/bus/iio/devices/iio:deviceX/r_select
> >>> +Date:		April 2012
> >>> +KernelVersion:	3.5
> >>> +Contact:	Johan Hovold <jhovold@gmail.com>
> >>> +Description:
> >>> +		Set the ALS internal pull-down resistor for analog input mode
> >>> +		(1..127), such that,
> >>> +
> >>> +		R_als = 200000 / r_select	(ohm)
> >>> +
> >>> +		This setting is ignored in PWM-mode (input is always high
> >>> +		impedance in PWM-mode).
> >>> +
> >>> +What:		/sys/.../events/in_illuminance0_thresh_either_en
> >>> +Date:		April 2012
> >>> +KernelVersion:	3.5
> >>> +Contact:	Johan Hovold <jhovold@gmail.com>
> >>> +Description:
> >>> +		Event generated when channel passes one of the four thresholds
> >>> +		in each direction (rising|falling) and a zone change occurs.
> >>> +		The corresponding light zone can be read from
> >>> +		in_illuminance0_zone.
> >>> +
> >>> +What:		/sys/.../events/in_illuminance0_threshY_hysteresis
> >>> +Date:		May 2012
> >>> +KernelVersion:	3.5
> >>> +Contact:	Johan Hovold <jhovold@gmail.com>
> >>> +Description:
> >>> +		Get the hysteresis for thresholds Y, that is,
> >>> +
> >>> +		threshY_hysteresis = threshY_raising - threshY_falling
> >>> +
> >>> +What:		/sys/.../events/illuminance_threshY_falling_value
> >>> +What:		/sys/.../events/illuminance_threshY_raising_value
> >>> +Date:		April 2012
> >>> +KernelVersion:	3.5
> >>> +Contact:	Johan Hovold <jhovold@gmail.com>
> >>> +Description:
> >>> +		Specifies the value of threshold that the device is
> >>> +		comparing against for the events enabled by
> >>> +		in_illuminance0_thresh_either_en, where Y in 0..3.
> >>> +
> >>> +		Note that threshY_falling must be less than or equal to
> >>> +		threshY_raising.
> >>> +
> >>> +		These thresholds correspond to the eight zone-boundary
> >>> +		registers (boundaryY_{low,high}) and defines the five light
> >>> +		zones.
> >>> +
> >>> +What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> >>> +Date:		April 2012
> >>> +KernelVersion:	3.5
> >>> +Contact:	Johan Hovold <jhovold@gmail.com>
> >>> +Description:
> >>> +		Get the current light zone (0..4) as defined by the
> >>> +		in_illuminance0_threshY_{falling,rising} thresholds.
> >>> +
> >>> +What:		/sys/bus/iio/devices/iio:deviceX/targetY_Z
> >>> +Date:		April 2012
> >>> +KernelVersion:	3.5
> >>> +Contact:	Johan Hovold <jhovold@gmail.com>
> >>> +Description:
> >>> +		Set the target brightness for ALS-mapper Y in light zone Z
> >>> +		(0..255), where Y in 1..3 and Z in 0..4.
> >>
> >> What are the units of this?
> > 
> > The datasheet reads "percent of the full-scale current" (actually depends
> > somewhat on whether the als is in linear or exponential mode). When the
> > leds or backlights are in PWM-mode (not the ALS necessarily), these
> > values are interpreted as a scale factor which is applied to the output
> > current determined by the PWM-signal.
> > 
> > Either way it could indeed be considered a raw output current (which
> > could be manipulated later by various factors).
> Fine.... Theoretically if at all possible we'd want the conversion
> factors to get it to an actual current (in amps) to be available though.
> (tend to relax that if there are unknowable elements or they aren't
> specified by board file etc).

Hmmm. I'm starting to get a feeling that we're over-doing this. The ALS
on the lm3533 isn't a general purpose sensor. It's simply a way to
control the leds and backlights of that device. So what you do is to 
determine the full-scale current (max current at maximum brightness 0xff
in this case -- set in board file). Then the ALS input range is divided
in 5 zones, and for each zone you set a brightness as a percent of the
full-scale current. You relly don't care about amps (except for the
maximum determined by the setup).

The equation's for determining the current are available in the
datasheets however, but they depend on which mapping mode (linear or
exponential) and can also be effected by PWM-input duty cycle etc. For
this particular device, I really don't see the point in trying to
determine actual current in amps in all these settings.

Note also that the actual output current cannot be determined in the
ALS as the required factors are only set/known in the led/backlight
devices (mapping mode, pwm-mode).

> >> Also arguably is it not the als that this is related to, but rather
> >> the light source?
> > 
> > Well, it would be a raw output, mapping the measured LUX.
> Fair enough, though I wonder if we are stepping on led / backlight
> classes stuff with this.

Keeping the target sets and the mapper terminology could still be an
option. 

ALS input mode is a special mode of the LEDs and backlights which
overrides the direct current control. It's indeed a special-purpose
device.

> >> A quick datasheet browse says that these are current targets? If so I
> >> wonder if we can make that explicit...  Could treat them as 3 output
> >> channels and have indexed values like we do for frequencies in dds
> >> devices (where external hardware is controlling them.
> > 
> > I think I like this.
> > 
> >> Hmm. lets see.
> >>
> >> out_currentX_currentY_raw
> >> (the double naming is a bit clunky but corresponds to frequency devices
> >> where we have
> >> out_altvoltageX_frequencyY_raw
> >>
> >> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
> >> as indexed values they can take 0,1,2,3,4
> >> out_currentX_raw is not read only and gives you the current for whichever
> >> zone the device is currently in.
> > 
> > I take it you mean "out_currentX_raw is read only".
> yes. I do indeed. oops.
> > 
> >> This may seem convoluted but I'd really rather have something generalizable
> >> for this if we possibly can.  We'd still need documentation to say what is
> >> controlling these, but at least they'd fit within our more general abi.
> >>
> >> What do you think?
> > 
> > I like it. From a user perspective it's mainly a change of names (and
> > indexes). But conceptually it's perhaps more clear: the als maps it's
> > input to an output current, which, just like a PWM-signal, could be used
> > as an input to the LEDs and backlights to determine their outputs.
> > 
> > I'd have to modify the LED and backlight interfaces somewhat to reflect
> > the changed indexes and terminology (e.g. "output channel" rather
> > than "ALS mapper"). Something like:
> > 
> > 	als_en		-- enable als input mode (0,1)
> > 	als_channel	-- which out_currentX to use as input (0..2) in
> > 			   als input mode
> Not entirely sure I'm happy with this. Would rather it was done
> on a per channel basis, so in_illuminance0_ *
> From point of view of sensors I don't really care if it is an als or
> measuring something active (hence inherently not ambient!)

Not sure I understood that. What is it you don't like about it? You need
to keep in mind what is actually there; three sets of target values per
zone of which one set is dedicated to the first backlight device. That
means, that the ALS mapper (or channel if we want to use that
terminology) needs to be set in the actual devices and not the other way
round. [ The als_en and als_channel attributes would belong to the
led/backlight devices. ]

What did you mean by "per channel basis, so in_illuminance0_ *"?

Again it's a special purpose device -- the lm3533 leds and backlights
are controlled in hw by the on-chip als interface. We can't just use any
generic iio device for this.

> Silly question but how is the out_current related to the input in als
> mode?

1. raw adc input is averaged
2. mean adc input is mapped to zone using zone registers ("thresholds")
3. zone is mapped to a percentage using ALS mapper registers, that is, 
   three sets of 8-bit values per zone. Which set is used can be set on
   a per-device (led, backlight) basis
4. the percentage is applied to the per-device full-scale current to
   determine the actual output. How this mapping is done depends on if
   linear or exponential mapping mode is set (also per-device).

[ And things can get even more complicated if the devices are in
  PWM-mode, but this is roughly the full picture. ]

And all of the above is done in hw.

So, we're only using out_current channels because it maybe fits iio
better. For anyone familiar with the lm3533 this may even just confuse
things. 

There are only the three tables of values that maps a zone to an output
percentage (e.g. half brightness in zone 2).

> > So to summarise, we get the following new sysfs-entries for the ALS
> > (where the first set replace targetX_Y):
> > 
> > 	out_currentX_currentY_raw	r/w, (0..255), X in 0..2, Y in 0..4
> > 	out_currentX_raw		ro (0..255), X in 0..2
> > 
> > Is there any support in core for the first set or should I simply
> > rename my target attributes?
> No support in the core yet for this sort of thing..
> Michael, any thoughts on this? In a sense it's very similar to
> out_altvoltageX_frequencyY_raw etc...
> > 
> > Thanks,
> > Johan
> 

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

* Re: [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-21 22:07               ` Johan Hovold
@ 2012-05-22  7:13                 ` Jonathan Cameron
  2012-05-22  9:09                   ` Johan Hovold
  0 siblings, 1 reply; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-22  7:13 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	Hennerich, Michael

On 5/21/2012 11:07 PM, Johan Hovold wrote:
> On Mon, May 21, 2012 at 05:37:11PM +0100, Jonathan Cameron wrote:
>> Michael cc'd for comments on core support of some stuff that is also
>> in frequency drivers down the end of the email.
>>
>> On 05/21/2012 10:50 AM, Johan Hovold wrote:
>>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>>>> Add sub-driver for the ambient-light-sensor interface on National
>>>>> Semiconductor / TI LM3533 lighting power chips.
>>>>>
>>>>> The sensor interface can be used to control the LEDs and backlights of
>>>>> the chip through defining five light zones and three sets of
>>>>> corresponding brightness target levels.
>>>>>
>>>>> The driver provides raw and mean adc readings along with the current
>>>>> light zone through sysfs. A threshold event can be generated on zone
>>>>> changes.
>>>>
>>>> Hi Johan,
>>>>
>>>> I hate to be a pain with this one, but it's a complex beast and I'd
>>>> really like to get the interface right first time - particularly as
>>>> you are going in after the move out of staging.
>>>>
>>>>
>>>> Queries for you.
>>>> 1) Ordering in the probe function. Normally expect iio_device_register
>>>> to be the last call. Why not here?
>>>> 2) Worth combining enable / disable into one as very similar functions?
>>>> 3) Suspicious code in als_set_input_mode
>>>>
>>>> Naming of the target values.  I think we can make the naming of these
>>>> fit in much better with the normal abi which is going to be all for the
>>>> good in the long run.  They are basically current output channels
>>>> with a controllable set of steps (where we don't have direct control
>>>> of which one we are in).  This is very similar to the frequency controls
>>>> on some of Analog's dds that we have a well defined interface for.
>>>>
>>>> More detail below, but in summary.
>>>>
>>>> out_currentX_currentY_raw for channel X value for zone Y.
>>>>
>>>> Jonathan
>>>>>
>>>>> Signed-off-by: Johan Hovold<jhovold@gmail.com>
>>>>> ---
>>>>>
>>>>> Note that addition of r_select to the platform data probably needs to go
>>>>> in via mfd.
>>>>>
>>>>>
>>>>> v2:
>>>>>   - reimplement using iio
>>>>>   - add sysfs-ABI documentation
>>>>> v3
>>>>>   - use indexed channel
>>>>>   - fix sysfs-ABI documentation typo and style
>>>>>   - replace gain attribute with in_illuminance0_calibscale
>>>>>   - add calibscale to platform data
>>>>>   - fix adc register definitions
>>>>>   - replace to_lm3533_dev_attr macro with inline function
>>>>>   - fix device used for error reporting at irq allocation
>>>>>   - use iio device for error reporting during setup/enable
>>>>>   - rebase on staging-next
>>>>>     - fix header include paths
>>>>>     - use dev_to_iio_dev
>>>>>     - add IIO_CHAN_INFO_RAW to info mask
>>>>>     - use iio_device_{alloc,free}
>>>>> v4
>>>>>   - move to driver/iio/light
>>>>>   - add events/in_illuminance0_threshY_hysteresis attributes
>>>>>   - fix device zone-boundary quirkiness
>>>>>   - clean up attribute handling
>>>>>   - replace calibscale with device-specific r_select attribute
>>>>>
>>>>>
>>>>>   .../ABI/testing/sysfs-bus-iio-light-lm3533-als     |   64 ++
>>>>>   drivers/iio/Kconfig                                |    1 +
>>>>>   drivers/iio/Makefile                               |    1 +
>>>>>   drivers/iio/light/Kconfig                          |   22 +
>>>>>   drivers/iio/light/Makefile                         |    5 +
>>>>>   drivers/iio/light/lm3533-als.c                     |  941 ++++++++++++++++++++
>>>>>   include/linux/mfd/lm3533.h                         |    1 +
>>>>>   7 files changed, 1035 insertions(+), 0 deletions(-)
>>>>>   create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>>>   create mode 100644 drivers/iio/light/Kconfig
>>>>>   create mode 100644 drivers/iio/light/Makefile
>>>>>   create mode 100644 drivers/iio/light/lm3533-als.c
>>>>>
>>>>> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>>> new file mode 100644
>>>>> index 0000000..7ea1770
>>>>> --- /dev/null
>>>>> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>>> @@ -0,0 +1,64 @@
>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/r_select
>>>>> +Date:		April 2012
>>>>> +KernelVersion:	3.5
>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>> +Description:
>>>>> +		Set the ALS internal pull-down resistor for analog input mode
>>>>> +		(1..127), such that,
>>>>> +
>>>>> +		R_als = 200000 / r_select	(ohm)
>>>>> +
>>>>> +		This setting is ignored in PWM-mode (input is always high
>>>>> +		impedance in PWM-mode).
>>>>> +
>>>>> +What:		/sys/.../events/in_illuminance0_thresh_either_en
>>>>> +Date:		April 2012
>>>>> +KernelVersion:	3.5
>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>> +Description:
>>>>> +		Event generated when channel passes one of the four thresholds
>>>>> +		in each direction (rising|falling) and a zone change occurs.
>>>>> +		The corresponding light zone can be read from
>>>>> +		in_illuminance0_zone.
>>>>> +
>>>>> +What:		/sys/.../events/in_illuminance0_threshY_hysteresis
>>>>> +Date:		May 2012
>>>>> +KernelVersion:	3.5
>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>> +Description:
>>>>> +		Get the hysteresis for thresholds Y, that is,
>>>>> +
>>>>> +		threshY_hysteresis = threshY_raising - threshY_falling
>>>>> +
>>>>> +What:		/sys/.../events/illuminance_threshY_falling_value
>>>>> +What:		/sys/.../events/illuminance_threshY_raising_value
>>>>> +Date:		April 2012
>>>>> +KernelVersion:	3.5
>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>> +Description:
>>>>> +		Specifies the value of threshold that the device is
>>>>> +		comparing against for the events enabled by
>>>>> +		in_illuminance0_thresh_either_en, where Y in 0..3.
>>>>> +
>>>>> +		Note that threshY_falling must be less than or equal to
>>>>> +		threshY_raising.
>>>>> +
>>>>> +		These thresholds correspond to the eight zone-boundary
>>>>> +		registers (boundaryY_{low,high}) and defines the five light
>>>>> +		zones.
>>>>> +
>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
>>>>> +Date:		April 2012
>>>>> +KernelVersion:	3.5
>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>> +Description:
>>>>> +		Get the current light zone (0..4) as defined by the
>>>>> +		in_illuminance0_threshY_{falling,rising} thresholds.
>>>>> +
>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/targetY_Z
>>>>> +Date:		April 2012
>>>>> +KernelVersion:	3.5
>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>> +Description:
>>>>> +		Set the target brightness for ALS-mapper Y in light zone Z
>>>>> +		(0..255), where Y in 1..3 and Z in 0..4.
>>>>
>>>> What are the units of this?
>>>
>>> The datasheet reads "percent of the full-scale current" (actually depends
>>> somewhat on whether the als is in linear or exponential mode). When the
>>> leds or backlights are in PWM-mode (not the ALS necessarily), these
>>> values are interpreted as a scale factor which is applied to the output
>>> current determined by the PWM-signal.
>>>
>>> Either way it could indeed be considered a raw output current (which
>>> could be manipulated later by various factors).
>> Fine.... Theoretically if at all possible we'd want the conversion
>> factors to get it to an actual current (in amps) to be available though.
>> (tend to relax that if there are unknowable elements or they aren't
>> specified by board file etc).
>
> Hmmm. I'm starting to get a feeling that we're over-doing this. The ALS
> on the lm3533 isn't a general purpose sensor. It's simply a way to
> control the leds and backlights of that device. So what you do is to
> determine the full-scale current (max current at maximum brightness 0xff
> in this case -- set in board file). Then the ALS input range is divided
> in 5 zones, and for each zone you set a brightness as a percent of the
> full-scale current. You relly don't care about amps (except for the
> maximum determined by the setup).
Then no need to provide scale etc.
>
> The equation's for determining the current are available in the
> datasheets however, but they depend on which mapping mode (linear or
> exponential) and can also be effected by PWM-input duty cycle etc. For
> this particular device, I really don't see the point in trying to
> determine actual current in amps in all these settings.
We can always add it later if anyone cares.
>
> Note also that the actual output current cannot be determined in the
> ALS as the required factors are only set/known in the led/backlight
> devices (mapping mode, pwm-mode).
Could query it back if it was useful, but sounds like probably not.
If we don't provide the information it can't be wrong...

I'd basically missunderstood where the division between the sensor and
the led/backlight drivers lay.  I think I'm happy now with where you 
have it.
>
>>>> Also arguably is it not the als that this is related to, but rather
>>>> the light source?
>>>
>>> Well, it would be a raw output, mapping the measured LUX.
>> Fair enough, though I wonder if we are stepping on led / backlight
>> classes stuff with this.
>
> Keeping the target sets and the mapper terminology could still be an
> option.
>
> ALS input mode is a special mode of the LEDs and backlights which
> overrides the direct current control. It's indeed a special-purpose
> device.
Yup, I understand you now!
>
>>>> A quick datasheet browse says that these are current targets? If so I
>>>> wonder if we can make that explicit...  Could treat them as 3 output
>>>> channels and have indexed values like we do for frequencies in dds
>>>> devices (where external hardware is controlling them.
>>>
>>> I think I like this.
>>>
>>>> Hmm. lets see.
>>>>
>>>> out_currentX_currentY_raw
>>>> (the double naming is a bit clunky but corresponds to frequency devices
>>>> where we have
>>>> out_altvoltageX_frequencyY_raw
>>>>
>>>> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
>>>> as indexed values they can take 0,1,2,3,4
>>>> out_currentX_raw is not read only and gives you the current for whichever
>>>> zone the device is currently in.
>>>
>>> I take it you mean "out_currentX_raw is read only".
>> yes. I do indeed. oops.
>>>
>>>> This may seem convoluted but I'd really rather have something generalizable
>>>> for this if we possibly can.  We'd still need documentation to say what is
>>>> controlling these, but at least they'd fit within our more general abi.
>>>>
>>>> What do you think?
>>>
>>> I like it. From a user perspective it's mainly a change of names (and
>>> indexes). But conceptually it's perhaps more clear: the als maps it's
>>> input to an output current, which, just like a PWM-signal, could be used
>>> as an input to the LEDs and backlights to determine their outputs.
>>>
>>> I'd have to modify the LED and backlight interfaces somewhat to reflect
>>> the changed indexes and terminology (e.g. "output channel" rather
>>> than "ALS mapper"). Something like:
>>>
>>> 	als_en		-- enable als input mode (0,1)
>>> 	als_channel	-- which out_currentX to use as input (0..2) in
>>> 			   als input mode
>> Not entirely sure I'm happy with this. Would rather it was done
>> on a per channel basis, so in_illuminance0_ *
>>  From point of view of sensors I don't really care if it is an als or
>> measuring something active (hence inherently not ambient!)
>
> Not sure I understood that. What is it you don't like about it?
  You need
> to keep in mind what is actually there; three sets of target values per
> zone of which one set is dedicated to the first backlight device. That
> means, that the ALS mapper (or channel if we want to use that
> terminology) needs to be set in the actual devices and not the other way
> round. [ The als_en and als_channel attributes would belong to the
> led/backlight devices. ]
Ah. THat last bit in brackets is what I'd missed ;)  That's fine with me 
then.
>
> What did you mean by "per channel basis, so in_illuminance0_ *"?
>
> Again it's a special purpose device -- the lm3533 leds and backlights
> are controlled in hw by the on-chip als interface. We can't just use any
> generic iio device for this.
sure. Had missunderstood completely.
>
>> Silly question but how is the out_current related to the input in als
>> mode?
>
> 1. raw adc input is averaged
> 2. mean adc input is mapped to zone using zone registers ("thresholds")
> 3. zone is mapped to a percentage using ALS mapper registers, that is,
>     three sets of 8-bit values per zone. Which set is used can be set on
>     a per-device (led, backlight) basis
> 4. the percentage is applied to the per-device full-scale current to
>     determine the actual output. How this mapping is done depends on if
>     linear or exponential mapping mode is set (also per-device).
>
All makes sense now.  Thanks for the clarification.

> [ And things can get even more complicated if the devices are in
>    PWM-mode, but this is roughly the full picture. ]
>
> And all of the above is done in hw.
>
> So, we're only using out_current channels because it maybe fits iio
> better. For anyone familiar with the lm3533 this may even just confuse
> things.
I'm realy keen to do this primarily because we may well hit similar 
devices at a later stage and it's much nicer to generalize earlier
than go through supporting the old method in parallel for years...
What we have here is general enough to support a wide range of possible
devices so lets go with it.
>
> There are only the three tables of values that maps a zone to an output
> percentage (e.g. half brightness in zone 2).

One last question.  Are the als -> LEd /backlight mapping something a 
device user would ever change?  If not the most general option would be
to use the iio inkernel mapping interfaces to do the assignments. Gives
you a clean easy way to allow reading back of current brightness in the
led drivers etc...
>
>>> So to summarise, we get the following new sysfs-entries for the ALS
>>> (where the first set replace targetX_Y):
>>>
>>> 	out_currentX_currentY_raw	r/w, (0..255), X in 0..2, Y in 0..4
>>> 	out_currentX_raw		ro (0..255), X in 0..2
>>>
>>> Is there any support in core for the first set or should I simply
>>> rename my target attributes?
>> No support in the core yet for this sort of thing..
>> Michael, any thoughts on this? In a sense it's very similar to
>> out_altvoltageX_frequencyY_raw etc...
>>>
>>> Thanks,
>>> Johan
>>


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

* Re: [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-21 16:37             ` Jonathan Cameron
  2012-05-21 22:07               ` Johan Hovold
@ 2012-05-22  7:45               ` Michael Hennerich
  2012-05-22  7:49                 ` Jonathan Cameron
  1 sibling, 1 reply; 131+ messages in thread
From: Michael Hennerich @ 2012-05-22  7:45 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Johan Hovold, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio

On 05/21/2012 06:37 PM, Jonathan Cameron wrote:
> Michael cc'd for comments on core support of some stuff that is also
> in frequency drivers down the end of the email.
>
> On 05/21/2012 10:50 AM, Johan Hovold wrote:
>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>> So to summarise, we get the following new sysfs-entries for the ALS
>>> (where the first set replace targetX_Y):
>>>
>>> 	out_currentX_currentY_raw	r/w, (0..255), X in 0..2, Y in 0..4
>>> 	out_currentX_raw		ro (0..255), X in 0..2
>>>
>>> Is there any support in core for the first set or should I simply
>>> rename my target attributes?
> No support in the core yet for this sort of thing..
> Michael, any thoughts on this? In a sense it's very similar to
> out_altvoltageX_frequencyY_raw etc...
>
Hi,

Exactly it's similar to what we have on the DDS parts.
The DDS parts would also benefit from a second index.

Can someone think of an use case where we have 'differential' or 'modified'
together with this pattern?

If not we could use channel2 and introduce an new flag, but I fear
that we end up using channel2 for too many things.
So we should better introduce an new variable?

-- 
Greetings,
Michael

--
Analog Devices GmbH      Wilhelm-Wagenfeld-Str. 6      80807 Muenchen
Sitz der Gesellschaft: Muenchen; Registergericht: Muenchen HRB 40368;
Geschaeftsfuehrer:Dr.Carsten Suckrow, Thomas Wessel, William A. Martin,
Margaret Seif



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

* Re: [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-22  7:45               ` Michael Hennerich
@ 2012-05-22  7:49                 ` Jonathan Cameron
  2012-05-22  8:11                   ` Michael Hennerich
  0 siblings, 1 reply; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-22  7:49 UTC (permalink / raw)
  To: michael.hennerich
  Cc: Jonathan Cameron, Johan Hovold, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio

On 5/22/2012 8:45 AM, Michael Hennerich wrote:
> On 05/21/2012 06:37 PM, Jonathan Cameron wrote:
>> Michael cc'd for comments on core support of some stuff that is also
>> in frequency drivers down the end of the email.
>>
>> On 05/21/2012 10:50 AM, Johan Hovold wrote:
>>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>>> So to summarise, we get the following new sysfs-entries for the ALS
>>>> (where the first set replace targetX_Y):
>>>>
>>>> out_currentX_currentY_raw r/w, (0..255), X in 0..2, Y in 0..4
>>>> out_currentX_raw ro (0..255), X in 0..2
>>>>
>>>> Is there any support in core for the first set or should I simply
>>>> rename my target attributes?
>> No support in the core yet for this sort of thing..
>> Michael, any thoughts on this? In a sense it's very similar to
>> out_altvoltageX_frequencyY_raw etc...
>>
> Hi,
>
> Exactly it's similar to what we have on the DDS parts.
> The DDS parts would also benefit from a second index.
>
> Can someone think of an use case where we have 'differential' or 'modified'
> together with this pattern?
>
> If not we could use channel2 and introduce an new flag, but I fear
> that we end up using channel2 for too many things.
> So we should better introduce an new variable?
>
New variable. Tedious but channel2 is getting rather too overloaded.



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

* Re: [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-22  7:49                 ` Jonathan Cameron
@ 2012-05-22  8:11                   ` Michael Hennerich
  2012-05-22  8:20                     ` Jonathan Cameron
  0 siblings, 1 reply; 131+ messages in thread
From: Michael Hennerich @ 2012-05-22  8:11 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Jonathan Cameron, Johan Hovold, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio

On 05/22/2012 09:49 AM, Jonathan Cameron wrote:
> On 5/22/2012 8:45 AM, Michael Hennerich wrote:
>> On 05/21/2012 06:37 PM, Jonathan Cameron wrote:
>>> Michael cc'd for comments on core support of some stuff that is also
>>> in frequency drivers down the end of the email.
>>>
>>> On 05/21/2012 10:50 AM, Johan Hovold wrote:
>>>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>>>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>>>> So to summarise, we get the following new sysfs-entries for the ALS
>>>>> (where the first set replace targetX_Y):
>>>>>
>>>>> out_currentX_currentY_raw r/w, (0..255), X in 0..2, Y in 0..4
>>>>> out_currentX_raw ro (0..255), X in 0..2
>>>>>
>>>>> Is there any support in core for the first set or should I simply
>>>>> rename my target attributes?
>>> No support in the core yet for this sort of thing..
>>> Michael, any thoughts on this? In a sense it's very similar to
>>> out_altvoltageX_frequencyY_raw etc...
>>>
>> Hi,
>>
>> Exactly it's similar to what we have on the DDS parts.
>> The DDS parts would also benefit from a second index.
>>
>> Can someone think of an use case where we have 'differential' or 'modified'
>> together with this pattern?
>>
>> If not we could use channel2 and introduce an new flag, but I fear
>> that we end up using channel2 for too many things.
>> So we should better introduce an new variable?
>>
> New variable. Tedious but channel2 is getting rather too overloaded.
>
>
>
Blah channel2 is not an option at all.
We need to generate an index on the postfix.
And we can't pass this information via info_mask.
So ext_info could be your friend?

-- 
Greetings,
Michael

--
Analog Devices GmbH      Wilhelm-Wagenfeld-Str. 6      80807 Muenchen
Sitz der Gesellschaft: Muenchen; Registergericht: Muenchen HRB 40368;
Geschaeftsfuehrer:Dr.Carsten Suckrow, Thomas Wessel, William A. Martin,
Margaret Seif



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

* Re: [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-22  8:11                   ` Michael Hennerich
@ 2012-05-22  8:20                     ` Jonathan Cameron
  0 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-22  8:20 UTC (permalink / raw)
  To: michael.hennerich
  Cc: Jonathan Cameron, Johan Hovold, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio

On 5/22/2012 9:11 AM, Michael Hennerich wrote:
> On 05/22/2012 09:49 AM, Jonathan Cameron wrote:
>> On 5/22/2012 8:45 AM, Michael Hennerich wrote:
>>> On 05/21/2012 06:37 PM, Jonathan Cameron wrote:
>>>> Michael cc'd for comments on core support of some stuff that is also
>>>> in frequency drivers down the end of the email.
>>>>
>>>> On 05/21/2012 10:50 AM, Johan Hovold wrote:
>>>>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>>>>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>>>>> So to summarise, we get the following new sysfs-entries for the ALS
>>>>>> (where the first set replace targetX_Y):
>>>>>>
>>>>>> out_currentX_currentY_raw r/w, (0..255), X in 0..2, Y in 0..4
>>>>>> out_currentX_raw ro (0..255), X in 0..2
>>>>>>
>>>>>> Is there any support in core for the first set or should I simply
>>>>>> rename my target attributes?
>>>> No support in the core yet for this sort of thing..
>>>> Michael, any thoughts on this? In a sense it's very similar to
>>>> out_altvoltageX_frequencyY_raw etc...
>>>>
>>> Hi,
>>>
>>> Exactly it's similar to what we have on the DDS parts.
>>> The DDS parts would also benefit from a second index.
>>>
>>> Can someone think of an use case where we have 'differential' or
>>> 'modified'
>>> together with this pattern?
>>>
>>> If not we could use channel2 and introduce an new flag, but I fear
>>> that we end up using channel2 for too many things.
>>> So we should better introduce an new variable?
>>>
>> New variable. Tedious but channel2 is getting rather too overloaded.
>>
>>
>>
> Blah channel2 is not an option at all.
Gah. Good point.  Not enough coffee this morning...
> We need to generate an index on the postfix.
> And we can't pass this information via info_mask.
> So ext_info could be your friend?
Short term yes, though we may want more specific support for this
down the line (to make for coherent in kernel interface). Not yet
sure what form that will take though.

>


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

* Re: [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-22  7:13                 ` Jonathan Cameron
@ 2012-05-22  9:09                   ` Johan Hovold
  2012-05-22  9:15                     ` Jonathan Cameron
  0 siblings, 1 reply; 131+ messages in thread
From: Johan Hovold @ 2012-05-22  9:09 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Johan Hovold, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Greg Kroah-Hartman, Florian Tobias Schandinat,
	Arnd Bergmann, Andrew Morton, Mark Brown, linux-doc,
	linux-kernel, linux-iio, Hennerich, Michael

On Tue, May 22, 2012 at 08:13:12AM +0100, Jonathan Cameron wrote:
> On 5/21/2012 11:07 PM, Johan Hovold wrote:
> > On Mon, May 21, 2012 at 05:37:11PM +0100, Jonathan Cameron wrote:
> >> Michael cc'd for comments on core support of some stuff that is also
> >> in frequency drivers down the end of the email.
> >>
> >> On 05/21/2012 10:50 AM, Johan Hovold wrote:
> >>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
> >>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
> >>>>> Add sub-driver for the ambient-light-sensor interface on National
> >>>>> Semiconductor / TI LM3533 lighting power chips.
> >>>>>
> >>>>> The sensor interface can be used to control the LEDs and backlights of
> >>>>> the chip through defining five light zones and three sets of
> >>>>> corresponding brightness target levels.
> >>>>>
> >>>>> The driver provides raw and mean adc readings along with the current
> >>>>> light zone through sysfs. A threshold event can be generated on zone
> >>>>> changes.
> >>>>
> >>>> Hi Johan,
> >>>>
> >>>> I hate to be a pain with this one, but it's a complex beast and I'd
> >>>> really like to get the interface right first time - particularly as
> >>>> you are going in after the move out of staging.
> >>>>
> >>>>
> >>>> Queries for you.
> >>>> 1) Ordering in the probe function. Normally expect iio_device_register
> >>>> to be the last call. Why not here?
> >>>> 2) Worth combining enable / disable into one as very similar functions?
> >>>> 3) Suspicious code in als_set_input_mode
> >>>>
> >>>> Naming of the target values.  I think we can make the naming of these
> >>>> fit in much better with the normal abi which is going to be all for the
> >>>> good in the long run.  They are basically current output channels
> >>>> with a controllable set of steps (where we don't have direct control
> >>>> of which one we are in).  This is very similar to the frequency controls
> >>>> on some of Analog's dds that we have a well defined interface for.
> >>>>
> >>>> More detail below, but in summary.
> >>>>
> >>>> out_currentX_currentY_raw for channel X value for zone Y.
> >>>>
> >>>> Jonathan
> >>>>>
> >>>>> Signed-off-by: Johan Hovold<jhovold@gmail.com>
> >>>>> ---
> >>>>>
> >>>>> Note that addition of r_select to the platform data probably needs to go
> >>>>> in via mfd.
> >>>>>
> >>>>>
> >>>>> v2:
> >>>>>   - reimplement using iio
> >>>>>   - add sysfs-ABI documentation
> >>>>> v3
> >>>>>   - use indexed channel
> >>>>>   - fix sysfs-ABI documentation typo and style
> >>>>>   - replace gain attribute with in_illuminance0_calibscale
> >>>>>   - add calibscale to platform data
> >>>>>   - fix adc register definitions
> >>>>>   - replace to_lm3533_dev_attr macro with inline function
> >>>>>   - fix device used for error reporting at irq allocation
> >>>>>   - use iio device for error reporting during setup/enable
> >>>>>   - rebase on staging-next
> >>>>>     - fix header include paths
> >>>>>     - use dev_to_iio_dev
> >>>>>     - add IIO_CHAN_INFO_RAW to info mask
> >>>>>     - use iio_device_{alloc,free}
> >>>>> v4
> >>>>>   - move to driver/iio/light
> >>>>>   - add events/in_illuminance0_threshY_hysteresis attributes
> >>>>>   - fix device zone-boundary quirkiness
> >>>>>   - clean up attribute handling
> >>>>>   - replace calibscale with device-specific r_select attribute
> >>>>>
> >>>>>
> >>>>>   .../ABI/testing/sysfs-bus-iio-light-lm3533-als     |   64 ++
> >>>>>   drivers/iio/Kconfig                                |    1 +
> >>>>>   drivers/iio/Makefile                               |    1 +
> >>>>>   drivers/iio/light/Kconfig                          |   22 +
> >>>>>   drivers/iio/light/Makefile                         |    5 +
> >>>>>   drivers/iio/light/lm3533-als.c                     |  941 ++++++++++++++++++++
> >>>>>   include/linux/mfd/lm3533.h                         |    1 +
> >>>>>   7 files changed, 1035 insertions(+), 0 deletions(-)
> >>>>>   create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >>>>>   create mode 100644 drivers/iio/light/Kconfig
> >>>>>   create mode 100644 drivers/iio/light/Makefile
> >>>>>   create mode 100644 drivers/iio/light/lm3533-als.c
> >>>>>
> >>>>> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >>>>> new file mode 100644
> >>>>> index 0000000..7ea1770
> >>>>> --- /dev/null
> >>>>> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >>>>> @@ -0,0 +1,64 @@
> >>>>> +What:		/sys/bus/iio/devices/iio:deviceX/r_select
> >>>>> +Date:		April 2012
> >>>>> +KernelVersion:	3.5
> >>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>> +Description:
> >>>>> +		Set the ALS internal pull-down resistor for analog input mode
> >>>>> +		(1..127), such that,
> >>>>> +
> >>>>> +		R_als = 200000 / r_select	(ohm)
> >>>>> +
> >>>>> +		This setting is ignored in PWM-mode (input is always high
> >>>>> +		impedance in PWM-mode).
> >>>>> +
> >>>>> +What:		/sys/.../events/in_illuminance0_thresh_either_en
> >>>>> +Date:		April 2012
> >>>>> +KernelVersion:	3.5
> >>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>> +Description:
> >>>>> +		Event generated when channel passes one of the four thresholds
> >>>>> +		in each direction (rising|falling) and a zone change occurs.
> >>>>> +		The corresponding light zone can be read from
> >>>>> +		in_illuminance0_zone.
> >>>>> +
> >>>>> +What:		/sys/.../events/in_illuminance0_threshY_hysteresis
> >>>>> +Date:		May 2012
> >>>>> +KernelVersion:	3.5
> >>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>> +Description:
> >>>>> +		Get the hysteresis for thresholds Y, that is,
> >>>>> +
> >>>>> +		threshY_hysteresis = threshY_raising - threshY_falling
> >>>>> +
> >>>>> +What:		/sys/.../events/illuminance_threshY_falling_value
> >>>>> +What:		/sys/.../events/illuminance_threshY_raising_value
> >>>>> +Date:		April 2012
> >>>>> +KernelVersion:	3.5
> >>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>> +Description:
> >>>>> +		Specifies the value of threshold that the device is
> >>>>> +		comparing against for the events enabled by
> >>>>> +		in_illuminance0_thresh_either_en, where Y in 0..3.
> >>>>> +
> >>>>> +		Note that threshY_falling must be less than or equal to
> >>>>> +		threshY_raising.
> >>>>> +
> >>>>> +		These thresholds correspond to the eight zone-boundary
> >>>>> +		registers (boundaryY_{low,high}) and defines the five light
> >>>>> +		zones.
> >>>>> +
> >>>>> +What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> >>>>> +Date:		April 2012
> >>>>> +KernelVersion:	3.5
> >>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>> +Description:
> >>>>> +		Get the current light zone (0..4) as defined by the
> >>>>> +		in_illuminance0_threshY_{falling,rising} thresholds.
> >>>>> +
> >>>>> +What:		/sys/bus/iio/devices/iio:deviceX/targetY_Z
> >>>>> +Date:		April 2012
> >>>>> +KernelVersion:	3.5
> >>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
> >>>>> +Description:
> >>>>> +		Set the target brightness for ALS-mapper Y in light zone Z
> >>>>> +		(0..255), where Y in 1..3 and Z in 0..4.
> >>>>
> >>>> What are the units of this?
> >>>
> >>> The datasheet reads "percent of the full-scale current" (actually depends
> >>> somewhat on whether the als is in linear or exponential mode). When the
> >>> leds or backlights are in PWM-mode (not the ALS necessarily), these
> >>> values are interpreted as a scale factor which is applied to the output
> >>> current determined by the PWM-signal.
> >>>
> >>> Either way it could indeed be considered a raw output current (which
> >>> could be manipulated later by various factors).
> >> Fine.... Theoretically if at all possible we'd want the conversion
> >> factors to get it to an actual current (in amps) to be available though.
> >> (tend to relax that if there are unknowable elements or they aren't
> >> specified by board file etc).
> >
> > Hmmm. I'm starting to get a feeling that we're over-doing this. The ALS
> > on the lm3533 isn't a general purpose sensor. It's simply a way to
> > control the leds and backlights of that device. So what you do is to
> > determine the full-scale current (max current at maximum brightness 0xff
> > in this case -- set in board file). Then the ALS input range is divided
> > in 5 zones, and for each zone you set a brightness as a percent of the
> > full-scale current. You relly don't care about amps (except for the
> > maximum determined by the setup).
> Then no need to provide scale etc.

Good.

> > The equation's for determining the current are available in the
> > datasheets however, but they depend on which mapping mode (linear or
> > exponential) and can also be effected by PWM-input duty cycle etc. For
> > this particular device, I really don't see the point in trying to
> > determine actual current in amps in all these settings.
> We can always add it later if anyone cares.
> >
> > Note also that the actual output current cannot be determined in the
> > ALS as the required factors are only set/known in the led/backlight
> > devices (mapping mode, pwm-mode).
> Could query it back if it was useful, but sounds like probably not.
> If we don't provide the information it can't be wrong...

Problem is that we would have three out_currentY_raw channels generating
up to five different output currents depending on the configuration of
the five controlled devices...

> I'd basically missunderstood where the division between the sensor and
> the led/backlight drivers lay.  I think I'm happy now with where you 
> have it.

Great.

> >
> >>>> Also arguably is it not the als that this is related to, but rather
> >>>> the light source?
> >>>
> >>> Well, it would be a raw output, mapping the measured LUX.
> >> Fair enough, though I wonder if we are stepping on led / backlight
> >> classes stuff with this.
> >
> > Keeping the target sets and the mapper terminology could still be an
> > option.
> >
> > ALS input mode is a special mode of the LEDs and backlights which
> > overrides the direct current control. It's indeed a special-purpose
> > device.
> Yup, I understand you now!
> >
> >>>> A quick datasheet browse says that these are current targets? If so I
> >>>> wonder if we can make that explicit...  Could treat them as 3 output
> >>>> channels and have indexed values like we do for frequencies in dds
> >>>> devices (where external hardware is controlling them.
> >>>
> >>> I think I like this.
> >>>
> >>>> Hmm. lets see.
> >>>>
> >>>> out_currentX_currentY_raw
> >>>> (the double naming is a bit clunky but corresponds to frequency devices
> >>>> where we have
> >>>> out_altvoltageX_frequencyY_raw
> >>>>
> >>>> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
> >>>> as indexed values they can take 0,1,2,3,4
> >>>> out_currentX_raw is not read only and gives you the current for whichever
> >>>> zone the device is currently in.
> >>>
> >>> I take it you mean "out_currentX_raw is read only".
> >> yes. I do indeed. oops.
> >>>
> >>>> This may seem convoluted but I'd really rather have something generalizable
> >>>> for this if we possibly can.  We'd still need documentation to say what is
> >>>> controlling these, but at least they'd fit within our more general abi.
> >>>>
> >>>> What do you think?
> >>>
> >>> I like it. From a user perspective it's mainly a change of names (and
> >>> indexes). But conceptually it's perhaps more clear: the als maps it's
> >>> input to an output current, which, just like a PWM-signal, could be used
> >>> as an input to the LEDs and backlights to determine their outputs.
> >>>
> >>> I'd have to modify the LED and backlight interfaces somewhat to reflect
> >>> the changed indexes and terminology (e.g. "output channel" rather
> >>> than "ALS mapper"). Something like:
> >>>
> >>> 	als_en		-- enable als input mode (0,1)
> >>> 	als_channel	-- which out_currentX to use as input (0..2) in
> >>> 			   als input mode
> >> Not entirely sure I'm happy with this. Would rather it was done
> >> on a per channel basis, so in_illuminance0_ *
> >>  From point of view of sensors I don't really care if it is an als or
> >> measuring something active (hence inherently not ambient!)
> >
> > Not sure I understood that. What is it you don't like about it?  You
> > need to keep in mind what is actually there; three sets of target
> > values per zone of which one set is dedicated to the first backlight
> > device. That means, that the ALS mapper (or channel if we want to
> > use that terminology) needs to be set in the actual devices and not
> > the other way round. [ The als_en and als_channel attributes would
> > belong to the led/backlight devices. ]
> Ah. THat last bit in brackets is what I'd missed ;)  That's fine with me 
> then.

Suspected that. ;)

> > What did you mean by "per channel basis, so in_illuminance0_ *"?
> >
> > Again it's a special purpose device -- the lm3533 leds and backlights
> > are controlled in hw by the on-chip als interface. We can't just use any
> > generic iio device for this.
> sure. Had missunderstood completely.
> >
> >> Silly question but how is the out_current related to the input in als
> >> mode?
> >
> > 1. raw adc input is averaged
> > 2. mean adc input is mapped to zone using zone registers ("thresholds")
> > 3. zone is mapped to a percentage using ALS mapper registers, that is,
> >     three sets of 8-bit values per zone. Which set is used can be set on
> >     a per-device (led, backlight) basis
> > 4. the percentage is applied to the per-device full-scale current to
> >     determine the actual output. How this mapping is done depends on if
> >     linear or exponential mapping mode is set (also per-device).
> >
> All makes sense now.  Thanks for the clarification.
> 
> > [ And things can get even more complicated if the devices are in
> >    PWM-mode, but this is roughly the full picture. ]
> >
> > And all of the above is done in hw.
> >
> > So, we're only using out_current channels because it maybe fits iio
> > better. For anyone familiar with the lm3533 this may even just confuse
> > things.
> I'm realy keen to do this primarily because we may well hit similar 
> devices at a later stage and it's much nicer to generalize earlier
> than go through supporting the old method in parallel for years...
> What we have here is general enough to support a wide range of possible
> devices so lets go with it.

Ok.

> > There are only the three tables of values that maps a zone to an output
> > percentage (e.g. half brightness in zone 2).
> 
> One last question.  Are the als -> LEd /backlight mapping something a 
> device user would ever change?  If not the most general option would be
> to use the iio inkernel mapping interfaces to do the assignments. Gives
> you a clean easy way to allow reading back of current brightness in the
> led drivers etc...

Are you referring to the als_channel settings above (i.e., which output
current is used as device input is ALS mode)? I think that may be the
case. You could have two sets of brightness values and quickly be able
to switch from one to another (e.g., for PM reasons).

Wouldn't it be possible to both allow users to change this and to add
support for iio in-kernel mappings later if needed?

The mapping has the following constraints by the way:

	backlight.0	->	out_current0
	backlight.1	->	out_current1
	led.[0-4]	->	out_current[1-2]

So it would only be possible to change the mapping for the leds and
only to select between two channels.

Is there anything preventing the led driver acting as IIO consumer to
set up a map to both channels and then decide which to read from
depending on als_channel?

> >>> So to summarise, we get the following new sysfs-entries for the ALS
> >>> (where the first set replace targetX_Y):
> >>>
> >>> 	out_currentX_currentY_raw	r/w, (0..255), X in 0..2, Y in 0..4
> >>> 	out_currentX_raw		ro (0..255), X in 0..2
> >>>
> >>> Is there any support in core for the first set or should I simply
> >>> rename my target attributes?
> >> No support in the core yet for this sort of thing..
> >> Michael, any thoughts on this? In a sense it's very similar to
> >> out_altvoltageX_frequencyY_raw etc...

Core support could be added later as long as we get the naming right in
lm3533, right?

I'm really keen to get this one into 3.5 (along with the rest of the
MFD-driver) and I know Greg usually sends his merge requests early...
But if I understand you correctly, it should be possible to apply
lm3533-als v5 now?

Thanks,
Johan

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

* Re: [PATCH v4] iio: add LM3533 ambient-light-sensor driver
  2012-05-22  9:09                   ` Johan Hovold
@ 2012-05-22  9:15                     ` Jonathan Cameron
  0 siblings, 0 replies; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-22  9:15 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio,
	Hennerich, Michael

On 5/22/2012 10:09 AM, Johan Hovold wrote:
> On Tue, May 22, 2012 at 08:13:12AM +0100, Jonathan Cameron wrote:
>> On 5/21/2012 11:07 PM, Johan Hovold wrote:
>>> On Mon, May 21, 2012 at 05:37:11PM +0100, Jonathan Cameron wrote:
>>>> Michael cc'd for comments on core support of some stuff that is also
>>>> in frequency drivers down the end of the email.
>>>>
>>>> On 05/21/2012 10:50 AM, Johan Hovold wrote:
>>>>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>>>>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>>>>>> Add sub-driver for the ambient-light-sensor interface on National
>>>>>>> Semiconductor / TI LM3533 lighting power chips.
>>>>>>>
>>>>>>> The sensor interface can be used to control the LEDs and backlights of
>>>>>>> the chip through defining five light zones and three sets of
>>>>>>> corresponding brightness target levels.
>>>>>>>
>>>>>>> The driver provides raw and mean adc readings along with the current
>>>>>>> light zone through sysfs. A threshold event can be generated on zone
>>>>>>> changes.
>>>>>>
>>>>>> Hi Johan,
>>>>>>
>>>>>> I hate to be a pain with this one, but it's a complex beast and I'd
>>>>>> really like to get the interface right first time - particularly as
>>>>>> you are going in after the move out of staging.
>>>>>>
>>>>>>
>>>>>> Queries for you.
>>>>>> 1) Ordering in the probe function. Normally expect iio_device_register
>>>>>> to be the last call. Why not here?
>>>>>> 2) Worth combining enable / disable into one as very similar functions?
>>>>>> 3) Suspicious code in als_set_input_mode
>>>>>>
>>>>>> Naming of the target values.  I think we can make the naming of these
>>>>>> fit in much better with the normal abi which is going to be all for the
>>>>>> good in the long run.  They are basically current output channels
>>>>>> with a controllable set of steps (where we don't have direct control
>>>>>> of which one we are in).  This is very similar to the frequency controls
>>>>>> on some of Analog's dds that we have a well defined interface for.
>>>>>>
>>>>>> More detail below, but in summary.
>>>>>>
>>>>>> out_currentX_currentY_raw for channel X value for zone Y.
>>>>>>
>>>>>> Jonathan
>>>>>>>
>>>>>>> Signed-off-by: Johan Hovold<jhovold@gmail.com>
>>>>>>> ---
>>>>>>>
>>>>>>> Note that addition of r_select to the platform data probably needs to go
>>>>>>> in via mfd.
>>>>>>>
>>>>>>>
>>>>>>> v2:
>>>>>>>    - reimplement using iio
>>>>>>>    - add sysfs-ABI documentation
>>>>>>> v3
>>>>>>>    - use indexed channel
>>>>>>>    - fix sysfs-ABI documentation typo and style
>>>>>>>    - replace gain attribute with in_illuminance0_calibscale
>>>>>>>    - add calibscale to platform data
>>>>>>>    - fix adc register definitions
>>>>>>>    - replace to_lm3533_dev_attr macro with inline function
>>>>>>>    - fix device used for error reporting at irq allocation
>>>>>>>    - use iio device for error reporting during setup/enable
>>>>>>>    - rebase on staging-next
>>>>>>>      - fix header include paths
>>>>>>>      - use dev_to_iio_dev
>>>>>>>      - add IIO_CHAN_INFO_RAW to info mask
>>>>>>>      - use iio_device_{alloc,free}
>>>>>>> v4
>>>>>>>    - move to driver/iio/light
>>>>>>>    - add events/in_illuminance0_threshY_hysteresis attributes
>>>>>>>    - fix device zone-boundary quirkiness
>>>>>>>    - clean up attribute handling
>>>>>>>    - replace calibscale with device-specific r_select attribute
>>>>>>>
>>>>>>>
>>>>>>>    .../ABI/testing/sysfs-bus-iio-light-lm3533-als     |   64 ++
>>>>>>>    drivers/iio/Kconfig                                |    1 +
>>>>>>>    drivers/iio/Makefile                               |    1 +
>>>>>>>    drivers/iio/light/Kconfig                          |   22 +
>>>>>>>    drivers/iio/light/Makefile                         |    5 +
>>>>>>>    drivers/iio/light/lm3533-als.c                     |  941 ++++++++++++++++++++
>>>>>>>    include/linux/mfd/lm3533.h                         |    1 +
>>>>>>>    7 files changed, 1035 insertions(+), 0 deletions(-)
>>>>>>>    create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>>>>>    create mode 100644 drivers/iio/light/Kconfig
>>>>>>>    create mode 100644 drivers/iio/light/Makefile
>>>>>>>    create mode 100644 drivers/iio/light/lm3533-als.c
>>>>>>>
>>>>>>> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>>>>> new file mode 100644
>>>>>>> index 0000000..7ea1770
>>>>>>> --- /dev/null
>>>>>>> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>>>>> @@ -0,0 +1,64 @@
>>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/r_select
>>>>>>> +Date:		April 2012
>>>>>>> +KernelVersion:	3.5
>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>> +Description:
>>>>>>> +		Set the ALS internal pull-down resistor for analog input mode
>>>>>>> +		(1..127), such that,
>>>>>>> +
>>>>>>> +		R_als = 200000 / r_select	(ohm)
>>>>>>> +
>>>>>>> +		This setting is ignored in PWM-mode (input is always high
>>>>>>> +		impedance in PWM-mode).
>>>>>>> +
>>>>>>> +What:		/sys/.../events/in_illuminance0_thresh_either_en
>>>>>>> +Date:		April 2012
>>>>>>> +KernelVersion:	3.5
>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>> +Description:
>>>>>>> +		Event generated when channel passes one of the four thresholds
>>>>>>> +		in each direction (rising|falling) and a zone change occurs.
>>>>>>> +		The corresponding light zone can be read from
>>>>>>> +		in_illuminance0_zone.
>>>>>>> +
>>>>>>> +What:		/sys/.../events/in_illuminance0_threshY_hysteresis
>>>>>>> +Date:		May 2012
>>>>>>> +KernelVersion:	3.5
>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>> +Description:
>>>>>>> +		Get the hysteresis for thresholds Y, that is,
>>>>>>> +
>>>>>>> +		threshY_hysteresis = threshY_raising - threshY_falling
>>>>>>> +
>>>>>>> +What:		/sys/.../events/illuminance_threshY_falling_value
>>>>>>> +What:		/sys/.../events/illuminance_threshY_raising_value
>>>>>>> +Date:		April 2012
>>>>>>> +KernelVersion:	3.5
>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>> +Description:
>>>>>>> +		Specifies the value of threshold that the device is
>>>>>>> +		comparing against for the events enabled by
>>>>>>> +		in_illuminance0_thresh_either_en, where Y in 0..3.
>>>>>>> +
>>>>>>> +		Note that threshY_falling must be less than or equal to
>>>>>>> +		threshY_raising.
>>>>>>> +
>>>>>>> +		These thresholds correspond to the eight zone-boundary
>>>>>>> +		registers (boundaryY_{low,high}) and defines the five light
>>>>>>> +		zones.
>>>>>>> +
>>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
>>>>>>> +Date:		April 2012
>>>>>>> +KernelVersion:	3.5
>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>> +Description:
>>>>>>> +		Get the current light zone (0..4) as defined by the
>>>>>>> +		in_illuminance0_threshY_{falling,rising} thresholds.
>>>>>>> +
>>>>>>> +What:		/sys/bus/iio/devices/iio:deviceX/targetY_Z
>>>>>>> +Date:		April 2012
>>>>>>> +KernelVersion:	3.5
>>>>>>> +Contact:	Johan Hovold<jhovold@gmail.com>
>>>>>>> +Description:
>>>>>>> +		Set the target brightness for ALS-mapper Y in light zone Z
>>>>>>> +		(0..255), where Y in 1..3 and Z in 0..4.
>>>>>>
>>>>>> What are the units of this?
>>>>>
>>>>> The datasheet reads "percent of the full-scale current" (actually depends
>>>>> somewhat on whether the als is in linear or exponential mode). When the
>>>>> leds or backlights are in PWM-mode (not the ALS necessarily), these
>>>>> values are interpreted as a scale factor which is applied to the output
>>>>> current determined by the PWM-signal.
>>>>>
>>>>> Either way it could indeed be considered a raw output current (which
>>>>> could be manipulated later by various factors).
>>>> Fine.... Theoretically if at all possible we'd want the conversion
>>>> factors to get it to an actual current (in amps) to be available though.
>>>> (tend to relax that if there are unknowable elements or they aren't
>>>> specified by board file etc).
>>>
>>> Hmmm. I'm starting to get a feeling that we're over-doing this. The ALS
>>> on the lm3533 isn't a general purpose sensor. It's simply a way to
>>> control the leds and backlights of that device. So what you do is to
>>> determine the full-scale current (max current at maximum brightness 0xff
>>> in this case -- set in board file). Then the ALS input range is divided
>>> in 5 zones, and for each zone you set a brightness as a percent of the
>>> full-scale current. You relly don't care about amps (except for the
>>> maximum determined by the setup).
>> Then no need to provide scale etc.
>
> Good.
>
>>> The equation's for determining the current are available in the
>>> datasheets however, but they depend on which mapping mode (linear or
>>> exponential) and can also be effected by PWM-input duty cycle etc. For
>>> this particular device, I really don't see the point in trying to
>>> determine actual current in amps in all these settings.
>> We can always add it later if anyone cares.
>>>
>>> Note also that the actual output current cannot be determined in the
>>> ALS as the required factors are only set/known in the led/backlight
>>> devices (mapping mode, pwm-mode).
>> Could query it back if it was useful, but sounds like probably not.
>> If we don't provide the information it can't be wrong...
>
> Problem is that we would have three out_currentY_raw channels generating
> up to five different output currents depending on the configuration of
> the five controlled devices...
Gah.  So if we did do this, we'd have to define all 5.  What a pain.
Lest just skip that for now then.
>
>> I'd basically missunderstood where the division between the sensor and
>> the led/backlight drivers lay.  I think I'm happy now with where you
>> have it.
>
> Great.
>
>>>
>>>>>> Also arguably is it not the als that this is related to, but rather
>>>>>> the light source?
>>>>>
>>>>> Well, it would be a raw output, mapping the measured LUX.
>>>> Fair enough, though I wonder if we are stepping on led / backlight
>>>> classes stuff with this.
>>>
>>> Keeping the target sets and the mapper terminology could still be an
>>> option.
>>>
>>> ALS input mode is a special mode of the LEDs and backlights which
>>> overrides the direct current control. It's indeed a special-purpose
>>> device.
>> Yup, I understand you now!
>>>
>>>>>> A quick datasheet browse says that these are current targets? If so I
>>>>>> wonder if we can make that explicit...  Could treat them as 3 output
>>>>>> channels and have indexed values like we do for frequencies in dds
>>>>>> devices (where external hardware is controlling them.
>>>>>
>>>>> I think I like this.
>>>>>
>>>>>> Hmm. lets see.
>>>>>>
>>>>>> out_currentX_currentY_raw
>>>>>> (the double naming is a bit clunky but corresponds to frequency devices
>>>>>> where we have
>>>>>> out_altvoltageX_frequencyY_raw
>>>>>>
>>>>>> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
>>>>>> as indexed values they can take 0,1,2,3,4
>>>>>> out_currentX_raw is not read only and gives you the current for whichever
>>>>>> zone the device is currently in.
>>>>>
>>>>> I take it you mean "out_currentX_raw is read only".
>>>> yes. I do indeed. oops.
>>>>>
>>>>>> This may seem convoluted but I'd really rather have something generalizable
>>>>>> for this if we possibly can.  We'd still need documentation to say what is
>>>>>> controlling these, but at least they'd fit within our more general abi.
>>>>>>
>>>>>> What do you think?
>>>>>
>>>>> I like it. From a user perspective it's mainly a change of names (and
>>>>> indexes). But conceptually it's perhaps more clear: the als maps it's
>>>>> input to an output current, which, just like a PWM-signal, could be used
>>>>> as an input to the LEDs and backlights to determine their outputs.
>>>>>
>>>>> I'd have to modify the LED and backlight interfaces somewhat to reflect
>>>>> the changed indexes and terminology (e.g. "output channel" rather
>>>>> than "ALS mapper"). Something like:
>>>>>
>>>>> 	als_en		-- enable als input mode (0,1)
>>>>> 	als_channel	-- which out_currentX to use as input (0..2) in
>>>>> 			   als input mode
>>>> Not entirely sure I'm happy with this. Would rather it was done
>>>> on a per channel basis, so in_illuminance0_ *
>>>>   From point of view of sensors I don't really care if it is an als or
>>>> measuring something active (hence inherently not ambient!)
>>>
>>> Not sure I understood that. What is it you don't like about it?  You
>>> need to keep in mind what is actually there; three sets of target
>>> values per zone of which one set is dedicated to the first backlight
>>> device. That means, that the ALS mapper (or channel if we want to
>>> use that terminology) needs to be set in the actual devices and not
>>> the other way round. [ The als_en and als_channel attributes would
>>> belong to the led/backlight devices. ]
>> Ah. THat last bit in brackets is what I'd missed ;)  That's fine with me
>> then.
>
> Suspected that. ;)
>
>>> What did you mean by "per channel basis, so in_illuminance0_ *"?
>>>
>>> Again it's a special purpose device -- the lm3533 leds and backlights
>>> are controlled in hw by the on-chip als interface. We can't just use any
>>> generic iio device for this.
>> sure. Had missunderstood completely.
>>>
>>>> Silly question but how is the out_current related to the input in als
>>>> mode?
>>>
>>> 1. raw adc input is averaged
>>> 2. mean adc input is mapped to zone using zone registers ("thresholds")
>>> 3. zone is mapped to a percentage using ALS mapper registers, that is,
>>>      three sets of 8-bit values per zone. Which set is used can be set on
>>>      a per-device (led, backlight) basis
>>> 4. the percentage is applied to the per-device full-scale current to
>>>      determine the actual output. How this mapping is done depends on if
>>>      linear or exponential mapping mode is set (also per-device).
>>>
>> All makes sense now.  Thanks for the clarification.
>>
>>> [ And things can get even more complicated if the devices are in
>>>     PWM-mode, but this is roughly the full picture. ]
>>>
>>> And all of the above is done in hw.
>>>
>>> So, we're only using out_current channels because it maybe fits iio
>>> better. For anyone familiar with the lm3533 this may even just confuse
>>> things.
>> I'm realy keen to do this primarily because we may well hit similar
>> devices at a later stage and it's much nicer to generalize earlier
>> than go through supporting the old method in parallel for years...
>> What we have here is general enough to support a wide range of possible
>> devices so lets go with it.
>
> Ok.
>
>>> There are only the three tables of values that maps a zone to an output
>>> percentage (e.g. half brightness in zone 2).
>>
>> One last question.  Are the als ->  LEd /backlight mapping something a
>> device user would ever change?  If not the most general option would be
>> to use the iio inkernel mapping interfaces to do the assignments. Gives
>> you a clean easy way to allow reading back of current brightness in the
>> led drivers etc...
>
> Are you referring to the als_channel settings above (i.e., which output
> current is used as device input is ALS mode)? I think that may be the
> case. You could have two sets of brightness values and quickly be able
> to switch from one to another (e.g., for PM reasons).
>
> Wouldn't it be possible to both allow users to change this and to add
> support for iio in-kernel mappings later if needed?
In theory yes.  No core support for dynamically messing with this as
such but not to hard to do.
>
> The mapping has the following constraints by the way:
>
> 	backlight.0	->	out_current0
> 	backlight.1	->	out_current1
> 	led.[0-4]	->	out_current[1-2]
>
> So it would only be possible to change the mapping for the leds and
> only to select between two channels.
>
> Is there anything preventing the led driver acting as IIO consumer to
> set up a map to both channels and then decide which to read from
> depending on als_channel?
hmm. That mapping would need to be at the consumer side I think.
>
>>>>> So to summarise, we get the following new sysfs-entries for the ALS
>>>>> (where the first set replace targetX_Y):
>>>>>
>>>>> 	out_currentX_currentY_raw	r/w, (0..255), X in 0..2, Y in 0..4
>>>>> 	out_currentX_raw		ro (0..255), X in 0..2
>>>>>
>>>>> Is there any support in core for the first set or should I simply
>>>>> rename my target attributes?
>>>> No support in the core yet for this sort of thing..
>>>> Michael, any thoughts on this? In a sense it's very similar to
>>>> out_altvoltageX_frequencyY_raw etc...
>
> Core support could be added later as long as we get the naming right in
> lm3533, right?
>
> I'm really keen to get this one into 3.5 (along with the rest of the
> MFD-driver) and I know Greg usually sends his merge requests early...
> But if I understand you correctly, it should be possible to apply
> lm3533-als v5 now?
Err.  I'll try and have a quick last look shortly.

Jonathan
>
> Thanks,
> Johan


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

* Re: [PATCH v5] iio: add LM3533 ambient-light-sensor driver
  2012-05-21 12:18         ` [PATCH v5] " Johan Hovold
@ 2012-05-22  9:19           ` Jonathan Cameron
  2012-05-22  9:40             ` Johan Hovold
  0 siblings, 1 reply; 131+ messages in thread
From: Jonathan Cameron @ 2012-05-22  9:19 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Rob Landley, Richard Purdie, Samuel Ortiz,
	Greg Kroah-Hartman, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio

On 5/21/2012 1:18 PM, Johan Hovold wrote:
> Add sub-driver for the ambient-light-sensor interface on National
> Semiconductor / TI LM3533 lighting power chips.
>
> The sensor interface can be used to control the LEDs and backlights of
> the chip through defining five light zones and three sets of
> corresponding output-current values.
>
> The driver provides raw and mean adc readings along with the current
> light zone through sysfs. A threshold event can be generated on zone
> changes. The ALS-control output values can be set per zone for the three
> current output channels.
There are a few bits of documentation I'd rather were in
sysfs-bus-iio but we can move them any time.

I haven't compile tested as no machine availability till this evening.
Please do one last sanity check against staging-next.

Thanks,
>
> Signed-off-by: Johan Hovold<jhovold@gmail.com>
Acked-by: Jonathan Cameron <jic23@kernel.org>
> ---
>
> v2:
>   - reimplement using iio
>   - add sysfs-ABI documentation
> v3
>   - use indexed channel
>   - fix sysfs-ABI documentation typo and style
>   - replace gain attribute with in_illuminance0_calibscale
>   - add calibscale to platform data
>   - fix adc register definitions
>   - replace to_lm3533_dev_attr macro with inline function
>   - fix device used for error reporting at irq allocation
>   - use iio device for error reporting during setup/enable
>   - rebase on staging-next
>     - fix header include paths
>     - use dev_to_iio_dev
>     - add IIO_CHAN_INFO_RAW to info mask
>     - use iio_device_{alloc,free}
> v4
>   - move to driver/iio/light
>   - add events/in_illuminance0_threshY_hysteresis attributes
>   - fix device zone-boundary quirkiness
>   - clean up attribute handling
>   - replace calibscale with device-specific r_select attribute
> v5
>   - drop r_select from sysfs ABI
>   - fix set_input_mode
>   - minor style changes
>   - register iio device last at probe
>   - use dev_name for iio_dev and irq name
>   - add out_currentY channels
>
>
>   .../ABI/testing/sysfs-bus-iio-light-lm3533-als     |   62 ++
>   drivers/iio/Kconfig                                |    1 +
>   drivers/iio/Makefile                               |    1 +
>   drivers/iio/light/Kconfig                          |   22 +
>   drivers/iio/light/Makefile                         |    5 +
>   drivers/iio/light/lm3533-als.c                     |  932 ++++++++++++++++++++
>   6 files changed, 1023 insertions(+), 0 deletions(-)
>   create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>   create mode 100644 drivers/iio/light/Kconfig
>   create mode 100644 drivers/iio/light/Makefile
>   create mode 100644 drivers/iio/light/lm3533-als.c
>
> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> new file mode 100644
> index 0000000..694a52c
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> @@ -0,0 +1,62 @@
> +What:		/sys/.../events/in_illuminance0_thresh_either_en
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Event generated when channel passes one of the four thresholds
> +		in each direction (rising|falling) and a zone change occurs.
> +		The corresponding light zone can be read from
> +		in_illuminance0_zone.
> +
> +What:		/sys/.../events/in_illuminance0_threshY_hysteresis
> +Date:		May 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Get the hysteresis for thresholds Y, that is,
> +
> +		threshY_hysteresis = threshY_raising - threshY_falling
> +
> +What:		/sys/.../events/illuminance_threshY_falling_value
> +What:		/sys/.../events/illuminance_threshY_raising_value
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Specifies the value of threshold that the device is comparing
> +		against for the events enabled by
> +		in_illuminance0_thresh_either_en (0..255), where Y in 0..3.
> +
> +		Note that threshY_falling must be less than or equal to
> +		threshY_raising.
> +
> +		These thresholds correspond to the eight zone-boundary
> +		registers (boundaryY_{low,high}) and defines the five light
> +		zones.
> +
> +What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> +Date:		April 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Get the current light zone (0..4) as defined by the
> +		in_illuminance0_threshY_{falling,rising} thresholds.
> +
> +What:		/sys/bus/iio/devices/iio:deviceX/out_currentY_raw
> +Date:		May 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Get output current for channel Y (0..255), that is,
> +		out_currentY_currentZ_raw, where Z is the current zone.
Should be in core docs. Can fix that later though.
> +
> +What:		/sys/bus/iio/devices/iio:deviceX/out_currentY_currentZ_raw
> +Date:		May 2012
> +KernelVersion:	3.5
> +Contact:	Johan Hovold<jhovold@gmail.com>
> +Description:
> +		Set the output current for channel out_currentY when in zone
> +		Z (0..255), where Y in 0..2 and Z in 0..4.
> +
> +		These values correspond to the ALS-mapper target registers for
> +		ALS-mapper Y + 1.
Hmm. tempted to move this to core docs too.
> diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
> index 56eecef..cacc74d 100644
> --- a/drivers/iio/Kconfig
> +++ b/drivers/iio/Kconfig
> @@ -50,5 +50,6 @@ config IIO_CONSUMERS_PER_TRIGGER
>
>   source "drivers/iio/adc/Kconfig"
>   source "drivers/iio/amplifiers/Kconfig"
> +source "drivers/iio/light/Kconfig"
>
>   endif # IIO
> diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
> index e425afd..060b674 100644
> --- a/drivers/iio/Makefile
> +++ b/drivers/iio/Makefile
> @@ -11,3 +11,4 @@ obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o
>
>   obj-y += adc/
>   obj-y += amplifiers/
> +obj-y += light/
> diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> new file mode 100644
> index 0000000..db5618e
> --- /dev/null
> +++ b/drivers/iio/light/Kconfig
> @@ -0,0 +1,22 @@
> +#
> +# Light sensors
> +#
> +menu "Light sensors"
> +
> +config SENSORS_LM3533
> +	tristate "LM3533 ambient light sensor"
> +	depends on MFD_LM3533
> +	help
> +	  If you say yes here you get support for the ambient light sensor
> +	  interface on National Semiconductor / TI LM3533 Lighting Power
> +	  chips.
> +
> +	  The sensor interface can be used to control the LEDs and backlights
> +	  of the chip through defining five light zones and three sets of
> +	  corresponding output-current values.
> +
> +	  The driver provides raw and mean adc readings along with the current
> +	  light zone through sysfs. A threshold event can be generated on zone
> +	  changes. The ALS-control output values can be set per zone for the
> +	  three current output channels.
> +endmenu
> diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
> new file mode 100644
> index 0000000..c1c23a0
> --- /dev/null
> +++ b/drivers/iio/light/Makefile
> @@ -0,0 +1,5 @@
> +#
> +# Makefile for IIO Light sensors
> +#
> +
> +obj-$(CONFIG_SENSORS_LM3533)	+= lm3533-als.o
> diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
> new file mode 100644
> index 0000000..c3e7bac
> --- /dev/null
> +++ b/drivers/iio/light/lm3533-als.c
> @@ -0,0 +1,932 @@
> +/*
> + * lm3533-als.c -- LM3533 Ambient Light Sensor driver
> + *
> + * Copyright (C) 2011-2012 Texas Instruments
> + *
> + * Author: Johan Hovold<jhovold@gmail.com>
> + *
> + * 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.
> + */
> +
> +#include<linux/atomic.h>
> +#include<linux/fs.h>
> +#include<linux/interrupt.h>
> +#include<linux/io.h>
> +#include<linux/iio/events.h>
> +#include<linux/iio/iio.h>
> +#include<linux/module.h>
> +#include<linux/mutex.h>
> +#include<linux/mfd/core.h>
> +#include<linux/platform_device.h>
> +#include<linux/slab.h>
> +#include<linux/uaccess.h>
> +
> +#include<linux/mfd/lm3533.h>
> +
> +
> +#define LM3533_ALS_RESISTOR_MIN			1
> +#define LM3533_ALS_RESISTOR_MAX			127
> +#define LM3533_ALS_CHANNEL_CURRENT_MAX		2
> +#define LM3533_ALS_THRESH_MAX			3
> +#define LM3533_ALS_ZONE_MAX			4
> +
> +#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
> +#define LM3533_REG_ALS_CONF			0x31
> +#define LM3533_REG_ALS_ZONE_INFO		0x34
> +#define LM3533_REG_ALS_READ_ADC_RAW		0x37
> +#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x38
> +#define LM3533_REG_ALS_BOUNDARY_BASE		0x50
> +#define LM3533_REG_ALS_TARGET_BASE		0x60
> +
> +#define LM3533_ALS_ENABLE_MASK			0x01
> +#define LM3533_ALS_INPUT_MODE_MASK		0x02
> +#define LM3533_ALS_INT_ENABLE_MASK		0x01
> +
> +#define LM3533_ALS_ZONE_SHIFT			2
> +#define LM3533_ALS_ZONE_MASK			0x1c
> +
> +#define LM3533_ALS_FLAG_INT_ENABLED		1
> +
> +
> +struct lm3533_als {
> +	struct lm3533 *lm3533;
> +	struct platform_device *pdev;
> +
> +	unsigned long flags;
> +	int irq;
> +
> +	atomic_t zone;
> +	struct mutex thresh_mutex;
> +};
> +
> +
> +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
> +								int *adc)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 reg;
> +	u8 val;
> +	int ret;
> +
> +	if (average)
> +		reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
> +	else
> +		reg = LM3533_REG_ALS_READ_ADC_RAW;
> +
> +	ret = lm3533_read(als->lm3533, reg,&val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to read adc\n");
> +		return ret;
> +	}
> +
> +	*adc = val;
> +
> +	return 0;
> +}
> +
> +static int _lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 val;
> +	int ret;
> +
> +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO,&val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to read zone\n");
> +		return ret;
> +	}
> +
> +	val = (val&  LM3533_ALS_ZONE_MASK)>>  LM3533_ALS_ZONE_SHIFT;
> +	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
> +
> +	return 0;
> +}
> +
> +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	int ret;
> +
> +	if (test_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags)) {
> +		*zone = atomic_read(&als->zone);
> +	} else {
> +		ret = _lm3533_als_get_zone(indio_dev, zone);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * channel	output channel 0..2
> + * zone		zone 0..4
> + */
> +static inline u8 lm3533_als_get_target_reg(unsigned channel, unsigned zone)
> +{
> +	return LM3533_REG_ALS_TARGET_BASE + 5 * channel + zone;
> +}
> +
> +static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned channel,
> +							unsigned zone, u8 *val)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 reg;
> +	int ret;
> +
> +	if (channel>  LM3533_ALS_CHANNEL_CURRENT_MAX)
> +		return -EINVAL;
> +
> +	if (zone>  LM3533_ALS_ZONE_MAX)
> +		return -EINVAL;
> +
> +	reg = lm3533_als_get_target_reg(channel, zone);
> +	ret = lm3533_read(als->lm3533, reg, val);
> +	if (ret)
> +		dev_err(&indio_dev->dev, "failed to get target current\n");
> +
> +	return ret;
> +}
> +
> +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned channel,
> +							unsigned zone, u8 val)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 reg;
> +	int ret;
> +
> +	if (channel>  LM3533_ALS_CHANNEL_CURRENT_MAX)
> +		return -EINVAL;
> +
> +	if (zone>  LM3533_ALS_ZONE_MAX)
> +		return -EINVAL;
> +
> +	reg = lm3533_als_get_target_reg(channel, zone);
> +	ret = lm3533_write(als->lm3533, reg, val);
> +	if (ret)
> +		dev_err(&indio_dev->dev, "failed to set target current\n");
> +
> +	return ret;
> +}
> +
> +static int lm3533_als_get_current(struct iio_dev *indio_dev, unsigned channel,
> +								int *val)
> +{
> +	u8 zone;
> +	u8 target;
> +	int ret;
> +
> +	ret = lm3533_als_get_zone(indio_dev,&zone);
> +	if (ret)
> +		return ret;
> +
> +	ret = lm3533_als_get_target(indio_dev, channel, zone,&target);
> +	if (ret)
> +		return ret;
> +
> +	*val = target;
> +
> +	return 0;
> +}
> +
> +static int lm3533_als_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val, int *val2, long mask)
> +{
> +	int ret;
> +
> +	switch (mask) {
> +	case 0:
> +		switch (chan->type) {
> +		case IIO_LIGHT:
> +			ret = lm3533_als_get_adc(indio_dev, false, val);
> +			break;
> +		case IIO_CURRENT:
> +			ret = lm3533_als_get_current(indio_dev, chan->channel,
> +									val);
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +		break;
> +	case IIO_CHAN_INFO_AVERAGE_RAW:
> +		ret = lm3533_als_get_adc(indio_dev, true, val);
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	return IIO_VAL_INT;
> +}
> +
> +#define CHANNEL_CURRENT(_channel)					\
> +	{								\
> +		.type		= IIO_CURRENT,				\
> +		.channel	= _channel,				\
> +		.indexed	= true,					\
> +		.output		= true,					\
> +		.info_mask	= IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
> +	}
> +
> +static const struct iio_chan_spec lm3533_als_channels[] = {
> +	{
> +		.type		= IIO_LIGHT,
> +		.channel	= 0,
> +		.indexed	= true,
> +		.info_mask	= (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
> +				   IIO_CHAN_INFO_RAW_SEPARATE_BIT),
> +	},
> +	CHANNEL_CURRENT(0),
> +	CHANNEL_CURRENT(1),
> +	CHANNEL_CURRENT(2),
> +};
> +
> +static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
> +{
> +
> +	struct iio_dev *indio_dev = dev_id;
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 zone;
> +	int ret;
> +
> +	/* Clear interrupt by reading the ALS zone register. */
> +	ret = _lm3533_als_get_zone(indio_dev,&zone);
> +	if (ret)
> +		goto out;
> +
> +	atomic_set(&als->zone, zone);
> +
> +	iio_push_event(indio_dev,
> +		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
> +					    0,
> +					    IIO_EV_TYPE_THRESH,
> +					    IIO_EV_DIR_EITHER),
> +		       iio_get_time_ns());
> +out:
> +	return IRQ_HANDLED;
> +}
> +
> +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> +	u8 val;
> +	int ret;
> +
> +	if (enable)
> +		val = mask;
> +	else
> +		val = 0;
> +
> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
> +								enable);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> +	u8 val;
> +	int ret;
> +
> +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO,&val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to get int mode\n");
> +		return ret;
> +	}
> +
> +	*enable = !!(val&  mask);
> +
> +	return 0;
> +}
> +
> +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
> +{
> +	u8 offset = !raising;
> +
> +	return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
> +}
> +
> +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
> +							bool raising, u8 *val)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 reg;
> +	int ret;
> +
> +	if (nr>  LM3533_ALS_THRESH_MAX)
> +		return -EINVAL;
> +
> +	reg = lm3533_als_get_threshold_reg(nr, raising);
> +	ret = lm3533_read(als->lm3533, reg, val);
> +	if (ret)
> +		dev_err(&indio_dev->dev, "failed to get threshold\n");
> +
> +	return ret;
> +}
> +
> +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
> +							bool raising, u8 val)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 val2;
> +	u8 reg, reg2;
> +	int ret;
> +
> +	if (nr>  LM3533_ALS_THRESH_MAX)
> +		return -EINVAL;
> +
> +	reg = lm3533_als_get_threshold_reg(nr, raising);
> +	reg2 = lm3533_als_get_threshold_reg(nr, !raising);
> +
> +	mutex_lock(&als->thresh_mutex);
> +	ret = lm3533_read(als->lm3533, reg2,&val2);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to get threshold\n");
> +		goto out;
> +	}
> +	/*
> +	 * This device does not allow negative hysteresis (in fact, it uses
> +	 * whichever value is smaller as the lower bound) so we need to make
> +	 * sure that thresh_falling<= thresh_raising.
> +	 */
> +	if ((raising&&  (val<  val2)) || (!raising&&  (val>  val2))) {
> +		ret = -EINVAL;
> +		goto out;
> +	}
> +
> +	ret = lm3533_write(als->lm3533, reg, val);
> +	if (ret) {
> +		dev_err(&indio_dev->dev, "failed to set threshold\n");
> +		goto out;
> +	}
> +out:
> +	mutex_unlock(&als->thresh_mutex);
> +
> +	return ret;
> +}
> +
> +static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr,
> +								u8 *val)
> +{
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	u8 falling;
> +	u8 raising;
> +	int ret;
> +
> +	if (nr>  LM3533_ALS_THRESH_MAX)
> +		return -EINVAL;
> +
> +	mutex_lock(&als->thresh_mutex);
> +	ret = lm3533_als_get_threshold(indio_dev, nr, false,&falling);
> +	if (ret)
> +		goto out;
> +	ret = lm3533_als_get_threshold(indio_dev, nr, true,&raising);
> +	if (ret)
> +		goto out;
> +
> +	*val = raising - falling;
> +out:
> +	mutex_unlock(&als->thresh_mutex);
> +
> +	return ret;
> +}
> +
> +static int show_thresh_either_en(struct device *dev,
> +					struct device_attribute *attr,
> +					char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	int enable;
> +	int ret;
> +
> +	if (als->irq) {
> +		ret = lm3533_als_get_int_mode(indio_dev,&enable);
> +		if (ret)
> +			return ret;
> +	} else {
> +		enable = 0;
> +	}
> +
> +	return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
> +}
> +
> +static int store_thresh_either_en(struct device *dev,
> +					struct device_attribute *attr,
> +					const char *buf, size_t len)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +	unsigned long enable;
> +	bool int_enabled;
> +	u8 zone;
> +	int ret;
> +
> +	if (!als->irq)
> +		return -EBUSY;
> +
> +	if (kstrtoul(buf, 0,&enable))
> +		return -EINVAL;
> +
> +	int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +
> +	if (enable&&  !int_enabled) {
> +		ret = lm3533_als_get_zone(indio_dev,&zone);
> +		if (ret)
> +			return ret;
> +
> +		atomic_set(&als->zone, zone);
> +
> +		set_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +	}
> +
> +	ret = lm3533_als_set_int_mode(indio_dev, enable);
> +	if (ret) {
> +		if (!int_enabled)
> +			clear_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +
> +		return ret;
> +	}
> +
> +	if (!enable)
> +		clear_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +
> +	return len;
> +}
> +
> +static ssize_t show_zone(struct device *dev,
> +				struct device_attribute *attr, char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	u8 zone;
> +	int ret;
> +
> +	ret = lm3533_als_get_zone(indio_dev,&zone);
> +	if (ret)
> +		return ret;
> +
> +	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
> +}
> +
> +enum lm3533_als_attribute_type {
> +	LM3533_ATTR_TYPE_HYSTERESIS,
> +	LM3533_ATTR_TYPE_TARGET,
> +	LM3533_ATTR_TYPE_THRESH_FALLING,
> +	LM3533_ATTR_TYPE_THRESH_RAISING,
> +};
> +
> +struct lm3533_als_attribute {
> +	struct device_attribute dev_attr;
> +	enum lm3533_als_attribute_type type;
> +	u8 val1;
> +	u8 val2;
> +};
> +
> +static inline struct lm3533_als_attribute *
> +to_lm3533_als_attr(struct device_attribute *attr)
> +{
> +	return container_of(attr, struct lm3533_als_attribute, dev_attr);
> +}
> +
> +static ssize_t show_als_attr(struct device *dev,
> +					struct device_attribute *attr,
> +					char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> +	u8 val;
> +	int ret;
> +
> +	switch (als_attr->type) {
> +	case LM3533_ATTR_TYPE_HYSTERESIS:
> +		ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1,
> +									&val);
> +		break;
> +	case LM3533_ATTR_TYPE_TARGET:
> +		ret = lm3533_als_get_target(indio_dev, als_attr->val1,
> +							als_attr->val2,&val);
> +		break;
> +	case LM3533_ATTR_TYPE_THRESH_FALLING:
> +		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
> +								false,&val);
> +		break;
> +	case LM3533_ATTR_TYPE_THRESH_RAISING:
> +		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
> +								true,&val);
> +		break;
> +	default:
> +		ret = -ENXIO;
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
> +}
> +
> +static ssize_t store_als_attr(struct device *dev,
> +					struct device_attribute *attr,
> +					const char *buf, size_t len)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> +	u8 val;
> +	int ret;
> +
> +	if (kstrtou8(buf, 0,&val))
> +		return -EINVAL;
> +
> +	switch (als_attr->type) {
> +	case LM3533_ATTR_TYPE_TARGET:
> +		ret = lm3533_als_set_target(indio_dev, als_attr->val1,
> +							als_attr->val2, val);
> +		break;
> +	case LM3533_ATTR_TYPE_THRESH_FALLING:
> +		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> +								false, val);
> +		break;
> +	case LM3533_ATTR_TYPE_THRESH_RAISING:
> +		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> +								true, val);
> +		break;
> +	default:
> +		ret = -ENXIO;
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)	\
> +	{ .dev_attr	= __ATTR(_name, _mode, _show, _store),		\
> +	  .type		= _type,					\
> +	  .val1		= _val1,					\
> +	  .val2		= _val2 }
> +
> +#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
> +	struct lm3533_als_attribute lm3533_als_attr_##_name =		  \
> +		ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)
> +
> +#define ALS_TARGET_ATTR_RW(_channel, _zone)				\
> +	LM3533_ALS_ATTR(out_current##_channel##_current##_zone##_raw,	\
> +				S_IRUGO | S_IWUSR,			\
> +				show_als_attr, store_als_attr,		\
> +				LM3533_ATTR_TYPE_TARGET, _channel, _zone)
> +/*
> + * ALS output current values (ALS mapper targets)
> + *
> + * out_current[0-2]_current[0-4]_raw		0-255
> + */
> +static ALS_TARGET_ATTR_RW(0, 0);
> +static ALS_TARGET_ATTR_RW(0, 1);
> +static ALS_TARGET_ATTR_RW(0, 2);
> +static ALS_TARGET_ATTR_RW(0, 3);
> +static ALS_TARGET_ATTR_RW(0, 4);
> +
> +static ALS_TARGET_ATTR_RW(1, 0);
> +static ALS_TARGET_ATTR_RW(1, 1);
> +static ALS_TARGET_ATTR_RW(1, 2);
> +static ALS_TARGET_ATTR_RW(1, 3);
> +static ALS_TARGET_ATTR_RW(1, 4);
> +
> +static ALS_TARGET_ATTR_RW(2, 0);
> +static ALS_TARGET_ATTR_RW(2, 1);
> +static ALS_TARGET_ATTR_RW(2, 2);
> +static ALS_TARGET_ATTR_RW(2, 3);
> +static ALS_TARGET_ATTR_RW(2, 4);
> +
> +#define ALS_THRESH_FALLING_ATTR_RW(_nr)					\
> +	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value,	\
> +			S_IRUGO | S_IWUSR,				\
> +			show_als_attr, store_als_attr,		\
> +			LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0)
> +
> +#define ALS_THRESH_RAISING_ATTR_RW(_nr)					\
> +	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value,	\
> +			S_IRUGO | S_IWUSR,				\
> +			show_als_attr, store_als_attr,			\
> +			LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0)
> +/*
> + * ALS Zone thresholds (boundaries)
> + *
> + * in_illuminance0_thresh[0-3]_falling_value	0-255
> + * in_illuminance0_thresh[0-3]_raising_value	0-255
> + */
> +static ALS_THRESH_FALLING_ATTR_RW(0);
> +static ALS_THRESH_FALLING_ATTR_RW(1);
> +static ALS_THRESH_FALLING_ATTR_RW(2);
> +static ALS_THRESH_FALLING_ATTR_RW(3);
> +
> +static ALS_THRESH_RAISING_ATTR_RW(0);
> +static ALS_THRESH_RAISING_ATTR_RW(1);
> +static ALS_THRESH_RAISING_ATTR_RW(2);
> +static ALS_THRESH_RAISING_ATTR_RW(3);
> +
> +#define ALS_HYSTERESIS_ATTR_RO(_nr)					\
> +	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis,	\
> +			S_IRUGO, show_als_attr, NULL,			\
> +			LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0)
> +/*
> + * ALS Zone threshold hysteresis
> + *
> + * threshY_hysteresis = threshY_raising - threshY_falling
> + *
> + * in_illuminance0_thresh[0-3]_hysteresis	0-255
> + * in_illuminance0_thresh[0-3]_hysteresis	0-255
> + */
> +static ALS_HYSTERESIS_ATTR_RO(0);
> +static ALS_HYSTERESIS_ATTR_RO(1);
> +static ALS_HYSTERESIS_ATTR_RO(2);
> +static ALS_HYSTERESIS_ATTR_RO(3);
> +
> +#define ILLUMINANCE_ATTR_RO(_name) \
> +	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
> +#define ILLUMINANCE_ATTR_RW(_name) \
> +	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
> +						show_##_name, store_##_name)
> +/*
> + * ALS Zone threshold-event enable
> + *
> + * in_illuminance0_thresh_either_en		0,1
> + */
> +static ILLUMINANCE_ATTR_RW(thresh_either_en);
> +
> +/*
> + * ALS Current Zone
> + *
> + * in_illuminance0_zone		0-4
> + */
> +static ILLUMINANCE_ATTR_RO(zone);
> +
> +static struct attribute *lm3533_als_event_attributes[] = {
> +	&dev_attr_in_illuminance0_thresh_either_en.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
> +	&lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
> +	NULL
> +};
> +
> +static struct attribute_group lm3533_als_event_attribute_group = {
> +	.attrs = lm3533_als_event_attributes
> +};
> +
> +static struct attribute *lm3533_als_attributes[] = {
> +	&dev_attr_in_illuminance0_zone.attr,
> +	&lm3533_als_attr_out_current0_current0_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current0_current1_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current0_current2_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current0_current3_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current0_current4_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current1_current0_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current1_current1_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current1_current2_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current1_current3_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current1_current4_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current2_current0_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current2_current1_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current2_current2_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current2_current3_raw.dev_attr.attr,
> +	&lm3533_als_attr_out_current2_current4_raw.dev_attr.attr,
> +	NULL
> +};
> +
> +static struct attribute_group lm3533_als_attribute_group = {
> +	.attrs = lm3533_als_attributes
> +};
> +
> +static int __devinit lm3533_als_set_input_mode(struct lm3533_als *als,
> +								bool pwm_mode)
> +{
> +	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
> +	u8 val;
> +	int ret;
> +
> +	if (pwm_mode)
> +		val = mask;	/* pwm input */
> +	else
> +		val = 0;	/* analog input */
> +
> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, val, mask);
> +	if (ret) {
> +		dev_err(&als->pdev->dev, "failed to set input mode %d\n",
> +								pwm_mode);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int __devinit lm3533_als_set_resistor(struct lm3533_als *als, u8 val)
> +{
> +	int ret;
> +
> +	if (val<  LM3533_ALS_RESISTOR_MIN || val>  LM3533_ALS_RESISTOR_MAX)
> +		return -EINVAL;
> +
> +	ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
> +	if (ret) {
> +		dev_err(&als->pdev->dev, "failed to set resistor\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int __devinit lm3533_als_setup(struct lm3533_als *als,
> +					struct lm3533_als_platform_data *pdata)
> +{
> +	int ret;
> +
> +	ret = lm3533_als_set_input_mode(als, pdata->pwm_mode);
> +	if (ret)
> +		return ret;
> +
> +	/* ALS input is always high impedance in PWM-mode. */
> +	if (!pdata->pwm_mode) {
> +		ret = lm3533_als_set_resistor(als, pdata->r_select);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int __devinit lm3533_als_setup_irq(struct lm3533_als *als, void *dev)
> +{
> +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> +	int ret;
> +
> +	/* Make sure interrupts are disabled. */
> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, 0, mask);
> +	if (ret) {
> +		dev_err(&als->pdev->dev, "failed to disable interrupts\n");
> +		return ret;
> +	}
> +
> +	ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
> +					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> +					dev_name(&als->pdev->dev), dev);
> +	if (ret) {
> +		dev_err(&als->pdev->dev, "failed to request irq %d\n",
> +								als->irq);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int __devinit lm3533_als_enable(struct lm3533_als *als)
> +{
> +	u8 mask = LM3533_ALS_ENABLE_MASK;
> +	int ret;
> +
> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
> +	if (ret)
> +		dev_err(&als->pdev->dev, "failed to enable ALS\n");
> +
> +	return ret;
> +}
> +
> +static int lm3533_als_disable(struct lm3533_als *als)
> +{
> +	u8 mask = LM3533_ALS_ENABLE_MASK;
> +	int ret;
> +
> +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
> +	if (ret)
> +		dev_err(&als->pdev->dev, "failed to disable ALS\n");
> +
> +	return ret;
> +}
> +
> +static const struct iio_info lm3533_als_info = {
> +	.attrs		=&lm3533_als_attribute_group,
> +	.event_attrs	=&lm3533_als_event_attribute_group,
> +	.driver_module	= THIS_MODULE,
> +	.read_raw	=&lm3533_als_read_raw,
> +};
> +
> +static int __devinit lm3533_als_probe(struct platform_device *pdev)
> +{
> +	struct lm3533 *lm3533;
> +	struct lm3533_als_platform_data *pdata;
> +	struct lm3533_als *als;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +
> +	lm3533 = dev_get_drvdata(pdev->dev.parent);
> +	if (!lm3533)
> +		return -EINVAL;
> +
> +	pdata = pdev->dev.platform_data;
> +	if (!pdata) {
> +		dev_err(&pdev->dev, "no platform data\n");
> +		return -EINVAL;
> +	}
> +
> +	indio_dev = iio_device_alloc(sizeof(*als));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	indio_dev->info =&lm3533_als_info;
> +	indio_dev->channels = lm3533_als_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
> +	indio_dev->name = dev_name(&pdev->dev);
> +	indio_dev->dev.parent = pdev->dev.parent;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	als = iio_priv(indio_dev);
> +	als->lm3533 = lm3533;
> +	als->pdev = pdev;
> +	als->irq = lm3533->irq;
> +	atomic_set(&als->zone, 0);
> +	mutex_init(&als->thresh_mutex);
> +
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	if (als->irq) {
> +		ret = lm3533_als_setup_irq(als, indio_dev);
> +		if (ret)
> +			goto err_free_dev;
> +	}
> +
> +	ret = lm3533_als_setup(als, pdata);
> +	if (ret)
> +		goto err_free_irq;
> +
> +	ret = lm3533_als_enable(als);
> +	if (ret)
> +		goto err_free_irq;
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to register ALS\n");
> +		goto err_disable;
> +	}
> +
> +	return 0;
> +
> +err_disable:
> +	lm3533_als_disable(als);
> +err_free_irq:
> +	if (als->irq)
> +		free_irq(als->irq, indio_dev);
> +err_free_dev:
> +	iio_device_free(indio_dev);
> +
> +	return ret;
> +}
> +
> +static int __devexit lm3533_als_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +	struct lm3533_als *als = iio_priv(indio_dev);
> +
> +	lm3533_als_set_int_mode(indio_dev, false);
> +	iio_device_unregister(indio_dev);
> +	lm3533_als_disable(als);
> +	if (als->irq)
> +		free_irq(als->irq, indio_dev);
> +	iio_device_free(indio_dev);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver lm3533_als_driver = {
> +	.driver	= {
> +		.name	= "lm3533-als",
> +		.owner	= THIS_MODULE,
> +	},
> +	.probe		= lm3533_als_probe,
> +	.remove		= __devexit_p(lm3533_als_remove),
> +};
> +module_platform_driver(lm3533_als_driver);
> +
> +MODULE_AUTHOR("Johan Hovold<jhovold@gmail.com>");
> +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:lm3533-als");


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

* Re: [PATCH v5] iio: add LM3533 ambient-light-sensor driver
  2012-05-22  9:19           ` Jonathan Cameron
@ 2012-05-22  9:40             ` Johan Hovold
  2012-05-22 13:55               ` Greg Kroah-Hartman
  2012-06-05  4:11               ` Greg Kroah-Hartman
  0 siblings, 2 replies; 131+ messages in thread
From: Johan Hovold @ 2012-05-22  9:40 UTC (permalink / raw)
  To: Jonathan Cameron, Greg Kroah-Hartman
  Cc: Johan Hovold, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio

On Tue, May 22, 2012 at 10:19:15AM +0100, Jonathan Cameron wrote:
> On 5/21/2012 1:18 PM, Johan Hovold wrote:
> > Add sub-driver for the ambient-light-sensor interface on National
> > Semiconductor / TI LM3533 lighting power chips.
> >
> > The sensor interface can be used to control the LEDs and backlights of
> > the chip through defining five light zones and three sets of
> > corresponding output-current values.
> >
> > The driver provides raw and mean adc readings along with the current
> > light zone through sysfs. A threshold event can be generated on zone
> > changes. The ALS-control output values can be set per zone for the three
> > current output channels.
> There are a few bits of documentation I'd rather were in
> sysfs-bus-iio but we can move them any time.
> 
> I haven't compile tested as no machine availability till this evening.
> Please do one last sanity check against staging-next.

Tested against staging-next of today (c3c6cc91b0ae7b).

Greg, any chance you can pick this one up for 3.5?

Thanks,
Johan

> 
> Thanks,
> >
> > Signed-off-by: Johan Hovold<jhovold@gmail.com>
> Acked-by: Jonathan Cameron <jic23@kernel.org>
> > ---
> >
> > v2:
> >   - reimplement using iio
> >   - add sysfs-ABI documentation
> > v3
> >   - use indexed channel
> >   - fix sysfs-ABI documentation typo and style
> >   - replace gain attribute with in_illuminance0_calibscale
> >   - add calibscale to platform data
> >   - fix adc register definitions
> >   - replace to_lm3533_dev_attr macro with inline function
> >   - fix device used for error reporting at irq allocation
> >   - use iio device for error reporting during setup/enable
> >   - rebase on staging-next
> >     - fix header include paths
> >     - use dev_to_iio_dev
> >     - add IIO_CHAN_INFO_RAW to info mask
> >     - use iio_device_{alloc,free}
> > v4
> >   - move to driver/iio/light
> >   - add events/in_illuminance0_threshY_hysteresis attributes
> >   - fix device zone-boundary quirkiness
> >   - clean up attribute handling
> >   - replace calibscale with device-specific r_select attribute
> > v5
> >   - drop r_select from sysfs ABI
> >   - fix set_input_mode
> >   - minor style changes
> >   - register iio device last at probe
> >   - use dev_name for iio_dev and irq name
> >   - add out_currentY channels
> >
> >
> >   .../ABI/testing/sysfs-bus-iio-light-lm3533-als     |   62 ++
> >   drivers/iio/Kconfig                                |    1 +
> >   drivers/iio/Makefile                               |    1 +
> >   drivers/iio/light/Kconfig                          |   22 +
> >   drivers/iio/light/Makefile                         |    5 +
> >   drivers/iio/light/lm3533-als.c                     |  932 ++++++++++++++++++++
> >   6 files changed, 1023 insertions(+), 0 deletions(-)
> >   create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >   create mode 100644 drivers/iio/light/Kconfig
> >   create mode 100644 drivers/iio/light/Makefile
> >   create mode 100644 drivers/iio/light/lm3533-als.c
> >
> > diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > new file mode 100644
> > index 0000000..694a52c
> > --- /dev/null
> > +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > @@ -0,0 +1,62 @@
> > +What:		/sys/.../events/in_illuminance0_thresh_either_en
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Event generated when channel passes one of the four thresholds
> > +		in each direction (rising|falling) and a zone change occurs.
> > +		The corresponding light zone can be read from
> > +		in_illuminance0_zone.
> > +
> > +What:		/sys/.../events/in_illuminance0_threshY_hysteresis
> > +Date:		May 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Get the hysteresis for thresholds Y, that is,
> > +
> > +		threshY_hysteresis = threshY_raising - threshY_falling
> > +
> > +What:		/sys/.../events/illuminance_threshY_falling_value
> > +What:		/sys/.../events/illuminance_threshY_raising_value
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Specifies the value of threshold that the device is comparing
> > +		against for the events enabled by
> > +		in_illuminance0_thresh_either_en (0..255), where Y in 0..3.
> > +
> > +		Note that threshY_falling must be less than or equal to
> > +		threshY_raising.
> > +
> > +		These thresholds correspond to the eight zone-boundary
> > +		registers (boundaryY_{low,high}) and defines the five light
> > +		zones.
> > +
> > +What:		/sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> > +Date:		April 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Get the current light zone (0..4) as defined by the
> > +		in_illuminance0_threshY_{falling,rising} thresholds.
> > +
> > +What:		/sys/bus/iio/devices/iio:deviceX/out_currentY_raw
> > +Date:		May 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Get output current for channel Y (0..255), that is,
> > +		out_currentY_currentZ_raw, where Z is the current zone.
> Should be in core docs. Can fix that later though.
> > +
> > +What:		/sys/bus/iio/devices/iio:deviceX/out_currentY_currentZ_raw
> > +Date:		May 2012
> > +KernelVersion:	3.5
> > +Contact:	Johan Hovold<jhovold@gmail.com>
> > +Description:
> > +		Set the output current for channel out_currentY when in zone
> > +		Z (0..255), where Y in 0..2 and Z in 0..4.
> > +
> > +		These values correspond to the ALS-mapper target registers for
> > +		ALS-mapper Y + 1.
> Hmm. tempted to move this to core docs too.
> > diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
> > index 56eecef..cacc74d 100644
> > --- a/drivers/iio/Kconfig
> > +++ b/drivers/iio/Kconfig
> > @@ -50,5 +50,6 @@ config IIO_CONSUMERS_PER_TRIGGER
> >
> >   source "drivers/iio/adc/Kconfig"
> >   source "drivers/iio/amplifiers/Kconfig"
> > +source "drivers/iio/light/Kconfig"
> >
> >   endif # IIO
> > diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
> > index e425afd..060b674 100644
> > --- a/drivers/iio/Makefile
> > +++ b/drivers/iio/Makefile
> > @@ -11,3 +11,4 @@ obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o
> >
> >   obj-y += adc/
> >   obj-y += amplifiers/
> > +obj-y += light/
> > diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> > new file mode 100644
> > index 0000000..db5618e
> > --- /dev/null
> > +++ b/drivers/iio/light/Kconfig
> > @@ -0,0 +1,22 @@
> > +#
> > +# Light sensors
> > +#
> > +menu "Light sensors"
> > +
> > +config SENSORS_LM3533
> > +	tristate "LM3533 ambient light sensor"
> > +	depends on MFD_LM3533
> > +	help
> > +	  If you say yes here you get support for the ambient light sensor
> > +	  interface on National Semiconductor / TI LM3533 Lighting Power
> > +	  chips.
> > +
> > +	  The sensor interface can be used to control the LEDs and backlights
> > +	  of the chip through defining five light zones and three sets of
> > +	  corresponding output-current values.
> > +
> > +	  The driver provides raw and mean adc readings along with the current
> > +	  light zone through sysfs. A threshold event can be generated on zone
> > +	  changes. The ALS-control output values can be set per zone for the
> > +	  three current output channels.
> > +endmenu
> > diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
> > new file mode 100644
> > index 0000000..c1c23a0
> > --- /dev/null
> > +++ b/drivers/iio/light/Makefile
> > @@ -0,0 +1,5 @@
> > +#
> > +# Makefile for IIO Light sensors
> > +#
> > +
> > +obj-$(CONFIG_SENSORS_LM3533)	+= lm3533-als.o
> > diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
> > new file mode 100644
> > index 0000000..c3e7bac
> > --- /dev/null
> > +++ b/drivers/iio/light/lm3533-als.c
> > @@ -0,0 +1,932 @@
> > +/*
> > + * lm3533-als.c -- LM3533 Ambient Light Sensor driver
> > + *
> > + * Copyright (C) 2011-2012 Texas Instruments
> > + *
> > + * Author: Johan Hovold<jhovold@gmail.com>
> > + *
> > + * 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.
> > + */
> > +
> > +#include<linux/atomic.h>
> > +#include<linux/fs.h>
> > +#include<linux/interrupt.h>
> > +#include<linux/io.h>
> > +#include<linux/iio/events.h>
> > +#include<linux/iio/iio.h>
> > +#include<linux/module.h>
> > +#include<linux/mutex.h>
> > +#include<linux/mfd/core.h>
> > +#include<linux/platform_device.h>
> > +#include<linux/slab.h>
> > +#include<linux/uaccess.h>
> > +
> > +#include<linux/mfd/lm3533.h>
> > +
> > +
> > +#define LM3533_ALS_RESISTOR_MIN			1
> > +#define LM3533_ALS_RESISTOR_MAX			127
> > +#define LM3533_ALS_CHANNEL_CURRENT_MAX		2
> > +#define LM3533_ALS_THRESH_MAX			3
> > +#define LM3533_ALS_ZONE_MAX			4
> > +
> > +#define LM3533_REG_ALS_RESISTOR_SELECT		0x30
> > +#define LM3533_REG_ALS_CONF			0x31
> > +#define LM3533_REG_ALS_ZONE_INFO		0x34
> > +#define LM3533_REG_ALS_READ_ADC_RAW		0x37
> > +#define LM3533_REG_ALS_READ_ADC_AVERAGE		0x38
> > +#define LM3533_REG_ALS_BOUNDARY_BASE		0x50
> > +#define LM3533_REG_ALS_TARGET_BASE		0x60
> > +
> > +#define LM3533_ALS_ENABLE_MASK			0x01
> > +#define LM3533_ALS_INPUT_MODE_MASK		0x02
> > +#define LM3533_ALS_INT_ENABLE_MASK		0x01
> > +
> > +#define LM3533_ALS_ZONE_SHIFT			2
> > +#define LM3533_ALS_ZONE_MASK			0x1c
> > +
> > +#define LM3533_ALS_FLAG_INT_ENABLED		1
> > +
> > +
> > +struct lm3533_als {
> > +	struct lm3533 *lm3533;
> > +	struct platform_device *pdev;
> > +
> > +	unsigned long flags;
> > +	int irq;
> > +
> > +	atomic_t zone;
> > +	struct mutex thresh_mutex;
> > +};
> > +
> > +
> > +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
> > +								int *adc)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 reg;
> > +	u8 val;
> > +	int ret;
> > +
> > +	if (average)
> > +		reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
> > +	else
> > +		reg = LM3533_REG_ALS_READ_ADC_RAW;
> > +
> > +	ret = lm3533_read(als->lm3533, reg,&val);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to read adc\n");
> > +		return ret;
> > +	}
> > +
> > +	*adc = val;
> > +
> > +	return 0;
> > +}
> > +
> > +static int _lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 val;
> > +	int ret;
> > +
> > +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO,&val);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to read zone\n");
> > +		return ret;
> > +	}
> > +
> > +	val = (val&  LM3533_ALS_ZONE_MASK)>>  LM3533_ALS_ZONE_SHIFT;
> > +	*zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
> > +
> > +	return 0;
> > +}
> > +
> > +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	int ret;
> > +
> > +	if (test_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags)) {
> > +		*zone = atomic_read(&als->zone);
> > +	} else {
> > +		ret = _lm3533_als_get_zone(indio_dev, zone);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +/*
> > + * channel	output channel 0..2
> > + * zone		zone 0..4
> > + */
> > +static inline u8 lm3533_als_get_target_reg(unsigned channel, unsigned zone)
> > +{
> > +	return LM3533_REG_ALS_TARGET_BASE + 5 * channel + zone;
> > +}
> > +
> > +static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned channel,
> > +							unsigned zone, u8 *val)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 reg;
> > +	int ret;
> > +
> > +	if (channel>  LM3533_ALS_CHANNEL_CURRENT_MAX)
> > +		return -EINVAL;
> > +
> > +	if (zone>  LM3533_ALS_ZONE_MAX)
> > +		return -EINVAL;
> > +
> > +	reg = lm3533_als_get_target_reg(channel, zone);
> > +	ret = lm3533_read(als->lm3533, reg, val);
> > +	if (ret)
> > +		dev_err(&indio_dev->dev, "failed to get target current\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned channel,
> > +							unsigned zone, u8 val)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 reg;
> > +	int ret;
> > +
> > +	if (channel>  LM3533_ALS_CHANNEL_CURRENT_MAX)
> > +		return -EINVAL;
> > +
> > +	if (zone>  LM3533_ALS_ZONE_MAX)
> > +		return -EINVAL;
> > +
> > +	reg = lm3533_als_get_target_reg(channel, zone);
> > +	ret = lm3533_write(als->lm3533, reg, val);
> > +	if (ret)
> > +		dev_err(&indio_dev->dev, "failed to set target current\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static int lm3533_als_get_current(struct iio_dev *indio_dev, unsigned channel,
> > +								int *val)
> > +{
> > +	u8 zone;
> > +	u8 target;
> > +	int ret;
> > +
> > +	ret = lm3533_als_get_zone(indio_dev,&zone);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = lm3533_als_get_target(indio_dev, channel, zone,&target);
> > +	if (ret)
> > +		return ret;
> > +
> > +	*val = target;
> > +
> > +	return 0;
> > +}
> > +
> > +static int lm3533_als_read_raw(struct iio_dev *indio_dev,
> > +				struct iio_chan_spec const *chan,
> > +				int *val, int *val2, long mask)
> > +{
> > +	int ret;
> > +
> > +	switch (mask) {
> > +	case 0:
> > +		switch (chan->type) {
> > +		case IIO_LIGHT:
> > +			ret = lm3533_als_get_adc(indio_dev, false, val);
> > +			break;
> > +		case IIO_CURRENT:
> > +			ret = lm3533_als_get_current(indio_dev, chan->channel,
> > +									val);
> > +			break;
> > +		default:
> > +			return -EINVAL;
> > +		}
> > +		break;
> > +	case IIO_CHAN_INFO_AVERAGE_RAW:
> > +		ret = lm3533_als_get_adc(indio_dev, true, val);
> > +		break;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (ret)
> > +		return ret;
> > +
> > +	return IIO_VAL_INT;
> > +}
> > +
> > +#define CHANNEL_CURRENT(_channel)					\
> > +	{								\
> > +		.type		= IIO_CURRENT,				\
> > +		.channel	= _channel,				\
> > +		.indexed	= true,					\
> > +		.output		= true,					\
> > +		.info_mask	= IIO_CHAN_INFO_RAW_SEPARATE_BIT,	\
> > +	}
> > +
> > +static const struct iio_chan_spec lm3533_als_channels[] = {
> > +	{
> > +		.type		= IIO_LIGHT,
> > +		.channel	= 0,
> > +		.indexed	= true,
> > +		.info_mask	= (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
> > +				   IIO_CHAN_INFO_RAW_SEPARATE_BIT),
> > +	},
> > +	CHANNEL_CURRENT(0),
> > +	CHANNEL_CURRENT(1),
> > +	CHANNEL_CURRENT(2),
> > +};
> > +
> > +static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
> > +{
> > +
> > +	struct iio_dev *indio_dev = dev_id;
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 zone;
> > +	int ret;
> > +
> > +	/* Clear interrupt by reading the ALS zone register. */
> > +	ret = _lm3533_als_get_zone(indio_dev,&zone);
> > +	if (ret)
> > +		goto out;
> > +
> > +	atomic_set(&als->zone, zone);
> > +
> > +	iio_push_event(indio_dev,
> > +		       IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
> > +					    0,
> > +					    IIO_EV_TYPE_THRESH,
> > +					    IIO_EV_DIR_EITHER),
> > +		       iio_get_time_ns());
> > +out:
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> > +	u8 val;
> > +	int ret;
> > +
> > +	if (enable)
> > +		val = mask;
> > +	else
> > +		val = 0;
> > +
> > +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to set int mode %d\n",
> > +								enable);
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> > +	u8 val;
> > +	int ret;
> > +
> > +	ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO,&val);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to get int mode\n");
> > +		return ret;
> > +	}
> > +
> > +	*enable = !!(val&  mask);
> > +
> > +	return 0;
> > +}
> > +
> > +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
> > +{
> > +	u8 offset = !raising;
> > +
> > +	return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
> > +}
> > +
> > +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
> > +							bool raising, u8 *val)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 reg;
> > +	int ret;
> > +
> > +	if (nr>  LM3533_ALS_THRESH_MAX)
> > +		return -EINVAL;
> > +
> > +	reg = lm3533_als_get_threshold_reg(nr, raising);
> > +	ret = lm3533_read(als->lm3533, reg, val);
> > +	if (ret)
> > +		dev_err(&indio_dev->dev, "failed to get threshold\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
> > +							bool raising, u8 val)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 val2;
> > +	u8 reg, reg2;
> > +	int ret;
> > +
> > +	if (nr>  LM3533_ALS_THRESH_MAX)
> > +		return -EINVAL;
> > +
> > +	reg = lm3533_als_get_threshold_reg(nr, raising);
> > +	reg2 = lm3533_als_get_threshold_reg(nr, !raising);
> > +
> > +	mutex_lock(&als->thresh_mutex);
> > +	ret = lm3533_read(als->lm3533, reg2,&val2);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to get threshold\n");
> > +		goto out;
> > +	}
> > +	/*
> > +	 * This device does not allow negative hysteresis (in fact, it uses
> > +	 * whichever value is smaller as the lower bound) so we need to make
> > +	 * sure that thresh_falling<= thresh_raising.
> > +	 */
> > +	if ((raising&&  (val<  val2)) || (!raising&&  (val>  val2))) {
> > +		ret = -EINVAL;
> > +		goto out;
> > +	}
> > +
> > +	ret = lm3533_write(als->lm3533, reg, val);
> > +	if (ret) {
> > +		dev_err(&indio_dev->dev, "failed to set threshold\n");
> > +		goto out;
> > +	}
> > +out:
> > +	mutex_unlock(&als->thresh_mutex);
> > +
> > +	return ret;
> > +}
> > +
> > +static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr,
> > +								u8 *val)
> > +{
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	u8 falling;
> > +	u8 raising;
> > +	int ret;
> > +
> > +	if (nr>  LM3533_ALS_THRESH_MAX)
> > +		return -EINVAL;
> > +
> > +	mutex_lock(&als->thresh_mutex);
> > +	ret = lm3533_als_get_threshold(indio_dev, nr, false,&falling);
> > +	if (ret)
> > +		goto out;
> > +	ret = lm3533_als_get_threshold(indio_dev, nr, true,&raising);
> > +	if (ret)
> > +		goto out;
> > +
> > +	*val = raising - falling;
> > +out:
> > +	mutex_unlock(&als->thresh_mutex);
> > +
> > +	return ret;
> > +}
> > +
> > +static int show_thresh_either_en(struct device *dev,
> > +					struct device_attribute *attr,
> > +					char *buf)
> > +{
> > +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	int enable;
> > +	int ret;
> > +
> > +	if (als->irq) {
> > +		ret = lm3533_als_get_int_mode(indio_dev,&enable);
> > +		if (ret)
> > +			return ret;
> > +	} else {
> > +		enable = 0;
> > +	}
> > +
> > +	return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
> > +}
> > +
> > +static int store_thresh_either_en(struct device *dev,
> > +					struct device_attribute *attr,
> > +					const char *buf, size_t len)
> > +{
> > +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +	unsigned long enable;
> > +	bool int_enabled;
> > +	u8 zone;
> > +	int ret;
> > +
> > +	if (!als->irq)
> > +		return -EBUSY;
> > +
> > +	if (kstrtoul(buf, 0,&enable))
> > +		return -EINVAL;
> > +
> > +	int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> > +
> > +	if (enable&&  !int_enabled) {
> > +		ret = lm3533_als_get_zone(indio_dev,&zone);
> > +		if (ret)
> > +			return ret;
> > +
> > +		atomic_set(&als->zone, zone);
> > +
> > +		set_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> > +	}
> > +
> > +	ret = lm3533_als_set_int_mode(indio_dev, enable);
> > +	if (ret) {
> > +		if (!int_enabled)
> > +			clear_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> > +
> > +		return ret;
> > +	}
> > +
> > +	if (!enable)
> > +		clear_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> > +
> > +	return len;
> > +}
> > +
> > +static ssize_t show_zone(struct device *dev,
> > +				struct device_attribute *attr, char *buf)
> > +{
> > +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > +	u8 zone;
> > +	int ret;
> > +
> > +	ret = lm3533_als_get_zone(indio_dev,&zone);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
> > +}
> > +
> > +enum lm3533_als_attribute_type {
> > +	LM3533_ATTR_TYPE_HYSTERESIS,
> > +	LM3533_ATTR_TYPE_TARGET,
> > +	LM3533_ATTR_TYPE_THRESH_FALLING,
> > +	LM3533_ATTR_TYPE_THRESH_RAISING,
> > +};
> > +
> > +struct lm3533_als_attribute {
> > +	struct device_attribute dev_attr;
> > +	enum lm3533_als_attribute_type type;
> > +	u8 val1;
> > +	u8 val2;
> > +};
> > +
> > +static inline struct lm3533_als_attribute *
> > +to_lm3533_als_attr(struct device_attribute *attr)
> > +{
> > +	return container_of(attr, struct lm3533_als_attribute, dev_attr);
> > +}
> > +
> > +static ssize_t show_als_attr(struct device *dev,
> > +					struct device_attribute *attr,
> > +					char *buf)
> > +{
> > +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > +	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> > +	u8 val;
> > +	int ret;
> > +
> > +	switch (als_attr->type) {
> > +	case LM3533_ATTR_TYPE_HYSTERESIS:
> > +		ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1,
> > +									&val);
> > +		break;
> > +	case LM3533_ATTR_TYPE_TARGET:
> > +		ret = lm3533_als_get_target(indio_dev, als_attr->val1,
> > +							als_attr->val2,&val);
> > +		break;
> > +	case LM3533_ATTR_TYPE_THRESH_FALLING:
> > +		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
> > +								false,&val);
> > +		break;
> > +	case LM3533_ATTR_TYPE_THRESH_RAISING:
> > +		ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
> > +								true,&val);
> > +		break;
> > +	default:
> > +		ret = -ENXIO;
> > +	}
> > +
> > +	if (ret)
> > +		return ret;
> > +
> > +	return scnprintf(buf, PAGE_SIZE, "%u\n", val);
> > +}
> > +
> > +static ssize_t store_als_attr(struct device *dev,
> > +					struct device_attribute *attr,
> > +					const char *buf, size_t len)
> > +{
> > +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > +	struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> > +	u8 val;
> > +	int ret;
> > +
> > +	if (kstrtou8(buf, 0,&val))
> > +		return -EINVAL;
> > +
> > +	switch (als_attr->type) {
> > +	case LM3533_ATTR_TYPE_TARGET:
> > +		ret = lm3533_als_set_target(indio_dev, als_attr->val1,
> > +							als_attr->val2, val);
> > +		break;
> > +	case LM3533_ATTR_TYPE_THRESH_FALLING:
> > +		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> > +								false, val);
> > +		break;
> > +	case LM3533_ATTR_TYPE_THRESH_RAISING:
> > +		ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> > +								true, val);
> > +		break;
> > +	default:
> > +		ret = -ENXIO;
> > +	}
> > +
> > +	if (ret)
> > +		return ret;
> > +
> > +	return len;
> > +}
> > +
> > +#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)	\
> > +	{ .dev_attr	= __ATTR(_name, _mode, _show, _store),		\
> > +	  .type		= _type,					\
> > +	  .val1		= _val1,					\
> > +	  .val2		= _val2 }
> > +
> > +#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
> > +	struct lm3533_als_attribute lm3533_als_attr_##_name =		  \
> > +		ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)
> > +
> > +#define ALS_TARGET_ATTR_RW(_channel, _zone)				\
> > +	LM3533_ALS_ATTR(out_current##_channel##_current##_zone##_raw,	\
> > +				S_IRUGO | S_IWUSR,			\
> > +				show_als_attr, store_als_attr,		\
> > +				LM3533_ATTR_TYPE_TARGET, _channel, _zone)
> > +/*
> > + * ALS output current values (ALS mapper targets)
> > + *
> > + * out_current[0-2]_current[0-4]_raw		0-255
> > + */
> > +static ALS_TARGET_ATTR_RW(0, 0);
> > +static ALS_TARGET_ATTR_RW(0, 1);
> > +static ALS_TARGET_ATTR_RW(0, 2);
> > +static ALS_TARGET_ATTR_RW(0, 3);
> > +static ALS_TARGET_ATTR_RW(0, 4);
> > +
> > +static ALS_TARGET_ATTR_RW(1, 0);
> > +static ALS_TARGET_ATTR_RW(1, 1);
> > +static ALS_TARGET_ATTR_RW(1, 2);
> > +static ALS_TARGET_ATTR_RW(1, 3);
> > +static ALS_TARGET_ATTR_RW(1, 4);
> > +
> > +static ALS_TARGET_ATTR_RW(2, 0);
> > +static ALS_TARGET_ATTR_RW(2, 1);
> > +static ALS_TARGET_ATTR_RW(2, 2);
> > +static ALS_TARGET_ATTR_RW(2, 3);
> > +static ALS_TARGET_ATTR_RW(2, 4);
> > +
> > +#define ALS_THRESH_FALLING_ATTR_RW(_nr)					\
> > +	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value,	\
> > +			S_IRUGO | S_IWUSR,				\
> > +			show_als_attr, store_als_attr,		\
> > +			LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0)
> > +
> > +#define ALS_THRESH_RAISING_ATTR_RW(_nr)					\
> > +	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value,	\
> > +			S_IRUGO | S_IWUSR,				\
> > +			show_als_attr, store_als_attr,			\
> > +			LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0)
> > +/*
> > + * ALS Zone thresholds (boundaries)
> > + *
> > + * in_illuminance0_thresh[0-3]_falling_value	0-255
> > + * in_illuminance0_thresh[0-3]_raising_value	0-255
> > + */
> > +static ALS_THRESH_FALLING_ATTR_RW(0);
> > +static ALS_THRESH_FALLING_ATTR_RW(1);
> > +static ALS_THRESH_FALLING_ATTR_RW(2);
> > +static ALS_THRESH_FALLING_ATTR_RW(3);
> > +
> > +static ALS_THRESH_RAISING_ATTR_RW(0);
> > +static ALS_THRESH_RAISING_ATTR_RW(1);
> > +static ALS_THRESH_RAISING_ATTR_RW(2);
> > +static ALS_THRESH_RAISING_ATTR_RW(3);
> > +
> > +#define ALS_HYSTERESIS_ATTR_RO(_nr)					\
> > +	LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis,	\
> > +			S_IRUGO, show_als_attr, NULL,			\
> > +			LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0)
> > +/*
> > + * ALS Zone threshold hysteresis
> > + *
> > + * threshY_hysteresis = threshY_raising - threshY_falling
> > + *
> > + * in_illuminance0_thresh[0-3]_hysteresis	0-255
> > + * in_illuminance0_thresh[0-3]_hysteresis	0-255
> > + */
> > +static ALS_HYSTERESIS_ATTR_RO(0);
> > +static ALS_HYSTERESIS_ATTR_RO(1);
> > +static ALS_HYSTERESIS_ATTR_RO(2);
> > +static ALS_HYSTERESIS_ATTR_RO(3);
> > +
> > +#define ILLUMINANCE_ATTR_RO(_name) \
> > +	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
> > +#define ILLUMINANCE_ATTR_RW(_name) \
> > +	DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
> > +						show_##_name, store_##_name)
> > +/*
> > + * ALS Zone threshold-event enable
> > + *
> > + * in_illuminance0_thresh_either_en		0,1
> > + */
> > +static ILLUMINANCE_ATTR_RW(thresh_either_en);
> > +
> > +/*
> > + * ALS Current Zone
> > + *
> > + * in_illuminance0_zone		0-4
> > + */
> > +static ILLUMINANCE_ATTR_RO(zone);
> > +
> > +static struct attribute *lm3533_als_event_attributes[] = {
> > +	&dev_attr_in_illuminance0_thresh_either_en.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
> > +	&lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
> > +	NULL
> > +};
> > +
> > +static struct attribute_group lm3533_als_event_attribute_group = {
> > +	.attrs = lm3533_als_event_attributes
> > +};
> > +
> > +static struct attribute *lm3533_als_attributes[] = {
> > +	&dev_attr_in_illuminance0_zone.attr,
> > +	&lm3533_als_attr_out_current0_current0_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current0_current1_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current0_current2_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current0_current3_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current0_current4_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current1_current0_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current1_current1_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current1_current2_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current1_current3_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current1_current4_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current2_current0_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current2_current1_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current2_current2_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current2_current3_raw.dev_attr.attr,
> > +	&lm3533_als_attr_out_current2_current4_raw.dev_attr.attr,
> > +	NULL
> > +};
> > +
> > +static struct attribute_group lm3533_als_attribute_group = {
> > +	.attrs = lm3533_als_attributes
> > +};
> > +
> > +static int __devinit lm3533_als_set_input_mode(struct lm3533_als *als,
> > +								bool pwm_mode)
> > +{
> > +	u8 mask = LM3533_ALS_INPUT_MODE_MASK;
> > +	u8 val;
> > +	int ret;
> > +
> > +	if (pwm_mode)
> > +		val = mask;	/* pwm input */
> > +	else
> > +		val = 0;	/* analog input */
> > +
> > +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, val, mask);
> > +	if (ret) {
> > +		dev_err(&als->pdev->dev, "failed to set input mode %d\n",
> > +								pwm_mode);
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int __devinit lm3533_als_set_resistor(struct lm3533_als *als, u8 val)
> > +{
> > +	int ret;
> > +
> > +	if (val<  LM3533_ALS_RESISTOR_MIN || val>  LM3533_ALS_RESISTOR_MAX)
> > +		return -EINVAL;
> > +
> > +	ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
> > +	if (ret) {
> > +		dev_err(&als->pdev->dev, "failed to set resistor\n");
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int __devinit lm3533_als_setup(struct lm3533_als *als,
> > +					struct lm3533_als_platform_data *pdata)
> > +{
> > +	int ret;
> > +
> > +	ret = lm3533_als_set_input_mode(als, pdata->pwm_mode);
> > +	if (ret)
> > +		return ret;
> > +
> > +	/* ALS input is always high impedance in PWM-mode. */
> > +	if (!pdata->pwm_mode) {
> > +		ret = lm3533_als_set_resistor(als, pdata->r_select);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int __devinit lm3533_als_setup_irq(struct lm3533_als *als, void *dev)
> > +{
> > +	u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> > +	int ret;
> > +
> > +	/* Make sure interrupts are disabled. */
> > +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, 0, mask);
> > +	if (ret) {
> > +		dev_err(&als->pdev->dev, "failed to disable interrupts\n");
> > +		return ret;
> > +	}
> > +
> > +	ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
> > +					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> > +					dev_name(&als->pdev->dev), dev);
> > +	if (ret) {
> > +		dev_err(&als->pdev->dev, "failed to request irq %d\n",
> > +								als->irq);
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int __devinit lm3533_als_enable(struct lm3533_als *als)
> > +{
> > +	u8 mask = LM3533_ALS_ENABLE_MASK;
> > +	int ret;
> > +
> > +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
> > +	if (ret)
> > +		dev_err(&als->pdev->dev, "failed to enable ALS\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static int lm3533_als_disable(struct lm3533_als *als)
> > +{
> > +	u8 mask = LM3533_ALS_ENABLE_MASK;
> > +	int ret;
> > +
> > +	ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
> > +	if (ret)
> > +		dev_err(&als->pdev->dev, "failed to disable ALS\n");
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct iio_info lm3533_als_info = {
> > +	.attrs		=&lm3533_als_attribute_group,
> > +	.event_attrs	=&lm3533_als_event_attribute_group,
> > +	.driver_module	= THIS_MODULE,
> > +	.read_raw	=&lm3533_als_read_raw,
> > +};
> > +
> > +static int __devinit lm3533_als_probe(struct platform_device *pdev)
> > +{
> > +	struct lm3533 *lm3533;
> > +	struct lm3533_als_platform_data *pdata;
> > +	struct lm3533_als *als;
> > +	struct iio_dev *indio_dev;
> > +	int ret;
> > +
> > +	lm3533 = dev_get_drvdata(pdev->dev.parent);
> > +	if (!lm3533)
> > +		return -EINVAL;
> > +
> > +	pdata = pdev->dev.platform_data;
> > +	if (!pdata) {
> > +		dev_err(&pdev->dev, "no platform data\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	indio_dev = iio_device_alloc(sizeof(*als));
> > +	if (!indio_dev)
> > +		return -ENOMEM;
> > +
> > +	indio_dev->info =&lm3533_als_info;
> > +	indio_dev->channels = lm3533_als_channels;
> > +	indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
> > +	indio_dev->name = dev_name(&pdev->dev);
> > +	indio_dev->dev.parent = pdev->dev.parent;
> > +	indio_dev->modes = INDIO_DIRECT_MODE;
> > +
> > +	als = iio_priv(indio_dev);
> > +	als->lm3533 = lm3533;
> > +	als->pdev = pdev;
> > +	als->irq = lm3533->irq;
> > +	atomic_set(&als->zone, 0);
> > +	mutex_init(&als->thresh_mutex);
> > +
> > +	platform_set_drvdata(pdev, indio_dev);
> > +
> > +	if (als->irq) {
> > +		ret = lm3533_als_setup_irq(als, indio_dev);
> > +		if (ret)
> > +			goto err_free_dev;
> > +	}
> > +
> > +	ret = lm3533_als_setup(als, pdata);
> > +	if (ret)
> > +		goto err_free_irq;
> > +
> > +	ret = lm3533_als_enable(als);
> > +	if (ret)
> > +		goto err_free_irq;
> > +
> > +	ret = iio_device_register(indio_dev);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "failed to register ALS\n");
> > +		goto err_disable;
> > +	}
> > +
> > +	return 0;
> > +
> > +err_disable:
> > +	lm3533_als_disable(als);
> > +err_free_irq:
> > +	if (als->irq)
> > +		free_irq(als->irq, indio_dev);
> > +err_free_dev:
> > +	iio_device_free(indio_dev);
> > +
> > +	return ret;
> > +}
> > +
> > +static int __devexit lm3533_als_remove(struct platform_device *pdev)
> > +{
> > +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> > +	struct lm3533_als *als = iio_priv(indio_dev);
> > +
> > +	lm3533_als_set_int_mode(indio_dev, false);
> > +	iio_device_unregister(indio_dev);
> > +	lm3533_als_disable(als);
> > +	if (als->irq)
> > +		free_irq(als->irq, indio_dev);
> > +	iio_device_free(indio_dev);
> > +
> > +	return 0;
> > +}
> > +
> > +static struct platform_driver lm3533_als_driver = {
> > +	.driver	= {
> > +		.name	= "lm3533-als",
> > +		.owner	= THIS_MODULE,
> > +	},
> > +	.probe		= lm3533_als_probe,
> > +	.remove		= __devexit_p(lm3533_als_remove),
> > +};
> > +module_platform_driver(lm3533_als_driver);
> > +
> > +MODULE_AUTHOR("Johan Hovold<jhovold@gmail.com>");
> > +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:lm3533-als");
> 

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

* Re: [PATCH v5] iio: add LM3533 ambient-light-sensor driver
  2012-05-22  9:40             ` Johan Hovold
@ 2012-05-22 13:55               ` Greg Kroah-Hartman
  2012-06-05  4:11               ` Greg Kroah-Hartman
  1 sibling, 0 replies; 131+ messages in thread
From: Greg Kroah-Hartman @ 2012-05-22 13:55 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio

On Tue, May 22, 2012 at 11:40:20AM +0200, Johan Hovold wrote:
> On Tue, May 22, 2012 at 10:19:15AM +0100, Jonathan Cameron wrote:
> > On 5/21/2012 1:18 PM, Johan Hovold wrote:
> > > Add sub-driver for the ambient-light-sensor interface on National
> > > Semiconductor / TI LM3533 lighting power chips.
> > >
> > > The sensor interface can be used to control the LEDs and backlights of
> > > the chip through defining five light zones and three sets of
> > > corresponding output-current values.
> > >
> > > The driver provides raw and mean adc readings along with the current
> > > light zone through sysfs. A threshold event can be generated on zone
> > > changes. The ALS-control output values can be set per zone for the three
> > > current output channels.
> > There are a few bits of documentation I'd rather were in
> > sysfs-bus-iio but we can move them any time.
> > 
> > I haven't compile tested as no machine availability till this evening.
> > Please do one last sanity check against staging-next.
> 
> Tested against staging-next of today (c3c6cc91b0ae7b).
> 
> Greg, any chance you can pick this one up for 3.5?

No, the merge window for 3.5 closed last week, sorry.  Now is the time
for me (and the other subsystem maintainers) to merge my trees with
Linus.  I'll pick this driver up when 3.5-rc1 is out.

thanks,

greg k-h

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

* Re: [PATCH v5] iio: add LM3533 ambient-light-sensor driver
  2012-05-22  9:40             ` Johan Hovold
  2012-05-22 13:55               ` Greg Kroah-Hartman
@ 2012-06-05  4:11               ` Greg Kroah-Hartman
  1 sibling, 0 replies; 131+ messages in thread
From: Greg Kroah-Hartman @ 2012-06-05  4:11 UTC (permalink / raw)
  To: Johan Hovold
  Cc: Jonathan Cameron, Jonathan Cameron, Rob Landley, Richard Purdie,
	Samuel Ortiz, Florian Tobias Schandinat, Arnd Bergmann,
	Andrew Morton, Mark Brown, linux-doc, linux-kernel, linux-iio

On Tue, May 22, 2012 at 11:40:20AM +0200, Johan Hovold wrote:
> On Tue, May 22, 2012 at 10:19:15AM +0100, Jonathan Cameron wrote:
> > On 5/21/2012 1:18 PM, Johan Hovold wrote:
> > > Add sub-driver for the ambient-light-sensor interface on National
> > > Semiconductor / TI LM3533 lighting power chips.
> > >
> > > The sensor interface can be used to control the LEDs and backlights of
> > > the chip through defining five light zones and three sets of
> > > corresponding output-current values.
> > >
> > > The driver provides raw and mean adc readings along with the current
> > > light zone through sysfs. A threshold event can be generated on zone
> > > changes. The ALS-control output values can be set per zone for the three
> > > current output channels.
> > There are a few bits of documentation I'd rather were in
> > sysfs-bus-iio but we can move them any time.
> > 
> > I haven't compile tested as no machine availability till this evening.
> > Please do one last sanity check against staging-next.
> 
> Tested against staging-next of today (c3c6cc91b0ae7b).
> 
> Greg, any chance you can pick this one up for 3.5?

To late for 3.5, the merge window for new stuff closed a week or so
before 3.4 came out, as stuff needs to be tested in linux-next.

I've queued it up now to be merge in 3.6.

thanks,

greg k-h

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

end of thread, other threads:[~2012-06-05  6:49 UTC | newest]

Thread overview: 131+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-04-20 15:30 [PATCH 0/4] mfd: add LM3533 lighting-power chip driver Johan Hovold
2012-04-20 15:30 ` Johan Hovold
2012-04-20 15:30 ` [PATCH 1/4] mfd: add LM3533 lighting-power core driver Johan Hovold
2012-04-20 15:30   ` Johan Hovold
2012-04-26 12:41   ` Mark Brown
2012-04-26 12:41     ` Mark Brown
2012-05-03 10:15     ` Johan Hovold
2012-05-03 10:15       ` Johan Hovold
2012-05-03 10:22     ` Johan Hovold
2012-05-03 10:22       ` Johan Hovold
2012-04-20 15:30 ` [PATCH 2/4] misc: add LM3533 ambient light sensor driver Johan Hovold
2012-04-20 15:30   ` Johan Hovold
2012-04-20 15:57   ` Greg Kroah-Hartman
2012-04-20 15:57     ` Greg Kroah-Hartman
2012-04-20 17:28     ` Johan Hovold
2012-04-20 17:28       ` Johan Hovold
2012-04-20 17:37       ` Greg Kroah-Hartman
2012-04-20 17:37         ` Greg Kroah-Hartman
2012-04-26 11:52         ` Johan Hovold
2012-04-26 11:52           ` Johan Hovold
2012-04-20 15:30 ` [PATCH 3/4] leds: add LM3533 LED driver Johan Hovold
2012-04-20 15:30   ` Johan Hovold
2012-04-20 16:10   ` Arnd Bergmann
2012-04-20 16:45     ` Johan Hovold
2012-04-20 16:45       ` Johan Hovold
2012-04-20 15:30 ` [PATCH 4/4] backlight: add LM3533 backlight driver Johan Hovold
2012-04-20 15:30   ` Johan Hovold
2012-05-03 10:26 ` [PATCH v2 0/4] mfd: add LM3533 lighting-power chip driver Johan Hovold
2012-05-03 10:26   ` Johan Hovold
2012-05-03 10:26   ` [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver Johan Hovold
2012-05-03 10:26     ` Johan Hovold
2012-05-03 10:38     ` Mark Brown
2012-05-03 10:38       ` Mark Brown
2012-05-03 11:28       ` Johan Hovold
2012-05-03 11:28         ` Johan Hovold
2012-05-03 11:38         ` Mark Brown
2012-05-03 11:38           ` Mark Brown
2012-05-03 15:00           ` Johan Hovold
2012-05-03 15:00             ` Johan Hovold
2012-05-03 15:24             ` Mark Brown
2012-05-03 15:24               ` Mark Brown
2012-05-03 16:54               ` Johan Hovold
2012-05-03 16:54                 ` Johan Hovold
2012-05-03 16:57                 ` Mark Brown
2012-05-03 16:57                   ` Mark Brown
2012-05-03 17:14                   ` Johan Hovold
2012-05-03 17:14                     ` Johan Hovold
2012-05-03 17:23                     ` Mark Brown
2012-05-03 17:23                       ` Mark Brown
2012-05-03 17:31                       ` Johan Hovold
2012-05-03 17:31                         ` Johan Hovold
2012-05-09 14:42     ` Samuel Ortiz
2012-05-09 14:42       ` Samuel Ortiz
2012-05-10 12:07       ` Johan Hovold
2012-05-10 12:07         ` Johan Hovold
2012-05-10 12:11         ` [PATCH 1/2] mfd: lm3533: add boost frequency and ovp to platform data Johan Hovold
2012-05-10 12:11           ` [PATCH 2/2] mfd: lm3533: remove boost attributes Johan Hovold
2012-05-10 17:18         ` [PATCH 0/2] mfd: lm3533: update max-current interface Johan Hovold
2012-05-10 17:18           ` [PATCH 1/2] mfd: lm3533: remove unused max-current function Johan Hovold
2012-05-10 17:18           ` [PATCH 2/2] mfd: lm3533: use SI-units for max-current interface Johan Hovold
2012-05-11 13:32         ` [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver Samuel Ortiz
2012-05-11 13:32           ` Samuel Ortiz
2012-05-03 10:26   ` [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver Johan Hovold
2012-05-03 10:26     ` Johan Hovold
2012-05-03 11:40     ` Jonathan Cameron
2012-05-03 11:40       ` Jonathan Cameron
2012-05-03 16:36       ` Johan Hovold
2012-05-03 16:36         ` Johan Hovold
2012-05-08 13:47         ` Jonathan Cameron
2012-05-08 13:47           ` Jonathan Cameron
2012-05-15 16:44           ` Johan Hovold
2012-05-15 16:44             ` Johan Hovold
2012-05-15 20:00             ` Jonathan Cameron
2012-05-15 20:00               ` Jonathan Cameron
2012-05-16 13:05               ` Johan Hovold
2012-05-16 13:05                 ` Johan Hovold
2012-05-16 14:21                 ` Jonathan Cameron
2012-05-16 14:21                   ` Jonathan Cameron
2012-05-18 12:27                   ` Johan Hovold
2012-05-18 12:27                     ` Johan Hovold
2012-05-18 17:34                     ` Jonathan Cameron
2012-05-18 17:34                       ` Jonathan Cameron
2012-05-18 17:57                       ` Johan Hovold
2012-05-18 17:57                         ` Johan Hovold
2012-05-19  8:04                         ` Jonathan Cameron
2012-05-19  8:04                           ` Jonathan Cameron
2012-05-15 16:46     ` [PATCH v3] iio: add LM3533 ambient-light-sensor driver Johan Hovold
2012-05-15 19:27       ` Andrew Morton
2012-05-15 20:00         ` Johan Hovold
2012-05-15 20:16           ` Jonathan Cameron
2012-05-18 13:07       ` [PATCH v4] " Johan Hovold
2012-05-19  8:48         ` Jonathan Cameron
2012-05-19 16:30           ` Johan Hovold
2012-05-19 13:26             ` Jonathan Cameron
2012-05-21  9:50           ` Johan Hovold
2012-05-21 16:37             ` Jonathan Cameron
2012-05-21 22:07               ` Johan Hovold
2012-05-22  7:13                 ` Jonathan Cameron
2012-05-22  9:09                   ` Johan Hovold
2012-05-22  9:15                     ` Jonathan Cameron
2012-05-22  7:45               ` Michael Hennerich
2012-05-22  7:49                 ` Jonathan Cameron
2012-05-22  8:11                   ` Michael Hennerich
2012-05-22  8:20                     ` Jonathan Cameron
2012-05-21 12:18         ` [PATCH v5] " Johan Hovold
2012-05-22  9:19           ` Jonathan Cameron
2012-05-22  9:40             ` Johan Hovold
2012-05-22 13:55               ` Greg Kroah-Hartman
2012-06-05  4:11               ` Greg Kroah-Hartman
2012-05-03 10:26   ` [PATCH v2 3/4] leds: add LM3533 LED driver Johan Hovold
2012-05-03 10:26     ` Johan Hovold
2012-05-03 10:43     ` Mark Brown
2012-05-03 10:43       ` Mark Brown
2012-05-03 11:50       ` Johan Hovold
2012-05-03 11:50         ` Johan Hovold
2012-05-03 14:51         ` Mark Brown
2012-05-03 14:51           ` Mark Brown
2012-05-03 16:46           ` Johan Hovold
2012-05-03 16:46             ` Johan Hovold
2012-05-10 18:27     ` [PATCH v3] " Johan Hovold
2012-05-10 18:48       ` Andrew Morton
2012-05-11  9:54         ` Johan Hovold
2012-05-11 22:24           ` Andrew Morton
2012-05-14 10:25             ` Johan Hovold
2012-05-14 10:31       ` [PATCH v4] " Johan Hovold
2012-05-03 10:26   ` [PATCH v2 4/4] backlight: add LM3533 backlight driver Johan Hovold
2012-05-03 10:26     ` Johan Hovold
2012-05-10 18:29     ` [PATCH v3] " Johan Hovold
2012-05-10 18:29       ` Johan Hovold
2012-05-15 19:13       ` [PATCH v4] " Johan Hovold
2012-05-15 19:13         ` Johan Hovold

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.