* [PATCH 0/4] mfd: add LM3533 lighting-power chip driver
@ 2012-04-20 15:30 Johan Hovold
2012-04-20 15:30 ` [PATCH 1/4] mfd: add LM3533 lighting-power core driver Johan Hovold
` (4 more replies)
0 siblings, 5 replies; 49+ 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] 49+ messages in thread
* [PATCH 1/4] mfd: add LM3533 lighting-power core driver
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-26 12:41 ` Mark Brown
2012-04-20 15:30 ` [PATCH 2/4] misc: add LM3533 ambient light sensor driver Johan Hovold
` (3 subsequent siblings)
4 siblings, 1 reply; 49+ 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", ®, &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] 49+ messages in thread
* [PATCH 2/4] misc: add LM3533 ambient light sensor driver
2012-04-20 15:30 [PATCH 0/4] mfd: add LM3533 lighting-power chip driver 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-20 15:57 ` Greg Kroah-Hartman
2012-04-20 15:30 ` [PATCH 3/4] leds: add LM3533 LED driver Johan Hovold
` (2 subsequent siblings)
4 siblings, 1 reply; 49+ 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] 49+ messages in thread
* [PATCH 3/4] leds: add LM3533 LED driver
2012-04-20 15:30 [PATCH 0/4] mfd: add LM3533 lighting-power chip driver Johan Hovold
2012-04-20 15:30 ` [PATCH 1/4] mfd: add LM3533 lighting-power core driver 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 16:10 ` Arnd Bergmann
2012-04-20 15:30 ` [PATCH 4/4] backlight: add LM3533 backlight driver Johan Hovold
2012-05-03 10:26 ` [PATCH v2 0/4] mfd: add LM3533 lighting-power chip driver Johan Hovold
4 siblings, 1 reply; 49+ 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] 49+ messages in thread
* [PATCH 4/4] backlight: add LM3533 backlight driver
2012-04-20 15:30 [PATCH 0/4] mfd: add LM3533 lighting-power chip driver Johan Hovold
` (2 preceding siblings ...)
2012-04-20 15:30 ` [PATCH 3/4] leds: add LM3533 LED 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
4 siblings, 0 replies; 49+ 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] 49+ messages in thread
* Re: [PATCH 2/4] misc: add LM3533 ambient light sensor driver
2012-04-20 15:30 ` [PATCH 2/4] misc: add LM3533 ambient light sensor driver Johan Hovold
@ 2012-04-20 15:57 ` Greg Kroah-Hartman
2012-04-20 17:28 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ messages in thread
* Re: [PATCH 3/4] leds: add LM3533 LED driver
2012-04-20 15:30 ` [PATCH 3/4] leds: add LM3533 LED driver Johan Hovold
@ 2012-04-20 16:10 ` Arnd Bergmann
2012-04-20 16:45 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ 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; 49+ 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] 49+ 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
2012-04-20 17:37 ` Greg Kroah-Hartman
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-04-26 11:52 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ 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
0 siblings, 0 replies; 49+ 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] 49+ messages in thread
* Re: [PATCH 1/4] mfd: add LM3533 lighting-power core driver
2012-04-20 15:30 ` [PATCH 1/4] mfd: add LM3533 lighting-power core driver Johan Hovold
@ 2012-04-26 12:41 ` Mark Brown
2012-05-03 10:15 ` Johan Hovold
2012-05-03 10:22 ` Johan Hovold
0 siblings, 2 replies; 49+ 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] 49+ 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
2012-05-03 10:22 ` Johan Hovold
1 sibling, 0 replies; 49+ 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] 49+ 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
@ 2012-05-03 10:22 ` Johan Hovold
1 sibling, 0 replies; 49+ 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] 49+ messages in thread
* [PATCH v2 0/4] mfd: add LM3533 lighting-power chip driver
2012-04-20 15:30 [PATCH 0/4] mfd: add LM3533 lighting-power chip driver Johan Hovold
` (3 preceding siblings ...)
2012-04-20 15:30 ` [PATCH 4/4] backlight: add LM3533 backlight 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
` (3 more replies)
4 siblings, 4 replies; 49+ 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] 49+ messages in thread
* [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
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:38 ` Mark Brown
2012-05-09 14:42 ` Samuel Ortiz
2012-05-03 10:26 ` [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver Johan Hovold
` (2 subsequent siblings)
3 siblings, 2 replies; 49+ 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, ®map_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] 49+ messages in thread
* [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
2012-05-03 10:26 ` [PATCH v2 0/4] mfd: add LM3533 lighting-power chip driver 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 11:40 ` Jonathan Cameron
2012-05-03 10:26 ` [PATCH v2 3/4] leds: add LM3533 LED driver Johan Hovold
2012-05-03 10:26 ` [PATCH v2 4/4] backlight: add LM3533 backlight driver Johan Hovold
3 siblings, 1 reply; 49+ 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] 49+ messages in thread
* [PATCH v2 3/4] leds: add LM3533 LED driver
2012-05-03 10:26 ` [PATCH v2 0/4] mfd: add LM3533 lighting-power chip driver 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 ` [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver Johan Hovold
@ 2012-05-03 10:26 ` Johan Hovold
2012-05-03 10:43 ` Mark Brown
2012-05-03 10:26 ` [PATCH v2 4/4] backlight: add LM3533 backlight driver Johan Hovold
3 siblings, 1 reply; 49+ 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] 49+ messages in thread
* [PATCH v2 4/4] backlight: add LM3533 backlight driver
2012-05-03 10:26 ` [PATCH v2 0/4] mfd: add LM3533 lighting-power chip driver Johan Hovold
` (2 preceding siblings ...)
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-10 18:29 ` [PATCH v3] " Johan Hovold
3 siblings, 1 reply; 49+ 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] 49+ messages in thread
* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
2012-05-03 10:26 ` [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver Johan Hovold
@ 2012-05-03 10:38 ` Mark Brown
2012-05-03 11:28 ` Johan Hovold
2012-05-09 14:42 ` Samuel Ortiz
1 sibling, 1 reply; 49+ 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] 49+ messages in thread
* Re: [PATCH v2 3/4] leds: add LM3533 LED driver
2012-05-03 10:26 ` [PATCH v2 3/4] leds: add LM3533 LED driver Johan Hovold
@ 2012-05-03 10:43 ` Mark Brown
2012-05-03 11:50 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-03 11:38 ` Mark Brown
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-03 15:00 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ messages in thread
* Re: [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver
2012-05-03 10:26 ` [PATCH v2 2/4] iio: add LM3533 ambient light sensor driver Johan Hovold
@ 2012-05-03 11:40 ` Jonathan Cameron
2012-05-03 16:36 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-03 14:51 ` Mark Brown
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-03 16:46 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-03 15:24 ` Mark Brown
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-03 16:54 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-08 13:47 ` Jonathan Cameron
0 siblings, 1 reply; 49+ 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] 49+ 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
0 siblings, 0 replies; 49+ 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] 49+ 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
2012-05-03 16:57 ` Mark Brown
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-03 17:14 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-03 17:23 ` Mark Brown
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-03 17:31 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ 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
0 siblings, 0 replies; 49+ 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] 49+ 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
2012-05-15 16:44 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ messages in thread
* Re: [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver
2012-05-03 10:26 ` [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver Johan Hovold
2012-05-03 10:38 ` Mark Brown
@ 2012-05-09 14:42 ` Samuel Ortiz
2012-05-10 12:07 ` Johan Hovold
1 sibling, 1 reply; 49+ 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] 49+ 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
2012-05-11 13:32 ` Samuel Ortiz
0 siblings, 1 reply; 49+ 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] 49+ messages in thread
* [PATCH v3] backlight: add LM3533 backlight driver
2012-05-03 10:26 ` [PATCH v2 4/4] backlight: add LM3533 backlight driver Johan Hovold
@ 2012-05-10 18:29 ` Johan Hovold
2012-05-15 19:13 ` [PATCH v4] " Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ 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
0 siblings, 0 replies; 49+ 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] 49+ 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
2012-05-15 20:00 ` Jonathan Cameron
0 siblings, 1 reply; 49+ 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] 49+ messages in thread
* [PATCH v4] backlight: add LM3533 backlight driver
2012-05-10 18:29 ` [PATCH v3] " Johan Hovold
@ 2012-05-15 19:13 ` Johan Hovold
0 siblings, 0 replies; 49+ 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] 49+ 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
2012-05-16 13:05 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-16 14:21 ` Jonathan Cameron
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-18 12:27 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-18 17:34 ` Jonathan Cameron
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-18 17:57 ` Johan Hovold
0 siblings, 1 reply; 49+ 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] 49+ 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
2012-05-19 8:04 ` Jonathan Cameron
0 siblings, 1 reply; 49+ 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] 49+ 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
0 siblings, 0 replies; 49+ 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] 49+ messages in thread
end of thread, other threads:[~2012-05-19 8:04 UTC | newest]
Thread overview: 49+ 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 ` [PATCH 1/4] mfd: add LM3533 lighting-power core driver Johan Hovold
2012-04-26 12:41 ` Mark Brown
2012-05-03 10:15 ` 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:57 ` Greg Kroah-Hartman
2012-04-20 17:28 ` Johan Hovold
2012-04-20 17:37 ` Greg Kroah-Hartman
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 16:10 ` Arnd Bergmann
2012-04-20 16:45 ` Johan Hovold
2012-04-20 15:30 ` [PATCH 4/4] backlight: add LM3533 backlight driver 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 ` [PATCH v2 1/4] mfd: add LM3533 lighting-power core driver Johan Hovold
2012-05-03 10:38 ` Mark Brown
2012-05-03 11:28 ` Johan Hovold
2012-05-03 11:38 ` Mark Brown
2012-05-03 15:00 ` Johan Hovold
2012-05-03 15:24 ` Mark Brown
2012-05-03 16:54 ` Johan Hovold
2012-05-03 16:57 ` Mark Brown
2012-05-03 17:14 ` Johan Hovold
2012-05-03 17:23 ` Mark Brown
2012-05-03 17:31 ` Johan Hovold
2012-05-09 14:42 ` Samuel Ortiz
2012-05-10 12:07 ` Johan Hovold
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 11:40 ` Jonathan Cameron
2012-05-03 16:36 ` Johan Hovold
2012-05-08 13:47 ` Jonathan Cameron
2012-05-15 16:44 ` Johan Hovold
2012-05-15 20:00 ` Jonathan Cameron
2012-05-16 13:05 ` Johan Hovold
2012-05-16 14:21 ` Jonathan Cameron
2012-05-18 12:27 ` Johan Hovold
2012-05-18 17:34 ` Jonathan Cameron
2012-05-18 17:57 ` Johan Hovold
2012-05-19 8:04 ` Jonathan Cameron
2012-05-03 10:26 ` [PATCH v2 3/4] leds: add LM3533 LED driver Johan Hovold
2012-05-03 10:43 ` Mark Brown
2012-05-03 11:50 ` Johan Hovold
2012-05-03 14:51 ` Mark Brown
2012-05-03 16:46 ` Johan Hovold
2012-05-03 10:26 ` [PATCH v2 4/4] backlight: add LM3533 backlight driver Johan Hovold
2012-05-10 18:29 ` [PATCH v3] " Johan Hovold
2012-05-15 19:13 ` [PATCH v4] " Johan Hovold
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).