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
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 <[email protected]>
---
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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
--
1.7.8.5
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 <[email protected]>
---
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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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
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 <[email protected]>
---
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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+Description:
+ Get the id of this led (0..3).
+
+What: /sys/class/leds/<led>/linear
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_DESCRIPTION("LM3533 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-leds");
--
1.7.8.5
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 <[email protected]>
---
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 <[email protected]>
+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 <[email protected]>
+Description:
+ Get the id of this backlight (0, 1).
+
+What: /sys/class/backlight/<backlight>/linear
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_DESCRIPTION("LM3533 Backlight driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-backlight");
--
1.7.8.5
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 <[email protected]>
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.
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 <[email protected]>
> +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 <[email protected]>
> +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 <[email protected]>
> +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.
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 <[email protected]>
>
> 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
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.
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<[email protected]>
> ---
>
> 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<[email protected]>
> +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<[email protected]>
> +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<[email protected]>
> +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<[email protected]>
> +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<[email protected]>
> +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<[email protected]>
> + *
> + * 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<[email protected]>");
> +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:lm3533-als");
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 <[email protected]>
> > +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 <[email protected]>
> > +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 <[email protected]>
> > +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
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).
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
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?
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<[email protected]>
> > +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<[email protected]>
> > +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<[email protected]>
> > +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<[email protected]>
> > +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<[email protected]>
> > +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<[email protected]>
> > + *
> > + * 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
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
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
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".
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
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.
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
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<[email protected]>
>>> +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<[email protected]>
>>> +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<[email protected]>
>>> +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<[email protected]>
>>> +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<[email protected]>
>>> +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<[email protected]>
>>> + *
>>> + * 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
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 <[email protected]>
> ---
>
> 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/
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 <[email protected]>
> > ---
> >
> > 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
Add boost-frequency and over-voltage-protection settings to platform
data.
Signed-off-by: Johan Hovold <[email protected]>
---
drivers/mfd/lm3533-core.c | 50 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/lm3533.h | 15 +++++++++++++
2 files changed, 65 insertions(+), 0 deletions(-)
diff --git a/drivers/mfd/lm3533-core.c b/drivers/mfd/lm3533-core.c
index 75f4b7f..053438c 100644
--- a/drivers/mfd/lm3533-core.c
+++ b/drivers/mfd/lm3533-core.c
@@ -138,6 +138,35 @@ int lm3533_update(struct lm3533 *lm3533, u8 reg, u8 val, u8 mask)
}
EXPORT_SYMBOL_GPL(lm3533_update);
+static int lm3533_set_boost_freq(struct lm3533 *lm3533,
+ enum lm3533_boost_freq freq)
+{
+ int ret;
+
+ ret = lm3533_update(lm3533, LM3533_REG_BOOST_PWM,
+ freq << LM3533_BOOST_FREQ_SHIFT,
+ LM3533_BOOST_FREQ_MASK);
+ if (ret)
+ dev_err(lm3533->dev, "failed to set boost frequency\n");
+
+ return ret;
+}
+
+
+static int lm3533_set_boost_ovp(struct lm3533 *lm3533,
+ enum lm3533_boost_ovp ovp)
+{
+ int ret;
+
+ ret = lm3533_update(lm3533, LM3533_REG_BOOST_PWM,
+ ovp << LM3533_BOOST_OVP_SHIFT,
+ LM3533_BOOST_OVP_MASK);
+ if (ret)
+ dev_err(lm3533->dev, "failed to set boost ovp\n");
+
+ return ret;
+}
+
/*
* HVLED output config -- output hvled controlled by backlight bl
*/
@@ -521,6 +550,22 @@ static int __devinit lm3533_device_led_init(struct lm3533 *lm3533)
return 0;
}
+static int __devinit lm3533_device_setup(struct lm3533 *lm3533,
+ struct lm3533_platform_data *pdata)
+{
+ int ret;
+
+ ret = lm3533_set_boost_freq(lm3533, pdata->boost_freq);
+ if (ret)
+ return ret;
+
+ ret = lm3533_set_boost_ovp(lm3533, pdata->boost_ovp);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
static int __devinit lm3533_device_init(struct lm3533 *lm3533)
{
struct lm3533_platform_data *pdata = lm3533->dev->platform_data;
@@ -550,6 +595,10 @@ static int __devinit lm3533_device_init(struct lm3533 *lm3533)
lm3533_enable(lm3533);
+ ret = lm3533_device_setup(lm3533, pdata);
+ if (ret)
+ goto err_disable;
+
lm3533_device_als_init(lm3533);
lm3533_device_bl_init(lm3533);
lm3533_device_led_init(lm3533);
@@ -564,6 +613,7 @@ static int __devinit lm3533_device_init(struct lm3533 *lm3533)
err_unregister:
mfd_remove_devices(lm3533->dev);
+err_disable:
lm3533_disable(lm3533);
if (gpio_is_valid(lm3533->gpio_hwen))
gpio_free(lm3533->gpio_hwen);
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
index 75f85f3..3361137 100644
--- a/include/linux/mfd/lm3533.h
+++ b/include/linux/mfd/lm3533.h
@@ -59,9 +59,24 @@ struct lm3533_led_platform_data {
u8 pwm; /* 0 - 0x3f */
};
+enum lm3533_boost_freq {
+ LM3533_BOOST_FREQ_500KHZ,
+ LM3533_BOOST_FREQ_1000KHZ,
+};
+
+enum lm3533_boost_ovp {
+ LM3533_BOOST_OVP_16V,
+ LM3533_BOOST_OVP_24V,
+ LM3533_BOOST_OVP_32V,
+ LM3533_BOOST_OVP_40V,
+};
+
struct lm3533_platform_data {
int gpio_hwen;
+ enum lm3533_boost_ovp boost_ovp;
+ enum lm3533_boost_freq boost_freq;
+
struct lm3533_als_platform_data *als;
struct lm3533_bl_platform_data *backlights;
--
1.7.8.5
Remove boost-frequency and ovp attributes, which can be set through
platform data, from sysfs.
Signed-off-by: Johan Hovold <[email protected]>
---
.../ABI/testing/sysfs-bus-i2c-devices-lm3533 | 23 -----
drivers/mfd/lm3533-core.c | 88 --------------------
2 files changed, 0 insertions(+), 111 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533 b/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
index 5700721..1b62230 100644
--- a/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
+++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-lm3533
@@ -1,26 +1,3 @@
-What: /sys/bus/i2c/devices/.../boost_freq
-Date: April 2012
-KernelVersion: 3.5
-Contact: Johan Hovold <[email protected]>
-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 <[email protected]>
-Description:
- Set the boost converter over-voltage protection threshold
- (0..3), where
-
- 0 - 16V
- 1 - 24V
- 2 - 32V
- 3 - 40V
-
What: /sys/bus/i2c/devices/.../output_hvled[n]
Date: April 2012
KernelVersion: 3.5
diff --git a/drivers/mfd/lm3533-core.c b/drivers/mfd/lm3533-core.c
index 053438c..d670543 100644
--- a/drivers/mfd/lm3533-core.c
+++ b/drivers/mfd/lm3533-core.c
@@ -26,11 +26,9 @@
#include <linux/mfd/lm3533.h>
-#define LM3533_BOOST_OVP_MAX 0x03
#define LM3533_BOOST_OVP_MASK 0x06
#define LM3533_BOOST_OVP_SHIFT 1
-#define LM3533_BOOST_FREQ_MAX 0x01
#define LM3533_BOOST_FREQ_MASK 0x01
#define LM3533_BOOST_FREQ_SHIFT 0
@@ -253,96 +251,12 @@ struct lm3533_device_attribute {
struct {
u8 id;
} output;
- struct {
- u8 reg;
- u8 shift;
- u8 mask;
- u8 max;
- } generic;
} u;
};
#define to_lm3533_dev_attr(_attr) \
container_of(_attr, struct lm3533_device_attribute, dev_attr)
-static ssize_t show_lm3533_reg(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct lm3533 *lm3533 = dev_get_drvdata(dev);
- struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
- u8 val;
- int ret;
-
- ret = lm3533_read(lm3533, lattr->u.generic.reg, &val);
- if (ret)
- return ret;
-
- val = (val & lattr->u.generic.mask) >> lattr->u.generic.shift;
-
- return scnprintf(buf, PAGE_SIZE, "%u\n", val);
-}
-
-static ssize_t store_lm3533_reg(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len)
-{
- struct lm3533 *lm3533 = dev_get_drvdata(dev);
- struct lm3533_device_attribute *lattr = to_lm3533_dev_attr(attr);
- u8 val;
- int ret;
-
- if (kstrtou8(buf, 0, &val) || val > lattr->u.generic.max)
- return -EINVAL;
-
- val = val << lattr->u.generic.shift;
- ret = lm3533_update(lm3533, lattr->u.generic.reg, val,
- lattr->u.generic.mask);
- if (ret)
- return ret;
-
- return len;
-}
-
-#define GENERIC_ATTR(_reg, _max, _mask, _shift) \
- { .reg = _reg, \
- .max = _max, \
- .mask = _mask, \
- .shift = _shift }
-
-#define LM3533_GENERIC_ATTR(_name, _mode, _show, _store, _type, \
- _reg, _max, _mask, _shift) \
- struct lm3533_device_attribute lm3533_dev_attr_##_name = { \
- .dev_attr = __ATTR(_name, _mode, _show, _store), \
- .type = _type, \
- .u.generic = GENERIC_ATTR(_reg, _max, _mask, _shift) }
-
-#define LM3533_GENERIC_ATTR_RW(_name, _type, _reg, _max, _mask, _shift) \
- LM3533_GENERIC_ATTR(_name, S_IRUGO | S_IWUSR, \
- show_lm3533_reg, store_lm3533_reg, \
- _type, _reg, _max, _mask, _shift)
-
-#define LM3533_BOOST_ATTR_RW(_name, _NAME) \
- LM3533_GENERIC_ATTR_RW(_name, LM3533_ATTR_TYPE_BACKLIGHT, \
- LM3533_REG_BOOST_PWM, LM3533_##_NAME##_MAX, \
- LM3533_##_NAME##_MASK, LM3533_##_NAME##_SHIFT)
-/*
- * Boost Over Voltage Protection Select
- *
- * 0 - 16 V (default)
- * 1 - 24 V
- * 2 - 32 V
- * 3 - 40 V
- */
-static LM3533_BOOST_ATTR_RW(boost_ovp, BOOST_OVP);
-
-/*
- * Boost Frequency Select
- *
- * 0 - 500 kHz (default)
- * 1 - 1 MHz
- */
-static LM3533_BOOST_ATTR_RW(boost_freq, BOOST_FREQ);
-
static ssize_t show_output(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -432,8 +346,6 @@ static LM3533_OUTPUT_LVLED_ATTR_RW(4);
static LM3533_OUTPUT_LVLED_ATTR_RW(5);
static struct attribute *lm3533_attributes[] = {
- &lm3533_dev_attr_boost_freq.dev_attr.attr,
- &lm3533_dev_attr_boost_ovp.dev_attr.attr,
&lm3533_dev_attr_output_hvled1.dev_attr.attr,
&lm3533_dev_attr_output_hvled2.dev_attr.attr,
&lm3533_dev_attr_output_lvled1.dev_attr.attr,
--
1.7.8.5
The max-current attributes of the subdrivers have been dropped so
remove the no longer used lm3533_ctrlbank_get_max_current function.
Signed-off-by: Johan Hovold <[email protected]>
---
drivers/mfd/lm3533-ctrlbank.c | 1 -
include/linux/mfd/lm3533.h | 2 --
2 files changed, 0 insertions(+), 3 deletions(-)
diff --git a/drivers/mfd/lm3533-ctrlbank.c b/drivers/mfd/lm3533-ctrlbank.c
index c2732a3..adf4c1a 100644
--- a/drivers/mfd/lm3533-ctrlbank.c
+++ b/drivers/mfd/lm3533-ctrlbank.c
@@ -113,7 +113,6 @@ lm3533_ctrlbank_get(brightness, BRIGHTNESS);
* 31 - 29.8 mA
*/
lm3533_ctrlbank_set(max_current, MAX_CURRENT);
-lm3533_ctrlbank_get(max_current, MAX_CURRENT);
/*
* PWM-input control mask:
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
index 3361137..7cfef9e 100644
--- a/include/linux/mfd/lm3533.h
+++ b/include/linux/mfd/lm3533.h
@@ -92,8 +92,6 @@ extern int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb);
extern int lm3533_ctrlbank_set_brightness(struct lm3533_ctrlbank *cb, u8 val);
extern int lm3533_ctrlbank_get_brightness(struct lm3533_ctrlbank *cb, u8 *val);
extern int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb, u8 val);
-extern int lm3533_ctrlbank_get_max_current(struct lm3533_ctrlbank *cb,
- u8 *val);
extern int lm3533_ctrlbank_set_pwm(struct lm3533_ctrlbank *cb, u8 val);
extern int lm3533_ctrlbank_get_pwm(struct lm3533_ctrlbank *cb, u8 *val);
--
1.7.8.5
Use SI-units (uA) for max-current interface (5000 - 29800 uA).
Signed-off-by: Johan Hovold <[email protected]>
---
drivers/mfd/lm3533-ctrlbank.c | 43 +++++++++++++++++++++++++++-------------
include/linux/mfd/lm3533.h | 7 +++--
2 files changed, 33 insertions(+), 17 deletions(-)
diff --git a/drivers/mfd/lm3533-ctrlbank.c b/drivers/mfd/lm3533-ctrlbank.c
index adf4c1a..a4cb7a5 100644
--- a/drivers/mfd/lm3533-ctrlbank.c
+++ b/drivers/mfd/lm3533-ctrlbank.c
@@ -17,8 +17,11 @@
#include <linux/mfd/lm3533.h>
+#define LM3533_MAX_CURRENT_MIN 5000
+#define LM3533_MAX_CURRENT_MAX 29800
+#define LM3533_MAX_CURRENT_STEP 800
+
#define LM3533_BRIGHTNESS_MAX 255
-#define LM3533_MAX_CURRENT_MAX 31
#define LM3533_PWM_MAX 0x3f
#define LM3533_REG_PWM_BASE 0x14
@@ -65,6 +68,31 @@ int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb)
}
EXPORT_SYMBOL_GPL(lm3533_ctrlbank_disable);
+/*
+ * Full-scale current.
+ *
+ * imax 5000 - 29800 uA (800 uA step)
+ */
+int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb, u16 imax)
+{
+ u8 reg;
+ u8 val;
+ int ret;
+
+ if (imax < LM3533_MAX_CURRENT_MIN || imax > LM3533_MAX_CURRENT_MAX)
+ return -EINVAL;
+
+ val = (imax - LM3533_MAX_CURRENT_MIN) / LM3533_MAX_CURRENT_STEP;
+
+ reg = lm3533_ctrlbank_get_reg(cb, LM3533_REG_MAX_CURRENT_BASE);
+ ret = lm3533_write(cb->lm3533, reg, val);
+ if (ret)
+ dev_err(cb->dev, "failed to set max current\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lm3533_ctrlbank_set_max_current);
+
#define lm3533_ctrlbank_set(_name, _NAME) \
int lm3533_ctrlbank_set_##_name(struct lm3533_ctrlbank *cb, u8 val) \
{ \
@@ -102,19 +130,6 @@ lm3533_ctrlbank_set(brightness, BRIGHTNESS);
lm3533_ctrlbank_get(brightness, BRIGHTNESS);
/*
- * Full scale current.
- *
- * Imax = 5 + val * 0.8 mA, e.g.:
- *
- * 0 - 5 mA
- * ...
- * 19 - 20.2 mA (default)
- * ...
- * 31 - 29.8 mA
- */
-lm3533_ctrlbank_set(max_current, MAX_CURRENT);
-
-/*
* PWM-input control mask:
*
* bit 5 - PWM-input enabled in Zone 4
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
index 7cfef9e..9660feb 100644
--- a/include/linux/mfd/lm3533.h
+++ b/include/linux/mfd/lm3533.h
@@ -47,15 +47,15 @@ struct lm3533_als_platform_data {
struct lm3533_bl_platform_data {
char *name;
+ u16 max_current; /* 5000 - 29800 uA (800 uA step) */
u8 default_brightness; /* 0 - 255 */
- u8 max_current; /* 0 - 31 */
u8 pwm; /* 0 - 0x3f */
};
struct lm3533_led_platform_data {
char *name;
const char *default_trigger;
- u8 max_current; /* 0 - 31 */
+ u16 max_current; /* 5000 - 29800 uA (800 uA step) */
u8 pwm; /* 0 - 0x3f */
};
@@ -91,7 +91,8 @@ extern int lm3533_ctrlbank_disable(struct lm3533_ctrlbank *cb);
extern int lm3533_ctrlbank_set_brightness(struct lm3533_ctrlbank *cb, u8 val);
extern int lm3533_ctrlbank_get_brightness(struct lm3533_ctrlbank *cb, u8 *val);
-extern int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb, u8 val);
+extern int lm3533_ctrlbank_set_max_current(struct lm3533_ctrlbank *cb,
+ u16 imax);
extern int lm3533_ctrlbank_set_pwm(struct lm3533_ctrlbank *cb, u8 val);
extern int lm3533_ctrlbank_get_pwm(struct lm3533_ctrlbank *cb, u8 *val);
--
1.7.8.5
Hi Samuel,
Here are the final two changes needed to lm3533 core to reflect changes that
have been decided for the subdriver interfaces.
Sorry I didn't have time to submit a v3 with this included before you applied
the patch.
Thanks,
Johan
Johan Hovold (2):
mfd: lm3533: remove unused max-current function
mfd: lm3533: use SI-units for max-current interface
drivers/mfd/lm3533-ctrlbank.c | 44 +++++++++++++++++++++++++++--------------
include/linux/mfd/lm3533.h | 9 +++----
2 files changed, 33 insertions(+), 20 deletions(-)
--
1.7.8.5
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 <[email protected]>
---
v2
- add sysfs-ABI documentation
- open code max_current/pwm macros
v3
- remove max_current attribute
.../ABI/testing/sysfs-class-led-driver-lm3533 | 58 ++
drivers/leds/Kconfig | 13 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-lm3533.c | 704 ++++++++++++++++++++
4 files changed, 776 insertions(+), 0 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-class-led-driver-lm3533
create mode 100644 drivers/leds/leds-lm3533.c
diff --git a/Documentation/ABI/testing/sysfs-class-led-driver-lm3533 b/Documentation/ABI/testing/sysfs-class-led-driver-lm3533
new file mode 100644
index 0000000..a9633fd
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-led-driver-lm3533
@@ -0,0 +1,58 @@
+What: /sys/class/leds/<led>/als
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+Description:
+ Get the id of this led (0..3).
+
+What: /sys/class/leds/<led>/linear
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Set the brightness-mapping mode (0, 1), where
+
+ 0 - exponential mode
+ 1 - linear mode
+
+What: /sys/class/leds/<led>/pwm
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Set the PWM-input control mask (5 bits), where
+
+ bit 5 - PWM-input enabled in Zone 4
+ bit 4 - PWM-input enabled in Zone 3
+ bit 3 - PWM-input enabled in Zone 2
+ bit 2 - PWM-input enabled in Zone 1
+ bit 1 - PWM-input enabled in Zone 0
+ bit 0 - PWM-input enabled
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index ff4b8cf..19bd829 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -50,6 +50,19 @@ config LEDS_LM3530
controlled manually or using PWM input or using ambient
light automatically.
+config LEDS_LM3533
+ tristate "LED support for LM3533"
+ depends on LEDS_CLASS
+ depends on MFD_LM3533
+ help
+ This option enables support for the LEDs on National Semiconductor /
+ TI LM3533 Lighting Power chips.
+
+ The LEDs can be controlled directly, through PWM input, or by the
+ ambient-light-sensor interface. The chip supports
+ hardware-accelerated blinking with maximum on and off periods of 9.8
+ and 77 seconds respectively.
+
config LEDS_LOCOMO
tristate "LED Support for Locomo device"
depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 890481c..f39a526 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o
obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
+obj-$(CONFIG_LEDS_LM3533) += leds-lm3533.o
obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o
obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o
obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o
diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c
new file mode 100644
index 0000000..2112197
--- /dev/null
+++ b/drivers/leds/leds-lm3533.c
@@ -0,0 +1,704 @@
+/*
+ * leds-lm3533.c -- LM3533 LED driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/mfd/core.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_LVCTRLBANK_MIN 2
+#define LM3533_LVCTRLBANK_MAX 5
+#define LM3533_LVCTRLBANK_COUNT 4
+#define LM3533_RISEFALLTIME_MAX 7
+#define LM3533_ALS_LV_MIN 2
+#define LM3533_ALS_LV_MAX 3
+
+#define LM3533_REG_CTRLBANK_BCONF_BASE 0x1b
+#define LM3533_REG_PATTERN_ENABLE 0x28
+#define LM3533_REG_PATTERN_LOW_TIME_BASE 0x71
+#define LM3533_REG_PATTERN_HIGH_TIME_BASE 0x72
+#define LM3533_REG_PATTERN_RISETIME_BASE 0x74
+#define LM3533_REG_PATTERN_FALLTIME_BASE 0x75
+
+#define LM3533_REG_PATTERN_STEP 0x10
+
+#define LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK 0x04
+#define LM3533_REG_CTRLBANK_BCONF_ALS_MASK 0x03
+
+#define LM3533_LED_FLAG_PATTERN_ENABLE 1
+
+
+struct lm3533_led {
+ struct lm3533 *lm3533;
+ struct lm3533_ctrlbank cb;
+ struct led_classdev cdev;
+ int id;
+
+ struct mutex mutex;
+ unsigned long flags;
+
+ struct work_struct work;
+ u8 new_brightness;
+};
+
+#define to_lm3533_led(_cdev) \
+ container_of(_cdev, struct lm3533_led, cdev)
+
+
+static inline int lm3533_led_get_ctrlbank_id(struct lm3533_led *led)
+{
+ return led->id + 2;
+}
+
+static inline u8 lm3533_led_get_lv_reg(struct lm3533_led *led, u8 base)
+{
+ return base + led->id;
+}
+
+static inline u8 lm3533_led_get_pattern(struct lm3533_led *led)
+{
+ return led->id;
+}
+
+static inline u8 lm3533_led_get_pattern_reg(struct lm3533_led *led,
+ u8 base)
+{
+ return base + lm3533_led_get_pattern(led) * LM3533_REG_PATTERN_STEP;
+}
+
+static int lm3533_led_pattern_enable(struct lm3533_led *led, int enable)
+{
+ u8 mask;
+ u8 val;
+ int pattern;
+ int state;
+ int ret = 0;
+
+ dev_dbg(led->cdev.dev, "%s - %d\n", __func__, enable);
+
+ mutex_lock(&led->mutex);
+
+ state = test_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+ if ((enable && state) || (!enable && !state))
+ goto out;
+
+ pattern = lm3533_led_get_pattern(led);
+ mask = 1 << (2 * pattern);
+
+ if (enable)
+ val = mask;
+ else
+ val = 0;
+
+ ret = lm3533_update(led->lm3533, LM3533_REG_PATTERN_ENABLE, val, mask);
+ if (ret) {
+ dev_err(led->cdev.dev, "failed to enable pattern %d (%d)\n",
+ pattern, enable);
+ goto out;
+ }
+
+ __change_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+out:
+ mutex_unlock(&led->mutex);
+
+ return ret;
+}
+
+static void lm3533_led_work(struct work_struct *work)
+{
+ struct lm3533_led *led = container_of(work, struct lm3533_led, work);
+
+ dev_dbg(led->cdev.dev, "%s - %u\n", __func__, led->new_brightness);
+
+ if (led->new_brightness == 0)
+ lm3533_led_pattern_enable(led, 0); /* disable blink */
+
+ lm3533_ctrlbank_set_brightness(&led->cb, led->new_brightness);
+}
+
+static void lm3533_led_set(struct led_classdev *cdev,
+ enum led_brightness value)
+{
+ struct lm3533_led *led = to_lm3533_led(cdev);
+
+ dev_dbg(led->cdev.dev, "%s - %d\n", __func__, value);
+
+ led->new_brightness = value;
+ schedule_work(&led->work);
+}
+
+static enum led_brightness lm3533_led_get(struct led_classdev *cdev)
+{
+ struct lm3533_led *led = to_lm3533_led(cdev);
+ u8 val;
+ int ret;
+
+ ret = lm3533_ctrlbank_get_brightness(&led->cb, &val);
+ if (ret)
+ return ret;
+
+ dev_dbg(led->cdev.dev, "%s - %u\n", __func__, val);
+
+ return val;
+}
+
+/* Pattern generator defines -- delays in us */
+#define LM3533_LED_DELAY_GROUP1_BASE 0x00
+#define LM3533_LED_DELAY_GROUP2_BASE 0x3d
+#define LM3533_LED_DELAY_GROUP3_BASE 0x80
+#define LM3533_LED_DELAY_MAX 0xff
+
+#define LM3533_LED_DELAY_GROUP1_STEP 16384
+#define LM3533_LED_DELAY_GROUP2_STEP 131072
+#define LM3533_LED_DELAY_GROUP3_STEP 524288
+#define LM3533_LED_DELAY_GROUP1_MIN 16384
+#define LM3533_LED_DELAY_GROUP2_MIN 1130496
+#define LM3533_LED_DELAY_GROUP3_MIN 10305536
+#define LM3533_LED_DELAY_GROUP1_MAX 999424
+#define LM3533_LED_DELAY_GROUP2_MAX 9781248
+#define LM3533_LED_DELAY_GROUP3_MAX 76890112
+
+/* Delay limits in ms */
+#define LM3533_LED_DELAY_ON_MAX 9845
+#define LM3533_LED_DELAY_OFF_MAX 77140
+
+static int time_to_val(long *t, long t_min, long t_max, long t_step,
+ int v_min, int v_max)
+{
+ int val;
+
+ *t += t_step / 2;
+ val = (*t - t_min) / t_step + v_min;
+ val = clamp(val, v_min, v_max);
+ *t = t_step * (val - v_min) + t_min;
+
+ return val;
+}
+
+static int lm3533_led_get_delay(long *delay)
+{
+ int val;
+
+ *delay *= 1000;
+
+ if (*delay >= LM3533_LED_DELAY_GROUP3_MIN -
+ LM3533_LED_DELAY_GROUP3_STEP / 2) {
+ val = time_to_val(delay, LM3533_LED_DELAY_GROUP3_MIN,
+ LM3533_LED_DELAY_GROUP3_MAX,
+ LM3533_LED_DELAY_GROUP3_STEP,
+ LM3533_LED_DELAY_GROUP3_BASE,
+ 0xff);
+ } else if (*delay >= LM3533_LED_DELAY_GROUP2_MIN -
+ LM3533_LED_DELAY_GROUP2_STEP / 2) {
+ val = time_to_val(delay, LM3533_LED_DELAY_GROUP2_MIN,
+ LM3533_LED_DELAY_GROUP2_MAX,
+ LM3533_LED_DELAY_GROUP2_STEP,
+ LM3533_LED_DELAY_GROUP2_BASE,
+ LM3533_LED_DELAY_GROUP3_BASE - 1);
+ } else {
+ val = time_to_val(delay, LM3533_LED_DELAY_GROUP1_MIN,
+ LM3533_LED_DELAY_GROUP1_MAX,
+ LM3533_LED_DELAY_GROUP1_STEP,
+ LM3533_LED_DELAY_GROUP1_BASE,
+ LM3533_LED_DELAY_GROUP2_BASE - 1);
+ }
+
+ *delay /= 1000;
+
+ return val;
+}
+
+static int lm3533_led_delay_set(struct lm3533_led *led, u8 base,
+ unsigned long *delay)
+{
+ u8 val;
+ u8 reg;
+ long t;
+ int ret;
+
+ t = *delay;
+ val = lm3533_led_get_delay(&t);
+
+ dev_dbg(led->cdev.dev, "%s - %lu: %ld (0x%02x)\n", __func__,
+ *delay, t, val);
+ reg = lm3533_led_get_pattern_reg(led, base);
+ ret = lm3533_write(led->lm3533, reg, val);
+ if (ret)
+ dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
+
+ *delay = t;
+
+ return ret;
+}
+
+static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
+{
+ *t = min_t(long, *t, LM3533_LED_DELAY_GROUP2_MAX / 1000);
+
+ return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
+}
+
+static int lm3533_led_delay_off_set(struct lm3533_led *led, unsigned long *t)
+{
+ *t = min_t(long, *t, LM3533_LED_DELAY_GROUP3_MAX / 1000);
+
+ return lm3533_led_delay_set(led, LM3533_REG_PATTERN_LOW_TIME_BASE, t);
+}
+
+static int lm3533_led_blink_set(struct led_classdev *cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct lm3533_led *led = to_lm3533_led(cdev);
+ int ret;
+
+ dev_dbg(led->cdev.dev, "%s - on = %lu, off = %lu\n", __func__,
+ *delay_on, *delay_off);
+
+ if (*delay_on > LM3533_LED_DELAY_ON_MAX ||
+ *delay_off > LM3533_LED_DELAY_OFF_MAX)
+ return -EINVAL;
+
+ if (*delay_on == 0 && *delay_off == 0) {
+ *delay_on = 500;
+ *delay_off = 500;
+ }
+
+ ret = lm3533_led_delay_on_set(led, delay_on);
+ if (ret)
+ return ret;
+
+ ret = lm3533_led_delay_off_set(led, delay_off);
+ if (ret)
+ return ret;
+
+ return lm3533_led_pattern_enable(led, 1);
+}
+
+static ssize_t show_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", led->id);
+}
+
+/*
+ * Pattern generator rise/fall times:
+ *
+ * 0 - 2048 us (default)
+ * 1 - 262 ms
+ * 2 - 524 ms
+ * 3 - 1.049 s
+ * 4 - 2.097 s
+ * 5 - 4.194 s
+ * 6 - 8.389 s
+ * 7 - 16.78 s
+ */
+static ssize_t show_risefalltime(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, u8 base)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ ssize_t ret;
+ u8 reg;
+ u8 val;
+
+ reg = lm3533_led_get_pattern_reg(led, base);
+ ret = lm3533_read(led->lm3533, reg, &val);
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%x\n", val);
+}
+
+static ssize_t show_risetime(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return show_risefalltime(dev, attr, buf,
+ LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t show_falltime(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return show_risefalltime(dev, attr, buf,
+ LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+static ssize_t store_risefalltime(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, u8 base)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 val;
+ u8 reg;
+ int ret;
+
+ if (kstrtou8(buf, 0, &val) || val > LM3533_RISEFALLTIME_MAX)
+ return -EINVAL;
+
+ reg = lm3533_led_get_pattern_reg(led, base);
+ ret = lm3533_write(led->lm3533, reg, val);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t store_risetime(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ return store_risefalltime(dev, attr, buf, len,
+ LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t store_falltime(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ return store_risefalltime(dev, attr, buf, len,
+ LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+/*
+ * ALS-control setting:
+ *
+ * 0 - ALS disabled
+ * 2 - ALS-mapper 2
+ * 3 - ALS-mapper 3
+ */
+static ssize_t show_als(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 reg;
+ u8 val;
+ int als;
+ int ret;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ ret = lm3533_read(led->lm3533, reg, &val);
+ if (ret)
+ return ret;
+
+ als = val & LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 als;
+ u8 reg;
+ u8 mask;
+ int ret;
+
+ if (kstrtou8(buf, 0, &als))
+ return -EINVAL;
+
+ if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
+ return -EINVAL;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ mask = LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+ ret = lm3533_update(led->lm3533, reg, als, mask);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 reg;
+ u8 val;
+ int linear;
+ int ret;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ ret = lm3533_read(led->lm3533, reg, &val);
+ if (ret)
+ return ret;
+
+ if (val & LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK)
+ linear = 1;
+ else
+ linear = 0;
+
+ return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ unsigned long linear;
+ u8 reg;
+ u8 mask;
+ u8 val;
+ int ret;
+
+ if (kstrtoul(buf, 0, &linear))
+ return -EINVAL;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ mask = LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK;
+
+ if (linear)
+ val = mask;
+ else
+ val = 0;
+
+ ret = lm3533_update(led->lm3533, reg, val, mask);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t show_pwm(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 val;
+ int ret;
+
+ ret = lm3533_ctrlbank_get_pwm(&led->cb, &val);
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 val;
+ int ret;
+
+ if (kstrtou8(buf, 0, &val))
+ return -EINVAL;
+
+ ret = lm3533_ctrlbank_set_pwm(&led->cb, val);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RW(falltime);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(pwm);
+static LM3533_ATTR_RW(risetime);
+
+static struct attribute *lm3533_led_attributes[] = {
+ &dev_attr_als.attr,
+ &dev_attr_falltime.attr,
+ &dev_attr_id.attr,
+ &dev_attr_linear.attr,
+ &dev_attr_pwm.attr,
+ &dev_attr_risetime.attr,
+ NULL,
+};
+
+static mode_t lm3533_led_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ mode_t mode = attr->mode;
+
+ if (attr == &dev_attr_als.attr) {
+ if (!led->lm3533->have_als)
+ mode = 0;
+ }
+
+ return mode;
+};
+
+static struct attribute_group lm3533_led_attribute_group = {
+ .is_visible = lm3533_led_attr_is_visible,
+ .attrs = lm3533_led_attributes
+};
+
+static int __devinit lm3533_led_setup(struct lm3533_led *led,
+ struct lm3533_led_platform_data *pdata)
+{
+ int ret;
+
+ ret = lm3533_ctrlbank_set_max_current(&led->cb, pdata->max_current);
+ if (ret)
+ return ret;
+
+ return lm3533_ctrlbank_set_pwm(&led->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_led_probe(struct platform_device *pdev)
+{
+ struct lm3533 *lm3533;
+ struct lm3533_led_platform_data *pdata;
+ struct lm3533_led *led;
+ int ret;
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ lm3533 = dev_get_drvdata(pdev->dev.parent);
+ if (!lm3533)
+ return -EINVAL;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ if (pdev->id < 0 || pdev->id >= LM3533_LVCTRLBANK_COUNT) {
+ dev_err(&pdev->dev, "illegal LED id %d\n", pdev->id);
+ return -EINVAL;
+ }
+
+ led = kzalloc(sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->lm3533 = lm3533;
+ led->cdev.name = pdata->name;
+ led->cdev.default_trigger = pdata->default_trigger;
+ led->cdev.brightness_set = lm3533_led_set;
+ led->cdev.brightness_get = lm3533_led_get;
+ led->cdev.blink_set = lm3533_led_blink_set;
+ led->cdev.brightness = LED_OFF;
+ led->id = pdev->id;
+
+ mutex_init(&led->mutex);
+ INIT_WORK(&led->work, lm3533_led_work);
+
+ /* The class framework makes a callback to get brightness during
+ * registration so use parent device (for error reporting) until
+ * registered.
+ */
+ led->cb.lm3533 = lm3533;
+ led->cb.id = lm3533_led_get_ctrlbank_id(led);
+ led->cb.dev = lm3533->dev;
+
+ platform_set_drvdata(pdev, led);
+
+ ret = led_classdev_register(pdev->dev.parent, &led->cdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id);
+ goto err_free;
+ }
+
+ led->cb.dev = led->cdev.dev;
+
+ ret = sysfs_create_group(&led->cdev.dev->kobj,
+ &lm3533_led_attribute_group);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+ goto err_unregister;
+ }
+
+ ret = lm3533_led_setup(led, pdata);
+ if (ret)
+ goto err_sysfs_remove;
+
+ ret = lm3533_ctrlbank_enable(&led->cb);
+ if (ret)
+ goto err_sysfs_remove;
+
+ return 0;
+
+err_sysfs_remove:
+ sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+err_unregister:
+ led_classdev_unregister(&led->cdev);
+ flush_work_sync(&led->work);
+err_free:
+ kfree(led);
+
+ return ret;
+}
+
+static int __devexit lm3533_led_remove(struct platform_device *pdev)
+{
+ struct lm3533_led *led = platform_get_drvdata(pdev);
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ lm3533_ctrlbank_disable(&led->cb);
+ sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+ led_classdev_unregister(&led->cdev);
+ flush_work_sync(&led->work);
+ kfree(led);
+
+ return 0;
+}
+
+static void lm3533_led_shutdown(struct platform_device *pdev)
+{
+
+ struct lm3533_led *led = platform_get_drvdata(pdev);
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ lm3533_ctrlbank_disable(&led->cb);
+ lm3533_led_set(&led->cdev, LED_OFF); /* disable blink */
+ flush_work_sync(&led->work);
+}
+
+static struct platform_driver lm3533_led_driver = {
+ .driver = {
+ .name = "lm3533-leds",
+ .owner = THIS_MODULE,
+ },
+ .probe = lm3533_led_probe,
+ .remove = __devexit_p(lm3533_led_remove),
+ .shutdown = lm3533_led_shutdown,
+};
+module_platform_driver(lm3533_led_driver);
+
+MODULE_AUTHOR("Johan Hovold <[email protected]>");
+MODULE_DESCRIPTION("LM3533 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-leds");
--
1.7.8.5
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 <[email protected]>
---
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 <[email protected]>
+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 <[email protected]>
+Description:
+ Get the id of this backlight (0, 1).
+
+What: /sys/class/backlight/<backlight>/linear
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_DESCRIPTION("LM3533 Backlight driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-backlight");
--
1.7.8.5
On Thu, 10 May 2012 20:27:05 +0200
Johan Hovold <[email protected]> wrote:
> Add sub-driver for the LEDs on National Semiconductor / TI LM3533
> lighting power chips.
>
> The chip provides 256 brightness levels, hardware accelerated blinking
> as well as ambient-light-sensor and pwm input control.
>
>
> ...
>
> +#define to_lm3533_led(_cdev) \
> + container_of(_cdev, struct lm3533_led, cdev)
Minor thing: container_of() is not fully type-safe: it can be passed
the address of any struct which contains a field called cdev and will
return a struct lm3533_led* (or something like that - it has holes...).
A way to fix that is to wrap container_of() in a real C function, not a
macro:
static inline struct lm3533_led *to_lm3533_led(struct struct led_classdev *cdev)
{
return container_of(_cdev, struct lm3533_led, cdev);
}
This has been another episode in the ongoing series "macros are always
wrong" :)
>
> ...
>
> +static int time_to_val(long *t, long t_min, long t_max, long t_step,
> + int v_min, int v_max)
> +{
> + int val;
> +
> + *t += t_step / 2;
> + val = (*t - t_min) / t_step + v_min;
> + val = clamp(val, v_min, v_max);
> + *t = t_step * (val - v_min) + t_min;
> +
> + return val;
> +}
Oh wow, what does all this do. Please, take pity upon the poor reader
and add a comment documenting this function's intent?
> +static int lm3533_led_get_delay(long *delay)
> +{
> + int val;
> +
> + *delay *= 1000;
> +
> + if (*delay >= LM3533_LED_DELAY_GROUP3_MIN -
> + LM3533_LED_DELAY_GROUP3_STEP / 2) {
> + val = time_to_val(delay, LM3533_LED_DELAY_GROUP3_MIN,
> + LM3533_LED_DELAY_GROUP3_MAX,
> + LM3533_LED_DELAY_GROUP3_STEP,
> + LM3533_LED_DELAY_GROUP3_BASE,
> + 0xff);
> + } else if (*delay >= LM3533_LED_DELAY_GROUP2_MIN -
> + LM3533_LED_DELAY_GROUP2_STEP / 2) {
> + val = time_to_val(delay, LM3533_LED_DELAY_GROUP2_MIN,
> + LM3533_LED_DELAY_GROUP2_MAX,
> + LM3533_LED_DELAY_GROUP2_STEP,
> + LM3533_LED_DELAY_GROUP2_BASE,
> + LM3533_LED_DELAY_GROUP3_BASE - 1);
> + } else {
> + val = time_to_val(delay, LM3533_LED_DELAY_GROUP1_MIN,
> + LM3533_LED_DELAY_GROUP1_MAX,
> + LM3533_LED_DELAY_GROUP1_STEP,
> + LM3533_LED_DELAY_GROUP1_BASE,
> + LM3533_LED_DELAY_GROUP2_BASE - 1);
> + }
> +
> + *delay /= 1000;
> +
> + return val;
> +}
And this one, please.
> +static int lm3533_led_delay_set(struct lm3533_led *led, u8 base,
> + unsigned long *delay)
> +{
> + u8 val;
> + u8 reg;
> + long t;
> + int ret;
> +
> + t = *delay;
> + val = lm3533_led_get_delay(&t);
> +
> + dev_dbg(led->cdev.dev, "%s - %lu: %ld (0x%02x)\n", __func__,
> + *delay, t, val);
> + reg = lm3533_led_get_pattern_reg(led, base);
> + ret = lm3533_write(led->lm3533, reg, val);
> + if (ret)
> + dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
> +
> + *delay = t;
> +
> + return ret;
> +}
Should `t' have unsigned long type? I think so. The above functions
confuddle longs with unsigned longs. As a negative delay is an
absurdity, perhaps everything should use unsigned long consistently?
> +static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
> +{
> + *t = min_t(long, *t, LM3533_LED_DELAY_GROUP2_MAX / 1000);
The use of min_t is often a sign that the types are mucked up. How to
fix this?
Are the LM3533_LED_DELAY_* constants logically to be considered to have
unsigned long type? If so, put a "L" after their values and everything
should work out nicely.
> + return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
> +}
> +
>
> ...
>
> +static ssize_t store_als(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> + struct lm3533_led *led = to_lm3533_led(led_cdev);
> + u8 als;
> + u8 reg;
> + u8 mask;
> + int ret;
> +
> + if (kstrtou8(buf, 0, &als))
> + return -EINVAL;
> +
> + if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
> + return -EINVAL;
The `als != 0' test doesn't do anything, and looks odd. Is there some
magical reason why als==0 would be illegal even if LM3533_ALS_LV_MIN
was negative? If so, it should be documented.
> +
> + reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
> + mask = LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
> +
> + ret = lm3533_update(led->lm3533, reg, als, mask);
> + if (ret)
> + return ret;
> +
> + return len;
> +}
> +
>
> ...
>
On Thu, May 10, 2012 at 11:48:17AM -0700, Andrew Morton wrote:
> On Thu, 10 May 2012 20:27:05 +0200
> Johan Hovold <[email protected]> wrote:
>
> > Add sub-driver for the LEDs on National Semiconductor / TI LM3533
> > lighting power chips.
> >
> > The chip provides 256 brightness levels, hardware accelerated blinking
> > as well as ambient-light-sensor and pwm input control.
> >
> > ...
> >
> > +#define to_lm3533_led(_cdev) \
> > + container_of(_cdev, struct lm3533_led, cdev)
>
> Minor thing: container_of() is not fully type-safe: it can be passed
> the address of any struct which contains a field called cdev and will
> return a struct lm3533_led* (or something like that - it has holes...).
>
> A way to fix that is to wrap container_of() in a real C function, not a
> macro:
>
> static inline struct lm3533_led *to_lm3533_led(struct struct led_classdev *cdev)
> {
> return container_of(_cdev, struct lm3533_led, cdev);
> }
>
> This has been another episode in the ongoing series "macros are always
> wrong" :)
Fair enough. :) Seems like the vast majority of drivers still use
convenience macros such as the this one for this kind of use (where the
functions are either passed the class device or it is retrieved through
device driver data).
Do you want me to replace the other three instances of container_of
convenience macros in the iio-subdriver and core (already added to the
mfd tree) as well?
> > ...
> >
> > +static int time_to_val(long *t, long t_min, long t_max, long t_step,
> > + int v_min, int v_max)
> > +{
> > + int val;
> > +
> > + *t += t_step / 2;
> > + val = (*t - t_min) / t_step + v_min;
> > + val = clamp(val, v_min, v_max);
> > + *t = t_step * (val - v_min) + t_min;
> > +
> > + return val;
> > +}
>
> Oh wow, what does all this do. Please, take pity upon the poor reader
> and add a comment documenting this function's intent?
Mapping a time in SI-units (us) to device-specific discrete time
and returning the actual time that will be used (multiple of t_step +
offset).
I'll try to make it more clear and add some comments.
> > +static int lm3533_led_get_delay(long *delay)
> > +{
> > + int val;
> > +
> > + *delay *= 1000;
> > +
> > + if (*delay >= LM3533_LED_DELAY_GROUP3_MIN -
> > + LM3533_LED_DELAY_GROUP3_STEP / 2) {
> > + val = time_to_val(delay, LM3533_LED_DELAY_GROUP3_MIN,
> > + LM3533_LED_DELAY_GROUP3_MAX,
> > + LM3533_LED_DELAY_GROUP3_STEP,
> > + LM3533_LED_DELAY_GROUP3_BASE,
> > + 0xff);
> > + } else if (*delay >= LM3533_LED_DELAY_GROUP2_MIN -
> > + LM3533_LED_DELAY_GROUP2_STEP / 2) {
> > + val = time_to_val(delay, LM3533_LED_DELAY_GROUP2_MIN,
> > + LM3533_LED_DELAY_GROUP2_MAX,
> > + LM3533_LED_DELAY_GROUP2_STEP,
> > + LM3533_LED_DELAY_GROUP2_BASE,
> > + LM3533_LED_DELAY_GROUP3_BASE - 1);
> > + } else {
> > + val = time_to_val(delay, LM3533_LED_DELAY_GROUP1_MIN,
> > + LM3533_LED_DELAY_GROUP1_MAX,
> > + LM3533_LED_DELAY_GROUP1_STEP,
> > + LM3533_LED_DELAY_GROUP1_BASE,
> > + LM3533_LED_DELAY_GROUP2_BASE - 1);
> > + }
> > +
> > + *delay /= 1000;
> > +
> > + return val;
> > +}
>
> And this one, please.
Will do. [ The device has three ranges of supported hardware-accelerated
blink times with different step sizes. ]
> > +static int lm3533_led_delay_set(struct lm3533_led *led, u8 base,
> > + unsigned long *delay)
> > +{
> > + u8 val;
> > + u8 reg;
> > + long t;
> > + int ret;
> > +
> > + t = *delay;
> > + val = lm3533_led_get_delay(&t);
> > +
> > + dev_dbg(led->cdev.dev, "%s - %lu: %ld (0x%02x)\n", __func__,
> > + *delay, t, val);
> > + reg = lm3533_led_get_pattern_reg(led, base);
> > + ret = lm3533_write(led->lm3533, reg, val);
> > + if (ret)
> > + dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
> > +
> > + *delay = t;
> > +
> > + return ret;
> > +}
>
> Should `t' have unsigned long type? I think so. The above functions
> confuddle longs with unsigned longs. As a negative delay is an
> absurdity, perhaps everything should use unsigned long consistently?
Indeed.
> > +static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
> > +{
> > + *t = min_t(long, *t, LM3533_LED_DELAY_GROUP2_MAX / 1000);
>
> The use of min_t is often a sign that the types are mucked up. How to
> fix this?
>
> Are the LM3533_LED_DELAY_* constants logically to be considered to have
> unsigned long type? If so, put a "L" after their values and everything
> should work out nicely.
Will do.
> > + return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
> > +}
> > +
> >
> > ...
> >
> > +static ssize_t store_als(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t len)
> > +{
> > + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> > + struct lm3533_led *led = to_lm3533_led(led_cdev);
> > + u8 als;
> > + u8 reg;
> > + u8 mask;
> > + int ret;
> > +
> > + if (kstrtou8(buf, 0, &als))
> > + return -EINVAL;
> > +
> > + if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
> > + return -EINVAL;
>
> The `als != 0' test doesn't do anything, and looks odd. Is there some
> magical reason why als==0 would be illegal even if LM3533_ALS_LV_MIN
> was negative? If so, it should be documented.
The non-zero-test is not redundant as 0 is the only valid input outside
of [LV_MIN,LV_MAX] (in fact, the only three valid values are 0,2 and 3).
Would you prefer
if ((als < LM3533_ALS_LV_MIN && als != 0) || als > LM3533_ALS_LV_MAX)
return -EINVAL;
or nested conditionals? Or should I simply add a comment?
Thanks,
Johan
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 <[email protected]>
> > > ---
> > >
> > > 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/
On Fri, 11 May 2012 11:54:11 +0200
Johan Hovold <[email protected]> wrote:
> On Thu, May 10, 2012 at 11:48:17AM -0700, Andrew Morton wrote:
> > On Thu, 10 May 2012 20:27:05 +0200
> > Johan Hovold <[email protected]> wrote:
> >
> > > Add sub-driver for the LEDs on National Semiconductor / TI LM3533
> > > lighting power chips.
> > >
> > > The chip provides 256 brightness levels, hardware accelerated blinking
> > > as well as ambient-light-sensor and pwm input control.
> > >
> > > ...
> > >
> > > +#define to_lm3533_led(_cdev) \
> > > + container_of(_cdev, struct lm3533_led, cdev)
> >
> > Minor thing: container_of() is not fully type-safe: it can be passed
> > the address of any struct which contains a field called cdev and will
> > return a struct lm3533_led* (or something like that - it has holes...).
> >
> > A way to fix that is to wrap container_of() in a real C function, not a
> > macro:
> >
> > static inline struct lm3533_led *to_lm3533_led(struct struct led_classdev *cdev)
> > {
> > return container_of(_cdev, struct lm3533_led, cdev);
> > }
> >
> > This has been another episode in the ongoing series "macros are always
> > wrong" :)
>
> Fair enough. :) Seems like the vast majority of drivers still use
> convenience macros such as the this one for this kind of use (where the
> functions are either passed the class device or it is retrieved through
> device driver data).
>
> Do you want me to replace the other three instances of container_of
> convenience macros in the iio-subdriver and core (already added to the
> mfd tree) as well?
Well, it does result in better code. How could I say no? ;)
> > > +static ssize_t store_als(struct device *dev,
> > > + struct device_attribute *attr,
> > > + const char *buf, size_t len)
> > > +{
> > > + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> > > + struct lm3533_led *led = to_lm3533_led(led_cdev);
> > > + u8 als;
> > > + u8 reg;
> > > + u8 mask;
> > > + int ret;
> > > +
> > > + if (kstrtou8(buf, 0, &als))
> > > + return -EINVAL;
> > > +
> > > + if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
> > > + return -EINVAL;
> >
> > The `als != 0' test doesn't do anything, and looks odd. Is there some
> > magical reason why als==0 would be illegal even if LM3533_ALS_LV_MIN
> > was negative? If so, it should be documented.
>
> The non-zero-test is not redundant as 0 is the only valid input outside
> of [LV_MIN,LV_MAX] (in fact, the only three valid values are 0,2 and 3).
ah, OK. One day I'll get the hang of this C thingy.
> Would you prefer
>
> if ((als < LM3533_ALS_LV_MIN && als != 0) || als > LM3533_ALS_LV_MAX)
> return -EINVAL;
>
> or nested conditionals? Or should I simply add a comment?
A comment would be nice. That 0 is also permitted is a surprise.
On Fri, May 11, 2012 at 03:24:36PM -0700, Andrew Morton wrote:
> On Fri, 11 May 2012 11:54:11 +0200
> Johan Hovold <[email protected]> wrote:
> > On Thu, May 10, 2012 at 11:48:17AM -0700, Andrew Morton wrote:
> > > On Thu, 10 May 2012 20:27:05 +0200
> > > Johan Hovold <[email protected]> wrote:
> > >
> > > > Add sub-driver for the LEDs on National Semiconductor / TI LM3533
> > > > lighting power chips.
> > > >
> > > > The chip provides 256 brightness levels, hardware accelerated blinking
> > > > as well as ambient-light-sensor and pwm input control.
[...]
> > > > +static ssize_t store_als(struct device *dev,
> > > > + struct device_attribute *attr,
> > > > + const char *buf, size_t len)
> > > > +{
> > > > + struct led_classdev *led_cdev = dev_get_drvdata(dev);
> > > > + struct lm3533_led *led = to_lm3533_led(led_cdev);
> > > > + u8 als;
> > > > + u8 reg;
> > > > + u8 mask;
> > > > + int ret;
> > > > +
> > > > + if (kstrtou8(buf, 0, &als))
> > > > + return -EINVAL;
> > > > +
> > > > + if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
> > > > + return -EINVAL;
> > >
> > > The `als != 0' test doesn't do anything, and looks odd. Is there some
> > > magical reason why als==0 would be illegal even if LM3533_ALS_LV_MIN
> > > was negative? If so, it should be documented.
> >
> > The non-zero-test is not redundant as 0 is the only valid input outside
> > of [LV_MIN,LV_MAX] (in fact, the only three valid values are 0,2 and 3).
>
> ah, OK. One day I'll get the hang of this C thingy.
>
> > Would you prefer
> >
> > if ((als < LM3533_ALS_LV_MIN && als != 0) || als > LM3533_ALS_LV_MAX)
> > return -EINVAL;
> >
> > or nested conditionals? Or should I simply add a comment?
>
> A comment would be nice. That 0 is also permitted is a surprise.
Actually, there is a comment already documenting the valid values and
it's placed above the show function immediately above the store one:
/*
* ALS-control setting:
*
* 0 - ALS disabled
* 2 - ALS-mapper 2
* 3 - ALS-mapper 3
*/
static ssize_t show_als(struct device *dev,
...
static ssize_t store_als(struct device *dev,
Thanks,
Johan
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 <[email protected]>
---
v2
- add sysfs-ABI documentation
- open code max_current/pwm macros
v3
- remove max_current attribute
v4
- fix return type of attribute is_visible
- replace to_lm3533_leds macro with inline function
- clean up delay handling
.../ABI/testing/sysfs-class-led-driver-lm3533 | 58 ++
drivers/leds/Kconfig | 13 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-lm3533.c | 738 ++++++++++++++++++++
4 files changed, 810 insertions(+), 0 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-class-led-driver-lm3533
create mode 100644 drivers/leds/leds-lm3533.c
diff --git a/Documentation/ABI/testing/sysfs-class-led-driver-lm3533 b/Documentation/ABI/testing/sysfs-class-led-driver-lm3533
new file mode 100644
index 0000000..a9633fd
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-led-driver-lm3533
@@ -0,0 +1,58 @@
+What: /sys/class/leds/<led>/als
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+Description:
+ Get the id of this led (0..3).
+
+What: /sys/class/leds/<led>/linear
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Set the brightness-mapping mode (0, 1), where
+
+ 0 - exponential mode
+ 1 - linear mode
+
+What: /sys/class/leds/<led>/pwm
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Set the PWM-input control mask (5 bits), where
+
+ bit 5 - PWM-input enabled in Zone 4
+ bit 4 - PWM-input enabled in Zone 3
+ bit 3 - PWM-input enabled in Zone 2
+ bit 2 - PWM-input enabled in Zone 1
+ bit 1 - PWM-input enabled in Zone 0
+ bit 0 - PWM-input enabled
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index ff4b8cf..19bd829 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -50,6 +50,19 @@ config LEDS_LM3530
controlled manually or using PWM input or using ambient
light automatically.
+config LEDS_LM3533
+ tristate "LED support for LM3533"
+ depends on LEDS_CLASS
+ depends on MFD_LM3533
+ help
+ This option enables support for the LEDs on National Semiconductor /
+ TI LM3533 Lighting Power chips.
+
+ The LEDs can be controlled directly, through PWM input, or by the
+ ambient-light-sensor interface. The chip supports
+ hardware-accelerated blinking with maximum on and off periods of 9.8
+ and 77 seconds respectively.
+
config LEDS_LOCOMO
tristate "LED Support for Locomo device"
depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 890481c..f39a526 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o
obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
+obj-$(CONFIG_LEDS_LM3533) += leds-lm3533.o
obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o
obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o
obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o
diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c
new file mode 100644
index 0000000..4ef496c
--- /dev/null
+++ b/drivers/leds/leds-lm3533.c
@@ -0,0 +1,738 @@
+/*
+ * leds-lm3533.c -- LM3533 LED driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/mfd/core.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_LVCTRLBANK_MIN 2
+#define LM3533_LVCTRLBANK_MAX 5
+#define LM3533_LVCTRLBANK_COUNT 4
+#define LM3533_RISEFALLTIME_MAX 7
+#define LM3533_ALS_LV_MIN 2
+#define LM3533_ALS_LV_MAX 3
+
+#define LM3533_REG_CTRLBANK_BCONF_BASE 0x1b
+#define LM3533_REG_PATTERN_ENABLE 0x28
+#define LM3533_REG_PATTERN_LOW_TIME_BASE 0x71
+#define LM3533_REG_PATTERN_HIGH_TIME_BASE 0x72
+#define LM3533_REG_PATTERN_RISETIME_BASE 0x74
+#define LM3533_REG_PATTERN_FALLTIME_BASE 0x75
+
+#define LM3533_REG_PATTERN_STEP 0x10
+
+#define LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK 0x04
+#define LM3533_REG_CTRLBANK_BCONF_ALS_MASK 0x03
+
+#define LM3533_LED_FLAG_PATTERN_ENABLE 1
+
+
+struct lm3533_led {
+ struct lm3533 *lm3533;
+ struct lm3533_ctrlbank cb;
+ struct led_classdev cdev;
+ int id;
+
+ struct mutex mutex;
+ unsigned long flags;
+
+ struct work_struct work;
+ u8 new_brightness;
+};
+
+
+static inline struct lm3533_led *to_lm3533_led(struct led_classdev *cdev)
+{
+ return container_of(cdev, struct lm3533_led, cdev);
+}
+
+static inline int lm3533_led_get_ctrlbank_id(struct lm3533_led *led)
+{
+ return led->id + 2;
+}
+
+static inline u8 lm3533_led_get_lv_reg(struct lm3533_led *led, u8 base)
+{
+ return base + led->id;
+}
+
+static inline u8 lm3533_led_get_pattern(struct lm3533_led *led)
+{
+ return led->id;
+}
+
+static inline u8 lm3533_led_get_pattern_reg(struct lm3533_led *led,
+ u8 base)
+{
+ return base + lm3533_led_get_pattern(led) * LM3533_REG_PATTERN_STEP;
+}
+
+static int lm3533_led_pattern_enable(struct lm3533_led *led, int enable)
+{
+ u8 mask;
+ u8 val;
+ int pattern;
+ int state;
+ int ret = 0;
+
+ dev_dbg(led->cdev.dev, "%s - %d\n", __func__, enable);
+
+ mutex_lock(&led->mutex);
+
+ state = test_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+ if ((enable && state) || (!enable && !state))
+ goto out;
+
+ pattern = lm3533_led_get_pattern(led);
+ mask = 1 << (2 * pattern);
+
+ if (enable)
+ val = mask;
+ else
+ val = 0;
+
+ ret = lm3533_update(led->lm3533, LM3533_REG_PATTERN_ENABLE, val, mask);
+ if (ret) {
+ dev_err(led->cdev.dev, "failed to enable pattern %d (%d)\n",
+ pattern, enable);
+ goto out;
+ }
+
+ __change_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+out:
+ mutex_unlock(&led->mutex);
+
+ return ret;
+}
+
+static void lm3533_led_work(struct work_struct *work)
+{
+ struct lm3533_led *led = container_of(work, struct lm3533_led, work);
+
+ dev_dbg(led->cdev.dev, "%s - %u\n", __func__, led->new_brightness);
+
+ if (led->new_brightness == 0)
+ lm3533_led_pattern_enable(led, 0); /* disable blink */
+
+ lm3533_ctrlbank_set_brightness(&led->cb, led->new_brightness);
+}
+
+static void lm3533_led_set(struct led_classdev *cdev,
+ enum led_brightness value)
+{
+ struct lm3533_led *led = to_lm3533_led(cdev);
+
+ dev_dbg(led->cdev.dev, "%s - %d\n", __func__, value);
+
+ led->new_brightness = value;
+ schedule_work(&led->work);
+}
+
+static enum led_brightness lm3533_led_get(struct led_classdev *cdev)
+{
+ struct lm3533_led *led = to_lm3533_led(cdev);
+ u8 val;
+ int ret;
+
+ ret = lm3533_ctrlbank_get_brightness(&led->cb, &val);
+ if (ret)
+ return ret;
+
+ dev_dbg(led->cdev.dev, "%s - %u\n", __func__, val);
+
+ return val;
+}
+
+/* Pattern generator defines (delays in us). */
+#define LM3533_LED_DELAY1_VMIN 0x00
+#define LM3533_LED_DELAY2_VMIN 0x3d
+#define LM3533_LED_DELAY3_VMIN 0x80
+
+#define LM3533_LED_DELAY1_VMAX (LM3533_LED_DELAY2_VMIN - 1)
+#define LM3533_LED_DELAY2_VMAX (LM3533_LED_DELAY3_VMIN - 1)
+#define LM3533_LED_DELAY3_VMAX 0xff
+
+#define LM3533_LED_DELAY1_TMIN 16384U
+#define LM3533_LED_DELAY2_TMIN 1130496U
+#define LM3533_LED_DELAY3_TMIN 10305536U
+
+#define LM3533_LED_DELAY1_TMAX 999424U
+#define LM3533_LED_DELAY2_TMAX 9781248U
+#define LM3533_LED_DELAY3_TMAX 76890112U
+
+/* t_step = (t_max - t_min) / (v_max - v_min) */
+#define LM3533_LED_DELAY1_TSTEP 16384
+#define LM3533_LED_DELAY2_TSTEP 131072
+#define LM3533_LED_DELAY3_TSTEP 524288
+
+/* Delay limits for hardware accelerated blinking (in ms). */
+#define LM3533_LED_DELAY_ON_MAX \
+ ((LM3533_LED_DELAY2_TMAX + LM3533_LED_DELAY2_TSTEP / 2) / 1000)
+#define LM3533_LED_DELAY_OFF_MAX \
+ ((LM3533_LED_DELAY3_TMAX + LM3533_LED_DELAY3_TSTEP / 2) / 1000)
+
+/*
+ * Returns linear map of *t from [t_min,t_max] to [v_min,v_max] with a step
+ * size of t_step, where
+ *
+ * t_step = (t_max - t_min) / (v_max - v_min)
+ *
+ * and updates *t to reflect the mapped value.
+ */
+static u8 time_to_val(unsigned *t, unsigned t_min, unsigned t_step,
+ u8 v_min, u8 v_max)
+{
+ unsigned val;
+
+ val = (*t + t_step / 2 - t_min) / t_step + v_min;
+
+ *t = t_step * (val - v_min) + t_min;
+
+ return (u8)val;
+}
+
+/*
+ * Returns time code corresponding to *delay (in ms) and updates *delay to
+ * reflect actual hardware delay.
+ *
+ * Hardware supports 256 discrete delay times, divided into three groups with
+ * the following ranges and step-sizes:
+ *
+ * [ 16, 999] [0x00, 0x3e] step 16 ms
+ * [ 1130, 9781] [0x3d, 0x7f] step 131 ms
+ * [10306, 76890] [0x80, 0xff] step 524 ms
+ *
+ * Note that delay group 3 is only available for delay_off.
+ */
+static u8 lm3533_led_get_hw_delay(unsigned *delay)
+{
+ unsigned t;
+ u8 val;
+
+ t = *delay * 1000;
+
+ if (t >= (LM3533_LED_DELAY2_TMAX + LM3533_LED_DELAY3_TMIN) / 2) {
+ t = clamp(t, LM3533_LED_DELAY3_TMIN, LM3533_LED_DELAY3_TMAX);
+ val = time_to_val(&t, LM3533_LED_DELAY3_TMIN,
+ LM3533_LED_DELAY3_TSTEP,
+ LM3533_LED_DELAY3_VMIN,
+ LM3533_LED_DELAY3_VMAX);
+ } else if (t >= (LM3533_LED_DELAY1_TMAX + LM3533_LED_DELAY2_TMIN) / 2) {
+ t = clamp(t, LM3533_LED_DELAY2_TMIN, LM3533_LED_DELAY2_TMAX);
+ val = time_to_val(&t, LM3533_LED_DELAY2_TMIN,
+ LM3533_LED_DELAY2_TSTEP,
+ LM3533_LED_DELAY2_VMIN,
+ LM3533_LED_DELAY2_VMAX);
+ } else {
+ t = clamp(t, LM3533_LED_DELAY1_TMIN, LM3533_LED_DELAY1_TMAX);
+ val = time_to_val(&t, LM3533_LED_DELAY1_TMIN,
+ LM3533_LED_DELAY1_TSTEP,
+ LM3533_LED_DELAY1_VMIN,
+ LM3533_LED_DELAY1_VMAX);
+ }
+
+ *delay = (t + 500) / 1000;
+
+ return val;
+}
+
+/*
+ * Set delay register base to *delay (in ms) and update *delay to reflect
+ * actual hardware delay used.
+ */
+static u8 lm3533_led_delay_set(struct lm3533_led *led, u8 base,
+ unsigned long *delay)
+{
+ unsigned t;
+ u8 val;
+ u8 reg;
+ int ret;
+
+ t = (unsigned)*delay;
+
+ /* Delay group 3 is only available for low time (delay off). */
+ if (base != LM3533_REG_PATTERN_LOW_TIME_BASE)
+ t = min(t, LM3533_LED_DELAY2_TMAX / 1000);
+
+ val = lm3533_led_get_hw_delay(&t);
+
+ dev_dbg(led->cdev.dev, "%s - %lu: %u (0x%02x)\n", __func__,
+ *delay, t, val);
+ reg = lm3533_led_get_pattern_reg(led, base);
+ ret = lm3533_write(led->lm3533, reg, val);
+ if (ret)
+ dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
+
+ *delay = t;
+
+ return ret;
+}
+
+static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
+{
+ return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
+}
+
+static int lm3533_led_delay_off_set(struct lm3533_led *led, unsigned long *t)
+{
+ return lm3533_led_delay_set(led, LM3533_REG_PATTERN_LOW_TIME_BASE, t);
+}
+
+static int lm3533_led_blink_set(struct led_classdev *cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct lm3533_led *led = to_lm3533_led(cdev);
+ int ret;
+
+ dev_dbg(led->cdev.dev, "%s - on = %lu, off = %lu\n", __func__,
+ *delay_on, *delay_off);
+
+ if (*delay_on > LM3533_LED_DELAY_ON_MAX ||
+ *delay_off > LM3533_LED_DELAY_OFF_MAX)
+ return -EINVAL;
+
+ if (*delay_on == 0 && *delay_off == 0) {
+ *delay_on = 500;
+ *delay_off = 500;
+ }
+
+ ret = lm3533_led_delay_on_set(led, delay_on);
+ if (ret)
+ return ret;
+
+ ret = lm3533_led_delay_off_set(led, delay_off);
+ if (ret)
+ return ret;
+
+ return lm3533_led_pattern_enable(led, 1);
+}
+
+static ssize_t show_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", led->id);
+}
+
+/*
+ * Pattern generator rise/fall times:
+ *
+ * 0 - 2048 us (default)
+ * 1 - 262 ms
+ * 2 - 524 ms
+ * 3 - 1.049 s
+ * 4 - 2.097 s
+ * 5 - 4.194 s
+ * 6 - 8.389 s
+ * 7 - 16.78 s
+ */
+static ssize_t show_risefalltime(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, u8 base)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ ssize_t ret;
+ u8 reg;
+ u8 val;
+
+ reg = lm3533_led_get_pattern_reg(led, base);
+ ret = lm3533_read(led->lm3533, reg, &val);
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%x\n", val);
+}
+
+static ssize_t show_risetime(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return show_risefalltime(dev, attr, buf,
+ LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t show_falltime(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return show_risefalltime(dev, attr, buf,
+ LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+static ssize_t store_risefalltime(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, u8 base)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 val;
+ u8 reg;
+ int ret;
+
+ if (kstrtou8(buf, 0, &val) || val > LM3533_RISEFALLTIME_MAX)
+ return -EINVAL;
+
+ reg = lm3533_led_get_pattern_reg(led, base);
+ ret = lm3533_write(led->lm3533, reg, val);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t store_risetime(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ return store_risefalltime(dev, attr, buf, len,
+ LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t store_falltime(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ return store_risefalltime(dev, attr, buf, len,
+ LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+/*
+ * ALS-control setting:
+ *
+ * 0 - ALS disabled
+ * 2 - ALS-mapper 2
+ * 3 - ALS-mapper 3
+ */
+static ssize_t show_als(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 reg;
+ u8 val;
+ int als;
+ int ret;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ ret = lm3533_read(led->lm3533, reg, &val);
+ if (ret)
+ return ret;
+
+ als = val & LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", als);
+}
+
+static ssize_t store_als(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 als;
+ u8 reg;
+ u8 mask;
+ int ret;
+
+ if (kstrtou8(buf, 0, &als))
+ return -EINVAL;
+
+ if (als != 0 && (als < LM3533_ALS_LV_MIN || als > LM3533_ALS_LV_MAX))
+ return -EINVAL;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ mask = LM3533_REG_CTRLBANK_BCONF_ALS_MASK;
+
+ ret = lm3533_update(led->lm3533, reg, als, mask);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 reg;
+ u8 val;
+ int linear;
+ int ret;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ ret = lm3533_read(led->lm3533, reg, &val);
+ if (ret)
+ return ret;
+
+ if (val & LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK)
+ linear = 1;
+ else
+ linear = 0;
+
+ return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ unsigned long linear;
+ u8 reg;
+ u8 mask;
+ u8 val;
+ int ret;
+
+ if (kstrtoul(buf, 0, &linear))
+ return -EINVAL;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ mask = LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK;
+
+ if (linear)
+ val = mask;
+ else
+ val = 0;
+
+ ret = lm3533_update(led->lm3533, reg, val, mask);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t show_pwm(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 val;
+ int ret;
+
+ ret = lm3533_ctrlbank_get_pwm(&led->cb, &val);
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 val;
+ int ret;
+
+ if (kstrtou8(buf, 0, &val))
+ return -EINVAL;
+
+ ret = lm3533_ctrlbank_set_pwm(&led->cb, val);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static LM3533_ATTR_RW(als);
+static LM3533_ATTR_RW(falltime);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(pwm);
+static LM3533_ATTR_RW(risetime);
+
+static struct attribute *lm3533_led_attributes[] = {
+ &dev_attr_als.attr,
+ &dev_attr_falltime.attr,
+ &dev_attr_id.attr,
+ &dev_attr_linear.attr,
+ &dev_attr_pwm.attr,
+ &dev_attr_risetime.attr,
+ NULL,
+};
+
+static umode_t lm3533_led_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ umode_t mode = attr->mode;
+
+ if (attr == &dev_attr_als.attr) {
+ if (!led->lm3533->have_als)
+ mode = 0;
+ }
+
+ return mode;
+};
+
+static struct attribute_group lm3533_led_attribute_group = {
+ .is_visible = lm3533_led_attr_is_visible,
+ .attrs = lm3533_led_attributes
+};
+
+static int __devinit lm3533_led_setup(struct lm3533_led *led,
+ struct lm3533_led_platform_data *pdata)
+{
+ int ret;
+
+ ret = lm3533_ctrlbank_set_max_current(&led->cb, pdata->max_current);
+ if (ret)
+ return ret;
+
+ return lm3533_ctrlbank_set_pwm(&led->cb, pdata->pwm);
+}
+
+static int __devinit lm3533_led_probe(struct platform_device *pdev)
+{
+ struct lm3533 *lm3533;
+ struct lm3533_led_platform_data *pdata;
+ struct lm3533_led *led;
+ int ret;
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ lm3533 = dev_get_drvdata(pdev->dev.parent);
+ if (!lm3533)
+ return -EINVAL;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ if (pdev->id < 0 || pdev->id >= LM3533_LVCTRLBANK_COUNT) {
+ dev_err(&pdev->dev, "illegal LED id %d\n", pdev->id);
+ return -EINVAL;
+ }
+
+ led = kzalloc(sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->lm3533 = lm3533;
+ led->cdev.name = pdata->name;
+ led->cdev.default_trigger = pdata->default_trigger;
+ led->cdev.brightness_set = lm3533_led_set;
+ led->cdev.brightness_get = lm3533_led_get;
+ led->cdev.blink_set = lm3533_led_blink_set;
+ led->cdev.brightness = LED_OFF;
+ led->id = pdev->id;
+
+ mutex_init(&led->mutex);
+ INIT_WORK(&led->work, lm3533_led_work);
+
+ /* The class framework makes a callback to get brightness during
+ * registration so use parent device (for error reporting) until
+ * registered.
+ */
+ led->cb.lm3533 = lm3533;
+ led->cb.id = lm3533_led_get_ctrlbank_id(led);
+ led->cb.dev = lm3533->dev;
+
+ platform_set_drvdata(pdev, led);
+
+ ret = led_classdev_register(pdev->dev.parent, &led->cdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id);
+ goto err_free;
+ }
+
+ led->cb.dev = led->cdev.dev;
+
+ ret = sysfs_create_group(&led->cdev.dev->kobj,
+ &lm3533_led_attribute_group);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to create sysfs attributes\n");
+ goto err_unregister;
+ }
+
+ ret = lm3533_led_setup(led, pdata);
+ if (ret)
+ goto err_sysfs_remove;
+
+ ret = lm3533_ctrlbank_enable(&led->cb);
+ if (ret)
+ goto err_sysfs_remove;
+
+ return 0;
+
+err_sysfs_remove:
+ sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+err_unregister:
+ led_classdev_unregister(&led->cdev);
+ flush_work_sync(&led->work);
+err_free:
+ kfree(led);
+
+ return ret;
+}
+
+static int __devexit lm3533_led_remove(struct platform_device *pdev)
+{
+ struct lm3533_led *led = platform_get_drvdata(pdev);
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ lm3533_ctrlbank_disable(&led->cb);
+ sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+ led_classdev_unregister(&led->cdev);
+ flush_work_sync(&led->work);
+ kfree(led);
+
+ return 0;
+}
+
+static void lm3533_led_shutdown(struct platform_device *pdev)
+{
+
+ struct lm3533_led *led = platform_get_drvdata(pdev);
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ lm3533_ctrlbank_disable(&led->cb);
+ lm3533_led_set(&led->cdev, LED_OFF); /* disable blink */
+ flush_work_sync(&led->work);
+}
+
+static struct platform_driver lm3533_led_driver = {
+ .driver = {
+ .name = "lm3533-leds",
+ .owner = THIS_MODULE,
+ },
+ .probe = lm3533_led_probe,
+ .remove = __devexit_p(lm3533_led_remove),
+ .shutdown = lm3533_led_shutdown,
+};
+module_platform_driver(lm3533_led_driver);
+
+MODULE_AUTHOR("Johan Hovold <[email protected]>");
+MODULE_DESCRIPTION("LM3533 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-leds");
--
1.7.8.5
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<[email protected]>
> >>> +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<[email protected]>
> >>> +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
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 <[email protected]>
---
This is a v3 rebased against staging-next of today (93c66ee1186a). Note
that I added calibscale to the platform data and that the modification
of the header file probably needs to go in via mfd once we have agreed
on the type.
Thanks,
Johan
v2:
- reimplement using iio
- add sysfs-ABI documentation
v3
- use indexed channel
- fix sysfs-ABI documentation typo and style
- replace gain attribute with in_illuminance0_calibscale
- add calibscale to platform data
- fix adc register definitions
- replace to_lm3533_dev_attr macro with inline function
- fix device used for error reporting at irq allocation
- use iio device for error reporting during setup/enable
- rebase on staging-next
- fix header include paths
- use dev_to_iio_dev
- add IIO_CHAN_INFO_RAW to info mask
- use iio_device_{alloc,free}
.../Documentation/sysfs-bus-iio-light-lm3533-als | 52 ++
drivers/staging/iio/light/Kconfig | 16 +
drivers/staging/iio/light/Makefile | 1 +
drivers/staging/iio/light/lm3533-als.c | 726 ++++++++++++++++++++
include/linux/mfd/lm3533.h | 3 +
5 files changed, 798 insertions(+), 0 deletions(-)
create mode 100644 drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
create mode 100644 drivers/staging/iio/light/lm3533-als.c
diff --git a/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
new file mode 100644
index 0000000..ba31538
--- /dev/null
+++ b/drivers/staging/iio/Documentation/sysfs-bus-iio-light-lm3533-als
@@ -0,0 +1,52 @@
+What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_calibscale
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Set the ALS calibration scale (internal resistors) for
+ analog input mode, where the scale factor is the current in uA
+ at 2V full-scale (10..1270, 10uA step), that is,
+
+ R_als = 2V / in_illuminance0_calibscale
+
+ This setting is ignored in PWM mode.
+
+What: /sys/.../events/in_illuminance0_thresh_either_en
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Event generated when channel passes one of the four thresholds
+ in each direction (rising|falling) and a zone change occurs.
+ The corresponding light zone can be read from
+ in_illuminance0_zone.
+
+What: /sys/.../events/illuminance_threshY_falling_value
+What: /sys/.../events/illuminance_threshY_raising_value
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Specifies the value of threshold that the device is
+ comparing against for the events enabled by
+ in_illuminance0_thresh_either_en, where Y in 0..3.
+
+ These thresholds correspond to the eight zone-boundary
+ registers (boundaryY_{low,high}) and defines the five light
+ zones.
+
+What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Get the current light zone (0..4) as defined by the
+ in_illuminance0_threshY_{falling,rising} thresholds.
+
+What: /sys/bus/iio/devices/iio:deviceX/targetY_Z
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Set the target brightness for ALS-mapper Y in light zone Z
+ (0..255), where Y in 1..3 and Z in 0..4.
diff --git a/drivers/staging/iio/light/Kconfig b/drivers/staging/iio/light/Kconfig
index 4bed30e..2170124 100644
--- a/drivers/staging/iio/light/Kconfig
+++ b/drivers/staging/iio/light/Kconfig
@@ -35,6 +35,22 @@ config SENSORS_TSL2563
This driver can also be built as a module. If so, the module
will be called tsl2563.
+config SENSORS_LM3533
+ tristate "LM3533 ambient light sensor"
+ depends on MFD_LM3533
+ help
+ If you say yes here you get support for the ambient light sensor
+ interface on National Semiconductor / TI LM3533 Lighting Power
+ chips.
+
+ The sensor interface can be used to control the LEDs and backlights
+ of the chip through defining five light zones and three sets of
+ corresponding brightness target levels.
+
+ The driver provides raw and mean adc readings along with the current
+ light zone through sysfs. A threshold event can be generated on zone
+ changes.
+
config TSL2583
tristate "TAOS TSL2580, TSL2581 and TSL2583 light-to-digital converters"
depends on I2C
diff --git a/drivers/staging/iio/light/Makefile b/drivers/staging/iio/light/Makefile
index 141af1e..a8c6144 100644
--- a/drivers/staging/iio/light/Makefile
+++ b/drivers/staging/iio/light/Makefile
@@ -5,5 +5,6 @@
obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o
obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o
obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o
+obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o
obj-$(CONFIG_TSL2583) += tsl2583.o
obj-$(CONFIG_TSL2x7x) += tsl2x7x_core.o
diff --git a/drivers/staging/iio/light/lm3533-als.c b/drivers/staging/iio/light/lm3533-als.c
new file mode 100644
index 0000000..75b315c
--- /dev/null
+++ b/drivers/staging/iio/light/lm3533-als.c
@@ -0,0 +1,726 @@
+/*
+ * lm3533-als.c -- LM3533 Ambient Light Sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/atomic.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_ALS_ADC_MAX 0xff
+#define LM3533_ALS_BOUNDARY_MAX LM3533_ALS_ADC_MAX
+#define LM3533_ALS_CALIBSCALE_MIN 10
+#define LM3533_ALS_CALIBSCALE_MAX 1270
+#define LM3533_ALS_CALIBSCALE_STEP 10
+#define LM3533_ALS_TARGET_MAX LM3533_ALS_ADC_MAX
+#define LM3533_ALS_ZONE_MAX 4
+
+#define LM3533_REG_ALS_RESISTOR_SELECT 0x30
+#define LM3533_REG_ALS_CONF 0x31
+#define LM3533_REG_ALS_ZONE_INFO 0x34
+#define LM3533_REG_ALS_READ_ADC_RAW 0x37
+#define LM3533_REG_ALS_READ_ADC_AVERAGE 0x38
+#define LM3533_REG_ALS_BOUNDARY0_HIGH 0x50
+#define LM3533_REG_ALS_BOUNDARY0_LOW 0x51
+#define LM3533_REG_ALS_BOUNDARY1_HIGH 0x52
+#define LM3533_REG_ALS_BOUNDARY1_LOW 0x53
+#define LM3533_REG_ALS_BOUNDARY2_HIGH 0x54
+#define LM3533_REG_ALS_BOUNDARY2_LOW 0x55
+#define LM3533_REG_ALS_BOUNDARY3_HIGH 0x56
+#define LM3533_REG_ALS_BOUNDARY3_LOW 0x57
+#define LM3533_REG_ALS_M1_TARGET_0 0x60
+#define LM3533_REG_ALS_M1_TARGET_1 0x61
+#define LM3533_REG_ALS_M1_TARGET_2 0x62
+#define LM3533_REG_ALS_M1_TARGET_3 0x63
+#define LM3533_REG_ALS_M1_TARGET_4 0x64
+#define LM3533_REG_ALS_M2_TARGET_0 0x65
+#define LM3533_REG_ALS_M2_TARGET_1 0x66
+#define LM3533_REG_ALS_M2_TARGET_2 0x67
+#define LM3533_REG_ALS_M2_TARGET_3 0x68
+#define LM3533_REG_ALS_M2_TARGET_4 0x69
+#define LM3533_REG_ALS_M3_TARGET_0 0x6a
+#define LM3533_REG_ALS_M3_TARGET_1 0x6b
+#define LM3533_REG_ALS_M3_TARGET_2 0x6c
+#define LM3533_REG_ALS_M3_TARGET_3 0x6d
+#define LM3533_REG_ALS_M3_TARGET_4 0x6e
+
+#define LM3533_ALS_ENABLE_MASK 0x01
+#define LM3533_ALS_INPUT_MODE_MASK 0x02
+#define LM3533_ALS_INT_ENABLE_MASK 0x01
+
+#define LM3533_ALS_ZONE_SHIFT 2
+#define LM3533_ALS_ZONE_MASK 0x1c
+
+#define LM3533_ALS_FLAG_INT_ENABLED 1
+
+
+struct lm3533_als {
+ struct lm3533 *lm3533;
+
+ unsigned long flags;
+ int irq;
+
+ int pwm_mode:1;
+
+ atomic_t zone;
+};
+
+
+static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
+ int *adc)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 reg;
+ u8 val;
+ int ret;
+
+ if (average)
+ reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
+ else
+ reg = LM3533_REG_ALS_READ_ADC_RAW;
+
+ ret = lm3533_read(als->lm3533, reg, &val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to read adc\n");
+ return ret;
+ }
+
+ *adc = val;
+
+ return 0;
+}
+
+static int lm3533_als_get_calibscale(struct iio_dev *indio_dev, int *scale)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 val;
+ int ret;
+
+ /* calibscale is ignored in pwm-mode */
+ if (als->pwm_mode) {
+ *scale = 0;
+ return 0;
+ }
+
+ ret = lm3533_read(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, &val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to read calibscale\n");
+ return ret;
+ }
+
+ *scale = val * LM3533_ALS_CALIBSCALE_STEP;
+
+ return 0;
+}
+
+static int lm3533_als_set_calibscale(struct iio_dev *indio_dev, int scale)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 val;
+ int ret;
+
+ /* calibscale is ignored in pwm-mode */
+ if (als->pwm_mode)
+ return -EINVAL;
+
+ if (scale < LM3533_ALS_CALIBSCALE_MIN ||
+ scale > LM3533_ALS_CALIBSCALE_MAX)
+ return -EINVAL;
+
+ val = (u8)(scale / LM3533_ALS_CALIBSCALE_STEP);
+
+ ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to write calibscale\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lm3533_als_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ int ret;
+
+ switch (mask) {
+ case 0:
+ ret = lm3533_als_get_adc(indio_dev, false, val);
+ break;
+ case IIO_CHAN_INFO_AVERAGE_RAW:
+ ret = lm3533_als_get_adc(indio_dev, true, val);
+ break;
+ case IIO_CHAN_INFO_CALIBSCALE:
+ ret = lm3533_als_get_calibscale(indio_dev, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+}
+
+static int lm3533_als_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ if (mask != IIO_CHAN_INFO_CALIBSCALE)
+ return -EINVAL;
+
+ return lm3533_als_set_calibscale(indio_dev, val);
+}
+
+static const struct iio_chan_spec lm3533_als_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .channel = 0,
+ .indexed = 1,
+ .info_mask = (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
+ IIO_CHAN_INFO_CALIBSCALE_SEPARATE_BIT |
+ IIO_CHAN_INFO_RAW_SEPARATE_BIT),
+ }
+};
+
+static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 val;
+ int ret;
+
+ ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to read zone\n");
+ return ret;
+ }
+
+ val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
+ *zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
+
+ return 0;
+}
+
+static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
+{
+
+ struct iio_dev *indio_dev = dev_id;
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 zone;
+ int ret;
+
+ /* Clear interrupt by reading the ALS zone register. */
+ ret = lm3533_als_get_zone(indio_dev, &zone);
+ if (ret)
+ goto out;
+
+ atomic_set(&als->zone, zone);
+
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+ 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns());
+out:
+ return IRQ_HANDLED;
+}
+
+static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+ u8 val;
+ int ret;
+
+ if (enable)
+ val = mask;
+ else
+ val = 0;
+
+ ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to set int mode %d\n",
+ enable);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+ u8 val;
+ int ret;
+
+ ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to get int mode\n");
+ return ret;
+ }
+
+ *enable = !!(val & mask);
+
+ return 0;
+}
+
+static int show_thresh_either_en(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als *als = iio_priv(indio_dev);
+ int enable;
+ int ret;
+
+ if (als->irq) {
+ ret = lm3533_als_get_int_mode(indio_dev, &enable);
+ if (ret)
+ return ret;
+ } else {
+ enable = 0;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
+}
+
+static int store_thresh_either_en(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als *als = iio_priv(indio_dev);
+ unsigned long enable;
+ bool int_enabled;
+ u8 zone;
+ int ret;
+
+ if (!als->irq)
+ return -EBUSY;
+
+ if (kstrtoul(buf, 0, &enable))
+ return -EINVAL;
+
+ int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+ if (enable && !int_enabled) {
+ ret = lm3533_als_get_zone(indio_dev, &zone);
+ if (ret)
+ return ret;
+
+ atomic_set(&als->zone, zone);
+
+ set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+ }
+
+ ret = lm3533_als_set_int_mode(indio_dev, enable);
+ if (ret) {
+ if (!int_enabled)
+ clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+ return ret;
+ }
+
+ if (!enable)
+ clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+ return len;
+}
+
+static ssize_t show_zone(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 zone;
+ int ret;
+
+ if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) {
+ zone = atomic_read(&als->zone);
+ } else {
+ ret = lm3533_als_get_zone(indio_dev, &zone);
+ if (ret)
+ return ret;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
+}
+
+struct lm3533_device_attribute {
+ struct device_attribute dev_attr;
+ u8 reg;
+ u8 max;
+};
+
+static inline struct lm3533_device_attribute *
+to_lm3533_dev_attr(struct device_attribute *attr)
+{
+ return container_of(attr, struct lm3533_device_attribute, dev_attr);
+}
+
+static ssize_t show_lm3533_als_reg(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als *als = iio_priv(indio_dev);
+ struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+ u8 val;
+ int ret;
+
+ ret = lm3533_read(als->lm3533, lm3533_attr->reg, &val);
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_lm3533_als_reg(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als *als = iio_priv(indio_dev);
+ struct lm3533_device_attribute *lm3533_attr = to_lm3533_dev_attr(attr);
+ u8 val;
+ int ret;
+
+ if (kstrtou8(buf, 0, &val) || val > lm3533_attr->max)
+ return -EINVAL;
+
+ ret = lm3533_write(als->lm3533, lm3533_attr->reg, val);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+#define REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+ { .dev_attr = __ATTR(_name, _mode, _show, _store), \
+ .reg = _reg, \
+ .max = _max }
+
+#define LM3533_REG_ATTR(_name, _mode, _show, _store, _reg, _max) \
+ struct lm3533_device_attribute lm3533_dev_attr_##_name \
+ = REG_ATTR(_name, _mode, _show, _store, _reg, _max)
+
+#define LM3533_REG_ATTR_RW(_name, _reg, _max) \
+ LM3533_REG_ATTR(_name, S_IRUGO | S_IWUSR, show_lm3533_als_reg, \
+ store_lm3533_als_reg, _reg, _max)
+
+#define ALS_THRESH_FALLING_ATTR_RW(_nr) \
+ LM3533_REG_ATTR_RW(in_illuminance0_thresh##_nr##_falling_value, \
+ LM3533_REG_ALS_BOUNDARY##_nr##_LOW, LM3533_ALS_BOUNDARY_MAX)
+
+#define ALS_THRESH_RAISING_ATTR_RW(_nr) \
+ LM3533_REG_ATTR_RW(in_illuminance0_thresh##_nr##_raising_value, \
+ LM3533_REG_ALS_BOUNDARY##_nr##_HIGH, LM3533_ALS_BOUNDARY_MAX)
+
+/*
+ * ALS Zone thresholds (boundaries)
+ *
+ * in_illuminance0_thresh[0-3]_falling_value 0-255
+ * in_illuminance0_thresh[0-3]_raising_value 0-255
+ */
+static ALS_THRESH_FALLING_ATTR_RW(0);
+static ALS_THRESH_FALLING_ATTR_RW(1);
+static ALS_THRESH_FALLING_ATTR_RW(2);
+static ALS_THRESH_FALLING_ATTR_RW(3);
+
+static ALS_THRESH_RAISING_ATTR_RW(0);
+static ALS_THRESH_RAISING_ATTR_RW(1);
+static ALS_THRESH_RAISING_ATTR_RW(2);
+static ALS_THRESH_RAISING_ATTR_RW(3);
+
+#define LM3533_ALS_ATTR_RO(_name) \
+ DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
+#define LM3533_ALS_ATTR_RW(_name) \
+ DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
+ show_##_name, store_##_name)
+
+/*
+ * ALS Zone threshold-event enable
+ *
+ * in_illuminance0_thresh_either_en 0,1
+ */
+static LM3533_ALS_ATTR_RW(thresh_either_en);
+
+/*
+ * ALS Current Zone
+ *
+ * in_illuminance0_zone 0-4
+ */
+static LM3533_ALS_ATTR_RO(zone);
+
+#define ALS_TARGET_ATTR_RW(_mapper, _nr) \
+ LM3533_REG_ATTR_RW(target##_mapper##_##_nr, \
+ LM3533_REG_ALS_M##_mapper##_TARGET_##_nr, LM3533_ALS_TARGET_MAX)
+
+/*
+ * ALS Mapper targets
+ *
+ * target[1-3]_[0-4] 0-255
+ */
+static ALS_TARGET_ATTR_RW(1, 0);
+static ALS_TARGET_ATTR_RW(1, 1);
+static ALS_TARGET_ATTR_RW(1, 2);
+static ALS_TARGET_ATTR_RW(1, 3);
+static ALS_TARGET_ATTR_RW(1, 4);
+
+static ALS_TARGET_ATTR_RW(2, 0);
+static ALS_TARGET_ATTR_RW(2, 1);
+static ALS_TARGET_ATTR_RW(2, 2);
+static ALS_TARGET_ATTR_RW(2, 3);
+static ALS_TARGET_ATTR_RW(2, 4);
+
+static ALS_TARGET_ATTR_RW(3, 0);
+static ALS_TARGET_ATTR_RW(3, 1);
+static ALS_TARGET_ATTR_RW(3, 2);
+static ALS_TARGET_ATTR_RW(3, 3);
+static ALS_TARGET_ATTR_RW(3, 4);
+
+static struct attribute *lm3533_als_event_attributes[] = {
+ &dev_attr_in_illuminance0_thresh_either_en.attr,
+ &lm3533_dev_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
+ &lm3533_dev_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
+ &lm3533_dev_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
+ &lm3533_dev_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
+ &lm3533_dev_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
+ &lm3533_dev_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
+ &lm3533_dev_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
+ &lm3533_dev_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
+ NULL
+};
+
+static struct attribute_group lm3533_als_event_attribute_group = {
+ .attrs = lm3533_als_event_attributes
+};
+
+static struct attribute *lm3533_als_attributes[] = {
+ &lm3533_dev_attr_target1_0.dev_attr.attr,
+ &lm3533_dev_attr_target1_1.dev_attr.attr,
+ &lm3533_dev_attr_target1_2.dev_attr.attr,
+ &lm3533_dev_attr_target1_3.dev_attr.attr,
+ &lm3533_dev_attr_target1_4.dev_attr.attr,
+ &lm3533_dev_attr_target2_0.dev_attr.attr,
+ &lm3533_dev_attr_target2_1.dev_attr.attr,
+ &lm3533_dev_attr_target2_2.dev_attr.attr,
+ &lm3533_dev_attr_target2_3.dev_attr.attr,
+ &lm3533_dev_attr_target2_4.dev_attr.attr,
+ &lm3533_dev_attr_target3_0.dev_attr.attr,
+ &lm3533_dev_attr_target3_1.dev_attr.attr,
+ &lm3533_dev_attr_target3_2.dev_attr.attr,
+ &lm3533_dev_attr_target3_3.dev_attr.attr,
+ &lm3533_dev_attr_target3_4.dev_attr.attr,
+ &dev_attr_in_illuminance0_zone.attr,
+ NULL
+};
+
+static struct attribute_group lm3533_als_attribute_group = {
+ .attrs = lm3533_als_attributes
+};
+
+static int __devinit lm3533_als_set_input_mode(struct iio_dev *indio_dev,
+ int pwm_mode)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 mask = LM3533_ALS_INPUT_MODE_MASK;
+ u8 val;
+ int ret;
+
+ if (pwm_mode)
+ val = mask; /* pwm input */
+ else
+ val = 0; /* analog input */
+
+ ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
+ if (ret) {
+ dev_err(&indio_dev->dev,
+ "failed to set input mode %d\n", pwm_mode);
+ }
+
+ return ret;
+}
+
+static int __devinit lm3533_als_setup(struct iio_dev *indio_dev,
+ struct lm3533_als_platform_data *pdata)
+{
+ int ret;
+
+ ret = lm3533_als_set_input_mode(indio_dev, pdata->pwm_mode);
+ if (ret)
+ return ret;
+
+ if (!pdata->pwm_mode) {
+ ret = lm3533_als_set_calibscale(indio_dev, pdata->calibscale);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devinit lm3533_als_enable(struct iio_dev *indio_dev)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 mask = LM3533_ALS_ENABLE_MASK;
+ int ret;
+
+ ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
+ if (ret)
+ dev_err(&indio_dev->dev, "failed to enable ALS\n");
+
+ return ret;
+}
+
+static int __devexit lm3533_als_disable(struct iio_dev *indio_dev)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 mask = LM3533_ALS_ENABLE_MASK;
+ int ret;
+
+ ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
+ if (ret)
+ dev_err(&indio_dev->dev, "failed to disable ALS\n");
+
+ return ret;
+}
+
+static const struct iio_info lm3533_als_info = {
+ .attrs = &lm3533_als_attribute_group,
+ .event_attrs = &lm3533_als_event_attribute_group,
+ .driver_module = THIS_MODULE,
+ .read_raw = &lm3533_als_read_raw,
+ .write_raw = &lm3533_als_write_raw,
+};
+
+static int __devinit lm3533_als_probe(struct platform_device *pdev)
+{
+ struct lm3533 *lm3533;
+ struct lm3533_als_platform_data *pdata;
+ struct lm3533_als *als;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ lm3533 = dev_get_drvdata(pdev->dev.parent);
+ if (!lm3533)
+ return -EINVAL;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ indio_dev = iio_device_alloc(sizeof(*als));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ indio_dev->info = &lm3533_als_info;
+ indio_dev->channels = lm3533_als_channels;
+ indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
+ indio_dev->name = "lm3533-als";
+ indio_dev->dev.parent = pdev->dev.parent;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ als = iio_priv(indio_dev);
+ als->lm3533 = lm3533;
+ als->irq = lm3533->irq;
+ als->pwm_mode = pdata->pwm_mode;
+ atomic_set(&als->zone, 0);
+
+ if (als->irq) {
+ ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request irq %d\n",
+ als->irq);
+ goto err_free_dev;
+ }
+ }
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register ALS\n");
+ goto err_free_irq;
+ }
+
+ ret = lm3533_als_setup(indio_dev, pdata);
+ if (ret)
+ goto err_unregister;
+
+ ret = lm3533_als_enable(indio_dev);
+ if (ret)
+ goto err_unregister;
+
+ return 0;
+
+err_unregister:
+ iio_device_unregister(indio_dev);
+err_free_irq:
+ if (als->irq)
+ free_irq(als->irq, indio_dev);
+err_free_dev:
+ iio_device_free(indio_dev);
+
+ return ret;
+}
+
+static int __devexit lm3533_als_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct lm3533_als *als = iio_priv(indio_dev);
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ lm3533_als_disable(indio_dev);
+ iio_device_unregister(indio_dev);
+ if (als->irq)
+ free_irq(als->irq, indio_dev);
+ iio_device_free(indio_dev);
+
+ return 0;
+}
+
+static struct platform_driver lm3533_als_driver = {
+ .driver = {
+ .name = "lm3533-als",
+ .owner = THIS_MODULE,
+ },
+ .probe = lm3533_als_probe,
+ .remove = __devexit_p(lm3533_als_remove),
+};
+module_platform_driver(lm3533_als_driver);
+
+MODULE_AUTHOR("Johan Hovold <[email protected]>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
index 9660feb..c1404cc 100644
--- a/include/linux/mfd/lm3533.h
+++ b/include/linux/mfd/lm3533.h
@@ -43,6 +43,9 @@ struct lm3533_ctrlbank {
struct lm3533_als_platform_data {
unsigned pwm_mode:1; /* PWM input mode (default analog) */
+ u16 calibscale; /* 10 - 1270 uA (10 uA step), current
+ * at 2V full-scale (analog mode)
+ */
};
struct lm3533_bl_platform_data {
--
1.7.8.5
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 <[email protected]>
---
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 <[email protected]>
+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 <[email protected]>
+Description:
+ Get the id of this backlight (0, 1).
+
+What: /sys/class/backlight/<backlight>/linear
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+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 <[email protected]>
+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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+MODULE_DESCRIPTION("LM3533 Backlight driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-backlight");
--
1.7.8.5
On Tue, 15 May 2012 18:46:36 +0200
Johan Hovold <[email protected]> wrote:
> Add sub-driver for the ambient-light-sensor interface on National
> Semiconductor / TI LM3533 lighting power chips.
>
> The sensor interface can be used to control the LEDs and backlights of
> the chip through defining five light zones and three sets of
> corresponding brightness target levels.
>
> The driver provides raw and mean adc readings along with the current
> light zone through sysfs. A threshold event can be generated on zone
> changes.
>
> ...
>
> drivers/staging/iio/light/Kconfig | 16 +
> drivers/staging/iio/light/Makefile | 1 +
> drivers/staging/iio/light/lm3533-als.c | 726 ++++++++++++++++++++
Why is this aimed at drivers/staging/, rather than directly into
drivers/iio/?
On Tue, May 15, 2012 at 12:27:05PM -0700, Andrew Morton wrote:
> On Tue, 15 May 2012 18:46:36 +0200
> Johan Hovold <[email protected]> wrote:
>
> > Add sub-driver for the ambient-light-sensor interface on National
> > Semiconductor / TI LM3533 lighting power chips.
> >
> > The sensor interface can be used to control the LEDs and backlights of
> > the chip through defining five light zones and three sets of
> > corresponding brightness target levels.
> >
> > The driver provides raw and mean adc readings along with the current
> > light zone through sysfs. A threshold event can be generated on zone
> > changes.
> >
> > ...
> >
> > drivers/staging/iio/light/Kconfig | 16 +
> > drivers/staging/iio/light/Makefile | 1 +
> > drivers/staging/iio/light/lm3533-als.c | 726 ++++++++++++++++++++
>
> Why is this aimed at drivers/staging/, rather than directly into
> drivers/iio/?
I understood it as it was only iio core which was moving out of staging
in 3.5, but I see now that some drivers have been added directly to
driver/iio. My mistake.
I'll make sure to move it to drivers/iio/light.
Thanks for pointing it out,
Johan
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<[email protected]>
>>>>> +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<[email protected]>
>>>>> +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
On 05/15/2012 09:00 PM, Johan Hovold wrote:
> On Tue, May 15, 2012 at 12:27:05PM -0700, Andrew Morton wrote:
>> On Tue, 15 May 2012 18:46:36 +0200
>> Johan Hovold <[email protected]> wrote:
>>
>>> Add sub-driver for the ambient-light-sensor interface on National
>>> Semiconductor / TI LM3533 lighting power chips.
>>>
>>> The sensor interface can be used to control the LEDs and backlights of
>>> the chip through defining five light zones and three sets of
>>> corresponding brightness target levels.
>>>
>>> The driver provides raw and mean adc readings along with the current
>>> light zone through sysfs. A threshold event can be generated on zone
>>> changes.
>>>
>>> ...
>>>
>>> drivers/staging/iio/light/Kconfig | 16 +
>>> drivers/staging/iio/light/Makefile | 1 +
>>> drivers/staging/iio/light/lm3533-als.c | 726 ++++++++++++++++++++
>>
>> Why is this aimed at drivers/staging/, rather than directly into
>> drivers/iio/?
>
> I understood it as it was only iio core which was moving out of staging
> in 3.5, but I see now that some drivers have been added directly to
> driver/iio. My mistake.
It was more a case that the core was the priority. Drivers would if
ready and people had time to do the patches!
>
> I'll make sure to move it to drivers/iio/light.
Yup, that's sensible for a new driver like this. I'll be even fussier
about the userspace interfaces though ;)
>
> Thanks for pointing it out,
> Johan
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<[email protected]>
> >>>>> +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<[email protected]>
> >>>>> +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
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<[email protected]>
>>>>>>> +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<[email protected]>
>>>>>>> +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
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<[email protected]>
> >>>>>>> +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<[email protected]>
> >>>>>>> +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
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 <[email protected]>
---
Note that addition of r_select to the platform data probably needs to go
in via mfd.
v2:
- reimplement using iio
- add sysfs-ABI documentation
v3
- use indexed channel
- fix sysfs-ABI documentation typo and style
- replace gain attribute with in_illuminance0_calibscale
- add calibscale to platform data
- fix adc register definitions
- replace to_lm3533_dev_attr macro with inline function
- fix device used for error reporting at irq allocation
- use iio device for error reporting during setup/enable
- rebase on staging-next
- fix header include paths
- use dev_to_iio_dev
- add IIO_CHAN_INFO_RAW to info mask
- use iio_device_{alloc,free}
v4
- move to driver/iio/light
- add events/in_illuminance0_threshY_hysteresis attributes
- fix device zone-boundary quirkiness
- clean up attribute handling
- replace calibscale with device-specific r_select attribute
.../ABI/testing/sysfs-bus-iio-light-lm3533-als | 64 ++
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/light/Kconfig | 22 +
drivers/iio/light/Makefile | 5 +
drivers/iio/light/lm3533-als.c | 941 ++++++++++++++++++++
include/linux/mfd/lm3533.h | 1 +
7 files changed, 1035 insertions(+), 0 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
create mode 100644 drivers/iio/light/Kconfig
create mode 100644 drivers/iio/light/Makefile
create mode 100644 drivers/iio/light/lm3533-als.c
diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
new file mode 100644
index 0000000..7ea1770
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
@@ -0,0 +1,64 @@
+What: /sys/bus/iio/devices/iio:deviceX/r_select
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Set the ALS internal pull-down resistor for analog input mode
+ (1..127), such that,
+
+ R_als = 200000 / r_select (ohm)
+
+ This setting is ignored in PWM-mode (input is always high
+ impedance in PWM-mode).
+
+What: /sys/.../events/in_illuminance0_thresh_either_en
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Event generated when channel passes one of the four thresholds
+ in each direction (rising|falling) and a zone change occurs.
+ The corresponding light zone can be read from
+ in_illuminance0_zone.
+
+What: /sys/.../events/in_illuminance0_threshY_hysteresis
+Date: May 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Get the hysteresis for thresholds Y, that is,
+
+ threshY_hysteresis = threshY_raising - threshY_falling
+
+What: /sys/.../events/illuminance_threshY_falling_value
+What: /sys/.../events/illuminance_threshY_raising_value
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Specifies the value of threshold that the device is
+ comparing against for the events enabled by
+ in_illuminance0_thresh_either_en, where Y in 0..3.
+
+ Note that threshY_falling must be less than or equal to
+ threshY_raising.
+
+ These thresholds correspond to the eight zone-boundary
+ registers (boundaryY_{low,high}) and defines the five light
+ zones.
+
+What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Get the current light zone (0..4) as defined by the
+ in_illuminance0_threshY_{falling,rising} thresholds.
+
+What: /sys/bus/iio/devices/iio:deviceX/targetY_Z
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Set the target brightness for ALS-mapper Y in light zone Z
+ (0..255), where Y in 1..3 and Z in 0..4.
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 56eecef..cacc74d 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -50,5 +50,6 @@ config IIO_CONSUMERS_PER_TRIGGER
source "drivers/iio/adc/Kconfig"
source "drivers/iio/amplifiers/Kconfig"
+source "drivers/iio/light/Kconfig"
endif # IIO
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index e425afd..060b674 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o
obj-y += adc/
obj-y += amplifiers/
+obj-y += light/
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
new file mode 100644
index 0000000..7738a58
--- /dev/null
+++ b/drivers/iio/light/Kconfig
@@ -0,0 +1,22 @@
+#
+# Light sensors
+#
+menu "Light sensors"
+
+config SENSORS_LM3533
+ tristate "LM3533 ambient light sensor"
+ depends on MFD_LM3533
+ help
+ If you say yes here you get support for the ambient light sensor
+ interface on National Semiconductor / TI LM3533 Lighting Power
+ chips.
+
+ The sensor interface can be used to control the LEDs and backlights
+ of the chip through defining five light zones and three sets of
+ corresponding brightness target levels.
+
+ The driver provides raw and mean adc readings along with the current
+ light zone through sysfs. A threshold event can be generated on zone
+ changes.
+
+endmenu
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
new file mode 100644
index 0000000..c1c23a0
--- /dev/null
+++ b/drivers/iio/light/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for IIO Light sensors
+#
+
+obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o
diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
new file mode 100644
index 0000000..5944ac1
--- /dev/null
+++ b/drivers/iio/light/lm3533-als.c
@@ -0,0 +1,941 @@
+/*
+ * lm3533-als.c -- LM3533 Ambient Light Sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/atomic.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_ALS_RESISTOR_MIN 1
+#define LM3533_ALS_RESISTOR_MAX 127
+#define LM3533_ALS_MAPPER_MIN 1
+#define LM3533_ALS_MAPPER_MAX 3
+#define LM3533_ALS_THRESH_MAX 3
+#define LM3533_ALS_ZONE_MAX 4
+
+#define LM3533_REG_ALS_RESISTOR_SELECT 0x30
+#define LM3533_REG_ALS_CONF 0x31
+#define LM3533_REG_ALS_ZONE_INFO 0x34
+#define LM3533_REG_ALS_READ_ADC_RAW 0x37
+#define LM3533_REG_ALS_READ_ADC_AVERAGE 0x38
+#define LM3533_REG_ALS_BOUNDARY_BASE 0x50
+#define LM3533_REG_ALS_TARGET_BASE 0x60
+
+#define LM3533_ALS_ENABLE_MASK 0x01
+#define LM3533_ALS_INPUT_MODE_MASK 0x02
+#define LM3533_ALS_INT_ENABLE_MASK 0x01
+
+#define LM3533_ALS_ZONE_SHIFT 2
+#define LM3533_ALS_ZONE_MASK 0x1c
+
+#define LM3533_ALS_FLAG_INT_ENABLED 1
+
+
+struct lm3533_als {
+ struct lm3533 *lm3533;
+
+ unsigned long flags;
+ int irq;
+
+ int pwm_mode:1;
+
+ atomic_t zone;
+ struct mutex thresh_mutex;
+};
+
+
+static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
+ int *adc)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 reg;
+ u8 val;
+ int ret;
+
+ if (average)
+ reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
+ else
+ reg = LM3533_REG_ALS_READ_ADC_RAW;
+
+ ret = lm3533_read(als->lm3533, reg, &val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to read adc\n");
+ return ret;
+ }
+
+ *adc = val;
+
+ return 0;
+}
+
+static int lm3533_als_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ int ret;
+
+ switch (mask) {
+ case 0:
+ ret = lm3533_als_get_adc(indio_dev, false, val);
+ break;
+ case IIO_CHAN_INFO_AVERAGE_RAW:
+ ret = lm3533_als_get_adc(indio_dev, true, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+}
+
+static const struct iio_chan_spec lm3533_als_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .channel = 0,
+ .indexed = 1,
+ .info_mask = (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
+ IIO_CHAN_INFO_RAW_SEPARATE_BIT),
+ }
+};
+
+static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 val;
+ int ret;
+
+ ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to read zone\n");
+ return ret;
+ }
+
+ val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
+ *zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
+
+ return 0;
+}
+
+static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
+{
+
+ struct iio_dev *indio_dev = dev_id;
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 zone;
+ int ret;
+
+ /* Clear interrupt by reading the ALS zone register. */
+ ret = lm3533_als_get_zone(indio_dev, &zone);
+ if (ret)
+ goto out;
+
+ atomic_set(&als->zone, zone);
+
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+ 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns());
+out:
+ return IRQ_HANDLED;
+}
+
+static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+ u8 val;
+ int ret;
+
+ if (enable)
+ val = mask;
+ else
+ val = 0;
+
+ ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to set int mode %d\n",
+ enable);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+ u8 val;
+ int ret;
+
+ ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to get int mode\n");
+ return ret;
+ }
+
+ *enable = !!(val & mask);
+
+ return 0;
+}
+
+static int lm3533_als_get_resistor(struct iio_dev *indio_dev, u8 *val)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ int ret;
+
+ if (als->pwm_mode) {
+ *val = 0; /* always high impedance */
+ return 0;
+ }
+
+ ret = lm3533_read(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to get resistor\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lm3533_als_set_resistor(struct iio_dev *indio_dev, u8 val)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ int ret;
+
+ if (als->pwm_mode)
+ return -EPERM; /* always high impedance */
+
+ if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX)
+ return -EINVAL;
+
+ ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to set resistor\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * nr -- als mapper 1..3
+ * zone -- als zone 0..4
+ */
+static inline u8 lm3533_als_get_target_reg(unsigned nr, unsigned zone)
+{
+ return LM3533_REG_ALS_TARGET_BASE + 5 * (nr - 1) + zone;
+}
+
+static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned nr,
+ unsigned zone, u8 *val)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 reg;
+ int ret;
+
+ if (nr < LM3533_ALS_MAPPER_MIN || nr > LM3533_ALS_MAPPER_MAX)
+ return -EINVAL;
+
+ if (zone > LM3533_ALS_ZONE_MAX)
+ return -EINVAL;
+
+ reg = lm3533_als_get_target_reg(nr, zone);
+ ret = lm3533_read(als->lm3533, reg, val);
+ if (ret)
+ dev_err(&indio_dev->dev, "failed to get target brightness\n");
+
+ return ret;
+}
+
+static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned nr,
+ unsigned zone, u8 val)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 reg;
+ int ret;
+
+ if (nr < LM3533_ALS_MAPPER_MIN || nr > LM3533_ALS_MAPPER_MAX)
+ return -EINVAL;
+
+ if (zone > LM3533_ALS_ZONE_MAX)
+ return -EINVAL;
+
+ reg = lm3533_als_get_target_reg(nr, zone);
+ ret = lm3533_write(als->lm3533, reg, val);
+ if (ret)
+ dev_err(&indio_dev->dev, "failed to set target brightness\n");
+
+ return ret;
+}
+
+static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
+{
+ u8 offset;
+
+ if (raising)
+ offset = 0;
+ else
+ offset = 1;
+
+ return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
+}
+
+static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
+ bool raising, u8 *val)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 reg;
+ int ret;
+
+ if (nr > LM3533_ALS_THRESH_MAX)
+ return -EINVAL;
+
+ reg = lm3533_als_get_threshold_reg(nr, raising);
+ ret = lm3533_read(als->lm3533, reg, val);
+ if (ret)
+ dev_err(&indio_dev->dev, "failed to get threshold\n");
+
+ return ret;
+}
+
+static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
+ bool raising, u8 val)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 val2;
+ u8 reg;
+ u8 reg2;
+ int ret;
+
+ if (nr > LM3533_ALS_THRESH_MAX)
+ return -EINVAL;
+
+ reg = lm3533_als_get_threshold_reg(nr, raising);
+ reg2 = lm3533_als_get_threshold_reg(nr, !raising);
+
+ mutex_lock(&als->thresh_mutex);
+ ret = lm3533_read(als->lm3533, reg2, &val2);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to get threshold\n");
+ goto out;
+ }
+ /*
+ * This device does not allow negative hysteresis (in fact, it uses
+ * whichever value is smaller as the lower bound) so we need to make
+ * sure that thresh_falling <= thresh_raising.
+ */
+ if ((raising && (val < val2)) || (!raising && (val > val2))) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = lm3533_write(als->lm3533, reg, val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to set threshold\n");
+ goto out;
+ }
+out:
+ mutex_unlock(&als->thresh_mutex);
+
+ return ret;
+}
+
+static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr,
+ u8 *val)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 falling;
+ u8 raising;
+ int ret;
+
+ if (nr > LM3533_ALS_THRESH_MAX)
+ return -EINVAL;
+
+ mutex_lock(&als->thresh_mutex);
+ ret = lm3533_als_get_threshold(indio_dev, nr, false, &falling);
+ if (ret)
+ goto out;
+ ret = lm3533_als_get_threshold(indio_dev, nr, true, &raising);
+ if (ret)
+ goto out;
+
+ *val = raising - falling;
+out:
+ mutex_unlock(&als->thresh_mutex);
+
+ return ret;
+}
+
+static int show_thresh_either_en(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als *als = iio_priv(indio_dev);
+ int enable;
+ int ret;
+
+ if (als->irq) {
+ ret = lm3533_als_get_int_mode(indio_dev, &enable);
+ if (ret)
+ return ret;
+ } else {
+ enable = 0;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
+}
+
+static int store_thresh_either_en(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als *als = iio_priv(indio_dev);
+ unsigned long enable;
+ bool int_enabled;
+ u8 zone;
+ int ret;
+
+ if (!als->irq)
+ return -EBUSY;
+
+ if (kstrtoul(buf, 0, &enable))
+ return -EINVAL;
+
+ int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+ if (enable && !int_enabled) {
+ ret = lm3533_als_get_zone(indio_dev, &zone);
+ if (ret)
+ return ret;
+
+ atomic_set(&als->zone, zone);
+
+ set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+ }
+
+ ret = lm3533_als_set_int_mode(indio_dev, enable);
+ if (ret) {
+ if (!int_enabled)
+ clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+ return ret;
+ }
+
+ if (!enable)
+ clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+ return len;
+}
+
+static ssize_t show_zone(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 zone;
+ int ret;
+
+ if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) {
+ zone = atomic_read(&als->zone);
+ } else {
+ ret = lm3533_als_get_zone(indio_dev, &zone);
+ if (ret)
+ return ret;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
+}
+
+static ssize_t show_r_select(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ u8 r_select;
+ int ret;
+
+ ret = lm3533_als_get_resistor(indio_dev, &r_select);
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", r_select);
+}
+
+static int store_r_select(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ unsigned long r_select;
+ int ret;
+
+ if (kstrtoul(buf, 0, &r_select))
+ return -EINVAL;
+
+ ret = lm3533_als_set_resistor(indio_dev, r_select);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+enum lm3533_als_attribute_type {
+ LM3533_ATTR_TYPE_HYSTERESIS,
+ LM3533_ATTR_TYPE_TARGET,
+ LM3533_ATTR_TYPE_THRESH_FALLING,
+ LM3533_ATTR_TYPE_THRESH_RAISING,
+};
+
+struct lm3533_als_attribute {
+ struct device_attribute dev_attr;
+ enum lm3533_als_attribute_type type;
+ u8 val1;
+ u8 val2;
+};
+
+static inline struct lm3533_als_attribute *
+to_lm3533_als_attr(struct device_attribute *attr)
+{
+ return container_of(attr, struct lm3533_als_attribute, dev_attr);
+}
+
+static ssize_t show_als_attr(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
+ u8 val;
+ int ret;
+
+ switch (als_attr->type) {
+ case LM3533_ATTR_TYPE_HYSTERESIS:
+ ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1,
+ &val);
+ break;
+ case LM3533_ATTR_TYPE_TARGET:
+ ret = lm3533_als_get_target(indio_dev, als_attr->val1,
+ als_attr->val2, &val);
+ break;
+ case LM3533_ATTR_TYPE_THRESH_FALLING:
+ ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
+ false, &val);
+ break;
+ case LM3533_ATTR_TYPE_THRESH_RAISING:
+ ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
+ true, &val);
+ break;
+ default:
+ WARN(1, "%s - bad attribute type %d\n", __func__,
+ als_attr->type);
+ ret = -ENXIO;
+ }
+
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_als_attr(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
+ u8 val;
+ int ret;
+
+ if (kstrtou8(buf, 0, &val))
+ return -EINVAL;
+
+ switch (als_attr->type) {
+ case LM3533_ATTR_TYPE_TARGET:
+ ret = lm3533_als_set_target(indio_dev, als_attr->val1,
+ als_attr->val2, val);
+ break;
+ case LM3533_ATTR_TYPE_THRESH_FALLING:
+ ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
+ false, val);
+ break;
+ case LM3533_ATTR_TYPE_THRESH_RAISING:
+ ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
+ true, val);
+ break;
+ default:
+ WARN(1, "%s - bad attribute type %d\n", __func__,
+ als_attr->type);
+ ret = -ENXIO;
+ }
+
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
+ { .dev_attr = __ATTR(_name, _mode, _show, _store), \
+ .type = _type, \
+ .val1 = _val1, \
+ .val2 = _val2 }
+
+#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
+ struct lm3533_als_attribute lm3533_als_attr_##_name = \
+ ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)
+
+#define ALS_TARGET_ATTR_RW(_nr, _zone) \
+ LM3533_ALS_ATTR(target##_nr##_##_zone, S_IRUGO | S_IWUSR, \
+ show_als_attr, store_als_attr, \
+ LM3533_ATTR_TYPE_TARGET, _nr, _zone)
+/*
+ * ALS Mapper targets
+ *
+ * target[1-3]_[0-4] 0-255
+ */
+static ALS_TARGET_ATTR_RW(1, 0);
+static ALS_TARGET_ATTR_RW(1, 1);
+static ALS_TARGET_ATTR_RW(1, 2);
+static ALS_TARGET_ATTR_RW(1, 3);
+static ALS_TARGET_ATTR_RW(1, 4);
+
+static ALS_TARGET_ATTR_RW(2, 0);
+static ALS_TARGET_ATTR_RW(2, 1);
+static ALS_TARGET_ATTR_RW(2, 2);
+static ALS_TARGET_ATTR_RW(2, 3);
+static ALS_TARGET_ATTR_RW(2, 4);
+
+static ALS_TARGET_ATTR_RW(3, 0);
+static ALS_TARGET_ATTR_RW(3, 1);
+static ALS_TARGET_ATTR_RW(3, 2);
+static ALS_TARGET_ATTR_RW(3, 3);
+static ALS_TARGET_ATTR_RW(3, 4);
+
+#define ALS_THRESH_FALLING_ATTR_RW(_nr) \
+ LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value, \
+ S_IRUGO | S_IWUSR, \
+ show_als_attr, store_als_attr, \
+ LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0)
+
+#define ALS_THRESH_RAISING_ATTR_RW(_nr) \
+ LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value, \
+ S_IRUGO | S_IWUSR, \
+ show_als_attr, store_als_attr, \
+ LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0)
+/*
+ * ALS Zone thresholds (boundaries)
+ *
+ * in_illuminance0_thresh[0-3]_falling_value 0-255
+ * in_illuminance0_thresh[0-3]_raising_value 0-255
+ */
+static ALS_THRESH_FALLING_ATTR_RW(0);
+static ALS_THRESH_FALLING_ATTR_RW(1);
+static ALS_THRESH_FALLING_ATTR_RW(2);
+static ALS_THRESH_FALLING_ATTR_RW(3);
+
+static ALS_THRESH_RAISING_ATTR_RW(0);
+static ALS_THRESH_RAISING_ATTR_RW(1);
+static ALS_THRESH_RAISING_ATTR_RW(2);
+static ALS_THRESH_RAISING_ATTR_RW(3);
+
+#define ALS_HYSTERESIS_ATTR_RO(_nr) \
+ LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis, \
+ S_IRUGO, show_als_attr, NULL, \
+ LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0)
+/*
+ * ALS Zone threshold hysteresis
+ *
+ * threshY_hysteresis = threshY_raising - threshY_falling
+ *
+ * in_illuminance0_thresh[0-3]_hysteresis 0-255
+ * in_illuminance0_thresh[0-3]_hysteresis 0-255
+ */
+static ALS_HYSTERESIS_ATTR_RO(0);
+static ALS_HYSTERESIS_ATTR_RO(1);
+static ALS_HYSTERESIS_ATTR_RO(2);
+static ALS_HYSTERESIS_ATTR_RO(3);
+
+#define ILLUMINANCE_ATTR_RO(_name) \
+ DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
+#define ILLUMINANCE_ATTR_RW(_name) \
+ DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
+ show_##_name, store_##_name)
+/*
+ * ALS Zone threshold-event enable
+ *
+ * in_illuminance0_thresh_either_en 0,1
+ */
+static ILLUMINANCE_ATTR_RW(thresh_either_en);
+
+/*
+ * ALS Current Zone
+ *
+ * in_illuminance0_zone 0-4
+ */
+static ILLUMINANCE_ATTR_RO(zone);
+
+/*
+ * ALS internal pull-down resistor select (analog mode)
+ *
+ * r_select 1-127
+ *
+ * R_als = 200000 / r_select (ohm)
+ */
+static LM3533_ATTR_RW(r_select);
+
+static struct attribute *lm3533_als_event_attributes[] = {
+ &dev_attr_in_illuminance0_thresh_either_en.attr,
+ &lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
+ NULL
+};
+
+static struct attribute_group lm3533_als_event_attribute_group = {
+ .attrs = lm3533_als_event_attributes
+};
+
+static struct attribute *lm3533_als_attributes[] = {
+ &dev_attr_r_select.attr,
+ &lm3533_als_attr_target1_0.dev_attr.attr,
+ &lm3533_als_attr_target1_1.dev_attr.attr,
+ &lm3533_als_attr_target1_2.dev_attr.attr,
+ &lm3533_als_attr_target1_3.dev_attr.attr,
+ &lm3533_als_attr_target1_4.dev_attr.attr,
+ &lm3533_als_attr_target2_0.dev_attr.attr,
+ &lm3533_als_attr_target2_1.dev_attr.attr,
+ &lm3533_als_attr_target2_2.dev_attr.attr,
+ &lm3533_als_attr_target2_3.dev_attr.attr,
+ &lm3533_als_attr_target2_4.dev_attr.attr,
+ &lm3533_als_attr_target3_0.dev_attr.attr,
+ &lm3533_als_attr_target3_1.dev_attr.attr,
+ &lm3533_als_attr_target3_2.dev_attr.attr,
+ &lm3533_als_attr_target3_3.dev_attr.attr,
+ &lm3533_als_attr_target3_4.dev_attr.attr,
+ &dev_attr_in_illuminance0_zone.attr,
+ NULL
+};
+
+static struct attribute_group lm3533_als_attribute_group = {
+ .attrs = lm3533_als_attributes
+};
+
+static int __devinit lm3533_als_set_input_mode(struct iio_dev *indio_dev,
+ int pwm_mode)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 mask = LM3533_ALS_INPUT_MODE_MASK;
+ u8 val;
+ int ret;
+
+ if (pwm_mode)
+ val = mask; /* pwm input */
+ else
+ val = 0; /* analog input */
+
+ ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
+ if (ret) {
+ dev_err(&indio_dev->dev,
+ "failed to set input mode %d\n", pwm_mode);
+ }
+
+ return ret;
+}
+
+static int __devinit lm3533_als_setup(struct iio_dev *indio_dev,
+ struct lm3533_als_platform_data *pdata)
+{
+ int ret;
+
+ ret = lm3533_als_set_input_mode(indio_dev, pdata->pwm_mode);
+ if (ret)
+ return ret;
+
+ /* ALS input is always high impedance in PWM-mode. */
+ if (!pdata->pwm_mode) {
+ ret = lm3533_als_set_resistor(indio_dev, pdata->r_select);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devinit lm3533_als_enable(struct iio_dev *indio_dev)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 mask = LM3533_ALS_ENABLE_MASK;
+ int ret;
+
+ ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
+ if (ret)
+ dev_err(&indio_dev->dev, "failed to enable ALS\n");
+
+ return ret;
+}
+
+static int __devexit lm3533_als_disable(struct iio_dev *indio_dev)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 mask = LM3533_ALS_ENABLE_MASK;
+ int ret;
+
+ ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
+ if (ret)
+ dev_err(&indio_dev->dev, "failed to disable ALS\n");
+
+ return ret;
+}
+
+static const struct iio_info lm3533_als_info = {
+ .attrs = &lm3533_als_attribute_group,
+ .event_attrs = &lm3533_als_event_attribute_group,
+ .driver_module = THIS_MODULE,
+ .read_raw = &lm3533_als_read_raw,
+};
+
+static int __devinit lm3533_als_probe(struct platform_device *pdev)
+{
+ struct lm3533 *lm3533;
+ struct lm3533_als_platform_data *pdata;
+ struct lm3533_als *als;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ lm3533 = dev_get_drvdata(pdev->dev.parent);
+ if (!lm3533)
+ return -EINVAL;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ indio_dev = iio_device_alloc(sizeof(*als));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ indio_dev->info = &lm3533_als_info;
+ indio_dev->channels = lm3533_als_channels;
+ indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
+ indio_dev->name = "lm3533-als";
+ indio_dev->dev.parent = pdev->dev.parent;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ als = iio_priv(indio_dev);
+ als->lm3533 = lm3533;
+ als->irq = lm3533->irq;
+ als->pwm_mode = pdata->pwm_mode;
+ atomic_set(&als->zone, 0);
+ mutex_init(&als->thresh_mutex);
+
+ if (als->irq) {
+ ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request irq %d\n",
+ als->irq);
+ goto err_free_dev;
+ }
+ }
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register ALS\n");
+ goto err_free_irq;
+ }
+
+ ret = lm3533_als_setup(indio_dev, pdata);
+ if (ret)
+ goto err_unregister;
+
+ ret = lm3533_als_enable(indio_dev);
+ if (ret)
+ goto err_unregister;
+
+ return 0;
+
+err_unregister:
+ iio_device_unregister(indio_dev);
+err_free_irq:
+ if (als->irq)
+ free_irq(als->irq, indio_dev);
+err_free_dev:
+ iio_device_free(indio_dev);
+
+ return ret;
+}
+
+static int __devexit lm3533_als_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct lm3533_als *als = iio_priv(indio_dev);
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ lm3533_als_disable(indio_dev);
+ iio_device_unregister(indio_dev);
+ if (als->irq)
+ free_irq(als->irq, indio_dev);
+ iio_device_free(indio_dev);
+
+ return 0;
+}
+
+static struct platform_driver lm3533_als_driver = {
+ .driver = {
+ .name = "lm3533-als",
+ .owner = THIS_MODULE,
+ },
+ .probe = lm3533_als_probe,
+ .remove = __devexit_p(lm3533_als_remove),
+};
+module_platform_driver(lm3533_als_driver);
+
+MODULE_AUTHOR("Johan Hovold <[email protected]>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
index 9660feb..594bc59 100644
--- a/include/linux/mfd/lm3533.h
+++ b/include/linux/mfd/lm3533.h
@@ -43,6 +43,7 @@ struct lm3533_ctrlbank {
struct lm3533_als_platform_data {
unsigned pwm_mode:1; /* PWM input mode (default analog) */
+ u8 r_select; /* 1 - 127 (ignored in PWM-mode) */
};
struct lm3533_bl_platform_data {
--
1.7.8.5
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<[email protected]>
>>>>>>>>> +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<[email protected]>
>>>>>>>>> +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
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
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
On 05/18/2012 02:07 PM, Johan Hovold wrote:
> Add sub-driver for the ambient-light-sensor interface on National
> Semiconductor / TI LM3533 lighting power chips.
>
> The sensor interface can be used to control the LEDs and backlights of
> the chip through defining five light zones and three sets of
> corresponding brightness target levels.
>
> The driver provides raw and mean adc readings along with the current
> light zone through sysfs. A threshold event can be generated on zone
> changes.
Hi Johan,
I hate to be a pain with this one, but it's a complex beast and I'd
really like to get the interface right first time - particularly as
you are going in after the move out of staging.
Queries for you.
1) Ordering in the probe function. Normally expect iio_device_register
to be the last call. Why not here?
2) Worth combining enable / disable into one as very similar functions?
3) Suspicious code in als_set_input_mode
Naming of the target values. I think we can make the naming of these
fit in much better with the normal abi which is going to be all for the
good in the long run. They are basically current output channels
with a controllable set of steps (where we don't have direct control
of which one we are in). This is very similar to the frequency controls
on some of Analog's dds that we have a well defined interface for.
More detail below, but in summary.
out_currentX_currentY_raw for channel X value for zone Y.
Jonathan
>
> Signed-off-by: Johan Hovold <[email protected]>
> ---
>
> Note that addition of r_select to the platform data probably needs to go
> in via mfd.
>
>
> v2:
> - reimplement using iio
> - add sysfs-ABI documentation
> v3
> - use indexed channel
> - fix sysfs-ABI documentation typo and style
> - replace gain attribute with in_illuminance0_calibscale
> - add calibscale to platform data
> - fix adc register definitions
> - replace to_lm3533_dev_attr macro with inline function
> - fix device used for error reporting at irq allocation
> - use iio device for error reporting during setup/enable
> - rebase on staging-next
> - fix header include paths
> - use dev_to_iio_dev
> - add IIO_CHAN_INFO_RAW to info mask
> - use iio_device_{alloc,free}
> v4
> - move to driver/iio/light
> - add events/in_illuminance0_threshY_hysteresis attributes
> - fix device zone-boundary quirkiness
> - clean up attribute handling
> - replace calibscale with device-specific r_select attribute
>
>
> .../ABI/testing/sysfs-bus-iio-light-lm3533-als | 64 ++
> drivers/iio/Kconfig | 1 +
> drivers/iio/Makefile | 1 +
> drivers/iio/light/Kconfig | 22 +
> drivers/iio/light/Makefile | 5 +
> drivers/iio/light/lm3533-als.c | 941 ++++++++++++++++++++
> include/linux/mfd/lm3533.h | 1 +
> 7 files changed, 1035 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> create mode 100644 drivers/iio/light/Kconfig
> create mode 100644 drivers/iio/light/Makefile
> create mode 100644 drivers/iio/light/lm3533-als.c
>
> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> new file mode 100644
> index 0000000..7ea1770
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> @@ -0,0 +1,64 @@
> +What: /sys/bus/iio/devices/iio:deviceX/r_select
> +Date: April 2012
> +KernelVersion: 3.5
> +Contact: Johan Hovold <[email protected]>
> +Description:
> + Set the ALS internal pull-down resistor for analog input mode
> + (1..127), such that,
> +
> + R_als = 200000 / r_select (ohm)
> +
> + This setting is ignored in PWM-mode (input is always high
> + impedance in PWM-mode).
> +
> +What: /sys/.../events/in_illuminance0_thresh_either_en
> +Date: April 2012
> +KernelVersion: 3.5
> +Contact: Johan Hovold <[email protected]>
> +Description:
> + Event generated when channel passes one of the four thresholds
> + in each direction (rising|falling) and a zone change occurs.
> + The corresponding light zone can be read from
> + in_illuminance0_zone.
> +
> +What: /sys/.../events/in_illuminance0_threshY_hysteresis
> +Date: May 2012
> +KernelVersion: 3.5
> +Contact: Johan Hovold <[email protected]>
> +Description:
> + Get the hysteresis for thresholds Y, that is,
> +
> + threshY_hysteresis = threshY_raising - threshY_falling
> +
> +What: /sys/.../events/illuminance_threshY_falling_value
> +What: /sys/.../events/illuminance_threshY_raising_value
> +Date: April 2012
> +KernelVersion: 3.5
> +Contact: Johan Hovold <[email protected]>
> +Description:
> + Specifies the value of threshold that the device is
> + comparing against for the events enabled by
> + in_illuminance0_thresh_either_en, where Y in 0..3.
> +
> + Note that threshY_falling must be less than or equal to
> + threshY_raising.
> +
> + These thresholds correspond to the eight zone-boundary
> + registers (boundaryY_{low,high}) and defines the five light
> + zones.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> +Date: April 2012
> +KernelVersion: 3.5
> +Contact: Johan Hovold <[email protected]>
> +Description:
> + Get the current light zone (0..4) as defined by the
> + in_illuminance0_threshY_{falling,rising} thresholds.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/targetY_Z
> +Date: April 2012
> +KernelVersion: 3.5
> +Contact: Johan Hovold <[email protected]>
> +Description:
> + Set the target brightness for ALS-mapper Y in light zone Z
> + (0..255), where Y in 1..3 and Z in 0..4.
What are the units of this? Also arguably is it not the als that this
is related to, but rather the light source? A quick datasheet browse says
that these are current targets? If so I wonder if we can make that
explicit... Could treat them as 3 output channels and have indexed values
like we do for frequencies in dds devices (where external hardware is
controlling them.
Hmm. lets see.
out_currentX_currentY_raw
(the double naming is a bit clunky but corresponds to frequency devices
where we have
out_altvoltageX_frequencyY_raw
Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
as indexed values they can take 0,1,2,3,4
out_currentX_raw is not read only and gives you the current for whichever
zone the device is currently in.
This may seem convoluted but I'd really rather have something generalizable
for this if we possibly can. We'd still need documentation to say what is
controlling these, but at least they'd fit within our more general abi.
What do you think?
> diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
> index 56eecef..cacc74d 100644
> --- a/drivers/iio/Kconfig
> +++ b/drivers/iio/Kconfig
> @@ -50,5 +50,6 @@ config IIO_CONSUMERS_PER_TRIGGER
>
> source "drivers/iio/adc/Kconfig"
> source "drivers/iio/amplifiers/Kconfig"
> +source "drivers/iio/light/Kconfig"
>
> endif # IIO
> diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
> index e425afd..060b674 100644
> --- a/drivers/iio/Makefile
> +++ b/drivers/iio/Makefile
> @@ -11,3 +11,4 @@ obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o
>
> obj-y += adc/
> obj-y += amplifiers/
> +obj-y += light/
> diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> new file mode 100644
> index 0000000..7738a58
> --- /dev/null
> +++ b/drivers/iio/light/Kconfig
> @@ -0,0 +1,22 @@
> +#
> +# Light sensors
> +#
> +menu "Light sensors"
> +
> +config SENSORS_LM3533
> + tristate "LM3533 ambient light sensor"
> + depends on MFD_LM3533
> + help
> + If you say yes here you get support for the ambient light sensor
> + interface on National Semiconductor / TI LM3533 Lighting Power
> + chips.
> +
> + The sensor interface can be used to control the LEDs and backlights
> + of the chip through defining five light zones and three sets of
> + corresponding brightness target levels.
> +
> + The driver provides raw and mean adc readings along with the current
> + light zone through sysfs. A threshold event can be generated on zone
> + changes.
> +
> +endmenu
> diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
> new file mode 100644
> index 0000000..c1c23a0
> --- /dev/null
> +++ b/drivers/iio/light/Makefile
> @@ -0,0 +1,5 @@
> +#
> +# Makefile for IIO Light sensors
> +#
> +
> +obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o
> diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
> new file mode 100644
> index 0000000..5944ac1
> --- /dev/null
> +++ b/drivers/iio/light/lm3533-als.c
> @@ -0,0 +1,941 @@
> +/*
> + * lm3533-als.c -- LM3533 Ambient Light Sensor driver
> + *
> + * Copyright (C) 2011-2012 Texas Instruments
> + *
> + * Author: Johan Hovold <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; either version 2 of the License, or (at your
> + * option) any later version.
> + */
> +
> +#include <linux/atomic.h>
> +#include <linux/fs.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/iio/events.h>
> +#include <linux/iio/iio.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/mfd/core.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +
> +#include <linux/mfd/lm3533.h>
> +
> +
> +#define LM3533_ALS_RESISTOR_MIN 1
> +#define LM3533_ALS_RESISTOR_MAX 127
> +#define LM3533_ALS_MAPPER_MIN 1
> +#define LM3533_ALS_MAPPER_MAX 3
> +#define LM3533_ALS_THRESH_MAX 3
> +#define LM3533_ALS_ZONE_MAX 4
> +
> +#define LM3533_REG_ALS_RESISTOR_SELECT 0x30
> +#define LM3533_REG_ALS_CONF 0x31
> +#define LM3533_REG_ALS_ZONE_INFO 0x34
> +#define LM3533_REG_ALS_READ_ADC_RAW 0x37
> +#define LM3533_REG_ALS_READ_ADC_AVERAGE 0x38
> +#define LM3533_REG_ALS_BOUNDARY_BASE 0x50
> +#define LM3533_REG_ALS_TARGET_BASE 0x60
> +
> +#define LM3533_ALS_ENABLE_MASK 0x01
> +#define LM3533_ALS_INPUT_MODE_MASK 0x02
> +#define LM3533_ALS_INT_ENABLE_MASK 0x01
> +
> +#define LM3533_ALS_ZONE_SHIFT 2
> +#define LM3533_ALS_ZONE_MASK 0x1c
> +
> +#define LM3533_ALS_FLAG_INT_ENABLED 1
> +
> +
> +struct lm3533_als {
> + struct lm3533 *lm3533;
> +
> + unsigned long flags;
> + int irq;
> +
Boolean might be better as it's not a though this will save
space!
> + int pwm_mode:1;
> +
> + atomic_t zone;
> + struct mutex thresh_mutex;
> +};
Rarely a reason for more than one blank line in my opinion...
> +
> +
May be roll this into it's one call site. will make for marginally
less code I think..
> +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
> + int *adc)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 reg;
> + u8 val;
> + int ret;
> +
> + if (average)
> + reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
> + else
> + reg = LM3533_REG_ALS_READ_ADC_RAW;
> +
> + ret = lm3533_read(als->lm3533, reg, &val);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to read adc\n");
> + return ret;
> + }
> +
> + *adc = val;
> +
> + return 0;
> +}
> +
> +static int lm3533_als_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val, int *val2, long mask)
> +{
> + int ret;
> +
> + switch (mask) {
> + case 0:
> + ret = lm3533_als_get_adc(indio_dev, false, val);
> + break;
> + case IIO_CHAN_INFO_AVERAGE_RAW:
> + ret = lm3533_als_get_adc(indio_dev, true, val);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (ret)
> + return ret;
> +
> + return IIO_VAL_INT;
> +}
> +
Why have an array? Just use the address and set the num_channels = 1;
> +static const struct iio_chan_spec lm3533_als_channels[] = {
> + {
> + .type = IIO_LIGHT,
> + .channel = 0,
> + .indexed = 1,
> + .info_mask = (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
> + IIO_CHAN_INFO_RAW_SEPARATE_BIT),
> + }
> +};
> +
> +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 val;
> + int ret;
> +
> + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to read zone\n");
> + return ret;
> + }
> +
> + val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
> + *zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
> +
> + return 0;
> +}
> +
> +static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
> +{
> +
> + struct iio_dev *indio_dev = dev_id;
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 zone;
> + int ret;
> +
> + /* Clear interrupt by reading the ALS zone register. */
> + ret = lm3533_als_get_zone(indio_dev, &zone);
> + if (ret)
> + goto out;
> +
> + atomic_set(&als->zone, zone);
> +
> + iio_push_event(indio_dev,
> + IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
> + 0,
> + IIO_EV_TYPE_THRESH,
> + IIO_EV_DIR_EITHER),
> + iio_get_time_ns());
> +out:
> + return IRQ_HANDLED;
> +}
> +
could just roll this into the one call point, it's not exactly complex.
> +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> + u8 val;
> + int ret;
> +
> + if (enable)
> + val = mask;
> + else
> + val = 0;
> +
> + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to set int mode %d\n",
> + enable);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> + u8 val;
> + int ret;
> +
> + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to get int mode\n");
> + return ret;
> + }
> +
> + *enable = !!(val & mask);
> +
> + return 0;
> +}
> +
Given only accessed from one place, why not just roll it in there?
> +static int lm3533_als_get_resistor(struct iio_dev *indio_dev, u8 *val)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + int ret;
> +
> + if (als->pwm_mode) {
> + *val = 0; /* always high impedance */
> + return 0;
> + }
> +
> + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to get resistor\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int lm3533_als_set_resistor(struct iio_dev *indio_dev, u8 val)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + int ret;
> +
> + if (als->pwm_mode)
> + return -EPERM; /* always high impedance */
> +
> + if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX)
> + return -EINVAL;
> +
> + ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to set resistor\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * nr -- als mapper 1..3
> + * zone -- als zone 0..4
> + */
> +static inline u8 lm3533_als_get_target_reg(unsigned nr, unsigned zone)
> +{
> + return LM3533_REG_ALS_TARGET_BASE + 5 * (nr - 1) + zone;
> +}
> +
> +static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned nr,
> + unsigned zone, u8 *val)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 reg;
> + int ret;
> +
> + if (nr < LM3533_ALS_MAPPER_MIN || nr > LM3533_ALS_MAPPER_MAX)
> + return -EINVAL;
> +
> + if (zone > LM3533_ALS_ZONE_MAX)
> + return -EINVAL;
> +
> + reg = lm3533_als_get_target_reg(nr, zone);
> + ret = lm3533_read(als->lm3533, reg, val);
> + if (ret)
> + dev_err(&indio_dev->dev, "failed to get target brightness\n");
> +
> + return ret;
> +}
> +
> +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned nr,
> + unsigned zone, u8 val)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 reg;
> + int ret;
> +
> + if (nr < LM3533_ALS_MAPPER_MIN || nr > LM3533_ALS_MAPPER_MAX)
> + return -EINVAL;
> +
> + if (zone > LM3533_ALS_ZONE_MAX)
> + return -EINVAL;
> +
> + reg = lm3533_als_get_target_reg(nr, zone);
> + ret = lm3533_write(als->lm3533, reg, val);
I wouldn't bother with the intermediate but up to you...
ret = lm3533_write(als->lm3533, lm3533_als_get_target_reg(nr, zone), val);
> + if (ret)
> + dev_err(&indio_dev->dev, "failed to set target brightness\n");
> +
> + return ret;
> +}
> +
> +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
> +{
> + u8 offset;
> +
offset = !raising;
> + if (raising)
> + offset = 0;
> + else
> + offset = 1;
> +
> + return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
> +}
> +
> +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
> + bool raising, u8 *val)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 reg;
> + int ret;
> +
> + if (nr > LM3533_ALS_THRESH_MAX)
> + return -EINVAL;
> +
> + reg = lm3533_als_get_threshold_reg(nr, raising);
> + ret = lm3533_read(als->lm3533, reg, val);
> + if (ret)
> + dev_err(&indio_dev->dev, "failed to get threshold\n");
> +
> + return ret;
> +}
> +
> +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
> + bool raising, u8 val)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 val2;
> + u8 reg;
> + u8 reg2;
u8 val2, reg, reg2; Shorter and still obvious.
> + int ret;
> +
> + if (nr > LM3533_ALS_THRESH_MAX)
> + return -EINVAL;
> +
> + reg = lm3533_als_get_threshold_reg(nr, raising);
> + reg2 = lm3533_als_get_threshold_reg(nr, !raising);
> +
> + mutex_lock(&als->thresh_mutex);
> + ret = lm3533_read(als->lm3533, reg2, &val2);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to get threshold\n");
> + goto out;
> + }
> + /*
> + * This device does not allow negative hysteresis (in fact, it uses
> + * whichever value is smaller as the lower bound) so we need to make
> + * sure that thresh_falling <= thresh_raising.
> + */
> + if ((raising && (val < val2)) || (!raising && (val > val2))) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + ret = lm3533_write(als->lm3533, reg, val);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to set threshold\n");
> + goto out;
> + }
> +out:
> + mutex_unlock(&als->thresh_mutex);
> +
> + return ret;
> +}
> +
> +static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr,
> + u8 *val)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 falling;
> + u8 raising;
> + int ret;
> +
> + if (nr > LM3533_ALS_THRESH_MAX)
> + return -EINVAL;
> +
> + mutex_lock(&als->thresh_mutex);
> + ret = lm3533_als_get_threshold(indio_dev, nr, false, &falling);
> + if (ret)
> + goto out;
> + ret = lm3533_als_get_threshold(indio_dev, nr, true, &raising);
> + if (ret)
> + goto out;
> +
> + *val = raising - falling;
> +out:
> + mutex_unlock(&als->thresh_mutex);
> +
> + return ret;
> +}
> +
> +static int show_thresh_either_en(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct lm3533_als *als = iio_priv(indio_dev);
> + int enable;
> + int ret;
> +
> + if (als->irq) {
> + ret = lm3533_als_get_int_mode(indio_dev, &enable);
> + if (ret)
> + return ret;
> + } else {
> + enable = 0;
> + }
> +
> + return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
> +}
> +
> +static int store_thresh_either_en(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct lm3533_als *als = iio_priv(indio_dev);
> + unsigned long enable;
> + bool int_enabled;
> + u8 zone;
> + int ret;
> +
> + if (!als->irq)
> + return -EBUSY;
> +
> + if (kstrtoul(buf, 0, &enable))
> + return -EINVAL;
> +
> + int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
> +
> + if (enable && !int_enabled) {
> + ret = lm3533_als_get_zone(indio_dev, &zone);
> + if (ret)
> + return ret;
> +
> + atomic_set(&als->zone, zone);
> +
> + set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
> + }
> +
> + ret = lm3533_als_set_int_mode(indio_dev, enable);
> + if (ret) {
> + if (!int_enabled)
> + clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
> +
> + return ret;
> + }
> +
> + if (!enable)
> + clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
> +
> + return len;
> +}
> +
> +static ssize_t show_zone(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 zone;
> + int ret;
> +
> + if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) {
> + zone = atomic_read(&als->zone);
> + } else {
> + ret = lm3533_als_get_zone(indio_dev, &zone);
> + if (ret)
> + return ret;
> + }
> +
> + return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
> +}
> +
> +static ssize_t show_r_select(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + u8 r_select;
> + int ret;
> +
> + ret = lm3533_als_get_resistor(indio_dev, &r_select);
> + if (ret)
> + return ret;
> +
> + return scnprintf(buf, PAGE_SIZE, "%u\n", r_select);
> +}
> +
> +static int store_r_select(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + unsigned long r_select;
> + int ret;
> +
> + if (kstrtoul(buf, 0, &r_select))
> + return -EINVAL;
> +
> + ret = lm3533_als_set_resistor(indio_dev, r_select);
> + if (ret)
> + return ret;
> +
> + return len;
> +}
> +
> +enum lm3533_als_attribute_type {
> + LM3533_ATTR_TYPE_HYSTERESIS,
> + LM3533_ATTR_TYPE_TARGET,
> + LM3533_ATTR_TYPE_THRESH_FALLING,
> + LM3533_ATTR_TYPE_THRESH_RAISING,
> +};
> +
> +struct lm3533_als_attribute {
> + struct device_attribute dev_attr;
> + enum lm3533_als_attribute_type type;
> + u8 val1;
> + u8 val2;
> +};
> +
> +static inline struct lm3533_als_attribute *
> +to_lm3533_als_attr(struct device_attribute *attr)
> +{
> + return container_of(attr, struct lm3533_als_attribute, dev_attr);
> +}
> +
> +static ssize_t show_als_attr(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> + u8 val;
> + int ret;
> +
> + switch (als_attr->type) {
> + case LM3533_ATTR_TYPE_HYSTERESIS:
> + ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1,
> + &val);
> + break;
> + case LM3533_ATTR_TYPE_TARGET:
> + ret = lm3533_als_get_target(indio_dev, als_attr->val1,
> + als_attr->val2, &val);
> + break;
> + case LM3533_ATTR_TYPE_THRESH_FALLING:
> + ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
> + false, &val);
> + break;
> + case LM3533_ATTR_TYPE_THRESH_RAISING:
> + ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
> + true, &val);
> + break;
> + default:
> + WARN(1, "%s - bad attribute type %d\n", __func__,
> + als_attr->type);
> + ret = -ENXIO;
> + }
> +
> + if (ret)
> + return ret;
> +
> + return scnprintf(buf, PAGE_SIZE, "%u\n", val);
> +}
> +
> +static ssize_t store_als_attr(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> + u8 val;
> + int ret;
> +
> + if (kstrtou8(buf, 0, &val))
> + return -EINVAL;
> +
> + switch (als_attr->type) {
> + case LM3533_ATTR_TYPE_TARGET:
> + ret = lm3533_als_set_target(indio_dev, als_attr->val1,
> + als_attr->val2, val);
> + break;
> + case LM3533_ATTR_TYPE_THRESH_FALLING:
> + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> + false, val);
> + break;
> + case LM3533_ATTR_TYPE_THRESH_RAISING:
> + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> + true, val);
> + break;
> + default:
I'd be tempted to drop this. It is easy to verify whether it will occur.
> + WARN(1, "%s - bad attribute type %d\n", __func__,
> + als_attr->type);
> + ret = -ENXIO;
> + }
> +
> + if (ret)
> + return ret;
> +
> + return len;
> +}
> +
> +#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
> + { .dev_attr = __ATTR(_name, _mode, _show, _store), \
> + .type = _type, \
> + .val1 = _val1, \
> + .val2 = _val2 }
> +
> +#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
> + struct lm3533_als_attribute lm3533_als_attr_##_name = \
> + ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)
> +
> +#define ALS_TARGET_ATTR_RW(_nr, _zone) \
> + LM3533_ALS_ATTR(target##_nr##_##_zone, S_IRUGO | S_IWUSR, \
> + show_als_attr, store_als_attr, \
> + LM3533_ATTR_TYPE_TARGET, _nr, _zone)
> +/*
> + * ALS Mapper targets
> + *
> + * target[1-3]_[0-4] 0-255
> + */
> +static ALS_TARGET_ATTR_RW(1, 0);
> +static ALS_TARGET_ATTR_RW(1, 1);
> +static ALS_TARGET_ATTR_RW(1, 2);
> +static ALS_TARGET_ATTR_RW(1, 3);
> +static ALS_TARGET_ATTR_RW(1, 4);
> +
> +static ALS_TARGET_ATTR_RW(2, 0);
> +static ALS_TARGET_ATTR_RW(2, 1);
> +static ALS_TARGET_ATTR_RW(2, 2);
> +static ALS_TARGET_ATTR_RW(2, 3);
> +static ALS_TARGET_ATTR_RW(2, 4);
> +
> +static ALS_TARGET_ATTR_RW(3, 0);
> +static ALS_TARGET_ATTR_RW(3, 1);
> +static ALS_TARGET_ATTR_RW(3, 2);
> +static ALS_TARGET_ATTR_RW(3, 3);
> +static ALS_TARGET_ATTR_RW(3, 4);
> +
> +#define ALS_THRESH_FALLING_ATTR_RW(_nr) \
> + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value, \
> + S_IRUGO | S_IWUSR, \
> + show_als_attr, store_als_attr, \
> + LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0)
> +
> +#define ALS_THRESH_RAISING_ATTR_RW(_nr) \
> + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value, \
> + S_IRUGO | S_IWUSR, \
> + show_als_attr, store_als_attr, \
> + LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0)
> +/*
> + * ALS Zone thresholds (boundaries)
> + *
> + * in_illuminance0_thresh[0-3]_falling_value 0-255
> + * in_illuminance0_thresh[0-3]_raising_value 0-255
> + */
> +static ALS_THRESH_FALLING_ATTR_RW(0);
> +static ALS_THRESH_FALLING_ATTR_RW(1);
> +static ALS_THRESH_FALLING_ATTR_RW(2);
> +static ALS_THRESH_FALLING_ATTR_RW(3);
> +
> +static ALS_THRESH_RAISING_ATTR_RW(0);
> +static ALS_THRESH_RAISING_ATTR_RW(1);
> +static ALS_THRESH_RAISING_ATTR_RW(2);
> +static ALS_THRESH_RAISING_ATTR_RW(3);
> +
> +#define ALS_HYSTERESIS_ATTR_RO(_nr) \
> + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis, \
> + S_IRUGO, show_als_attr, NULL, \
> + LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0)
> +/*
> + * ALS Zone threshold hysteresis
> + *
> + * threshY_hysteresis = threshY_raising - threshY_falling
> + *
> + * in_illuminance0_thresh[0-3]_hysteresis 0-255
> + * in_illuminance0_thresh[0-3]_hysteresis 0-255
> + */
> +static ALS_HYSTERESIS_ATTR_RO(0);
> +static ALS_HYSTERESIS_ATTR_RO(1);
> +static ALS_HYSTERESIS_ATTR_RO(2);
> +static ALS_HYSTERESIS_ATTR_RO(3);
> +
> +#define ILLUMINANCE_ATTR_RO(_name) \
> + DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
> +#define ILLUMINANCE_ATTR_RW(_name) \
> + DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
> + show_##_name, store_##_name)
> +/*
> + * ALS Zone threshold-event enable
> + *
> + * in_illuminance0_thresh_either_en 0,1
> + */
> +static ILLUMINANCE_ATTR_RW(thresh_either_en);
> +
> +/*
> + * ALS Current Zone
> + *
> + * in_illuminance0_zone 0-4
> + */
> +static ILLUMINANCE_ATTR_RO(zone);
> +
> +/*
> + * ALS internal pull-down resistor select (analog mode)
> + *
> + * r_select 1-127
> + *
> + * R_als = 200000 / r_select (ohm)
> + */
> +static LM3533_ATTR_RW(r_select);
> +
> +static struct attribute *lm3533_als_event_attributes[] = {
> + &dev_attr_in_illuminance0_thresh_either_en.attr,
> + &lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
Just to verify the hysteresis applies to bother thresh0_falling and
thresh0_rising?
> + &lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
> + NULL
> +};
> +
> +static struct attribute_group lm3533_als_event_attribute_group = {
> + .attrs = lm3533_als_event_attributes
> +};
> +
> +static struct attribute *lm3533_als_attributes[] = {
> + &dev_attr_r_select.attr,
Just to keep info in one place. We agreed in other branch
of thread that r_select would be done with platform data.
I wonder if we can make the naming a little clearer for these.. hmm.
> + &lm3533_als_attr_target1_0.dev_attr.attr,
> + &lm3533_als_attr_target1_1.dev_attr.attr,
> + &lm3533_als_attr_target1_2.dev_attr.attr,
> + &lm3533_als_attr_target1_3.dev_attr.attr,
> + &lm3533_als_attr_target1_4.dev_attr.attr,
> + &lm3533_als_attr_target2_0.dev_attr.attr,
> + &lm3533_als_attr_target2_1.dev_attr.attr,
> + &lm3533_als_attr_target2_2.dev_attr.attr,
> + &lm3533_als_attr_target2_3.dev_attr.attr,
> + &lm3533_als_attr_target2_4.dev_attr.attr,
> + &lm3533_als_attr_target3_0.dev_attr.attr,
> + &lm3533_als_attr_target3_1.dev_attr.attr,
> + &lm3533_als_attr_target3_2.dev_attr.attr,
> + &lm3533_als_attr_target3_3.dev_attr.attr,
> + &lm3533_als_attr_target3_4.dev_attr.attr,
> + &dev_attr_in_illuminance0_zone.attr,
> + NULL
> +};
> +
> +static struct attribute_group lm3533_als_attribute_group = {
> + .attrs = lm3533_als_attributes
> +};
> +
> +static int __devinit lm3533_als_set_input_mode(struct iio_dev *indio_dev,
> + int pwm_mode)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 mask = LM3533_ALS_INPUT_MODE_MASK;
> + u8 val;
Just use mask directly, why introduce val as well
(particularly as you don't use it ;)
> + int ret;
> +
> + if (pwm_mode)
> + val = mask; /* pwm input */
> + else
> + val = 0; /* analog input */
> +
Why have val?
> + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
> + if (ret) {
> + dev_err(&indio_dev->dev,
> + "failed to set input mode %d\n", pwm_mode);
> + }
> +
> + return ret;
> +}
> +
> +static int __devinit lm3533_als_setup(struct iio_dev *indio_dev,
> + struct lm3533_als_platform_data *pdata)
> +{
> + int ret;
> +
> + ret = lm3533_als_set_input_mode(indio_dev, pdata->pwm_mode);
> + if (ret)
> + return ret;
> +
> + /* ALS input is always high impedance in PWM-mode. */
> + if (!pdata->pwm_mode) {
> + ret = lm3533_als_set_resistor(indio_dev, pdata->r_select);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
This enable / disable pair does rather look like it could
be combined into one function and save a few lines of repeated
code
> +static int __devinit lm3533_als_enable(struct iio_dev *indio_dev)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 mask = LM3533_ALS_ENABLE_MASK;
> + int ret;
> +
> + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
> + if (ret)
> + dev_err(&indio_dev->dev, "failed to enable ALS\n");
> +
> + return ret;
> +}
> +
> +static int __devexit lm3533_als_disable(struct iio_dev *indio_dev)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 mask = LM3533_ALS_ENABLE_MASK;
> + int ret;
> +
> + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
> + if (ret)
> + dev_err(&indio_dev->dev, "failed to disable ALS\n");
> +
> + return ret;
> +}
> +
> +static const struct iio_info lm3533_als_info = {
> + .attrs = &lm3533_als_attribute_group,
> + .event_attrs = &lm3533_als_event_attribute_group,
> + .driver_module = THIS_MODULE,
> + .read_raw = &lm3533_als_read_raw,
> +};
> +
> +static int __devinit lm3533_als_probe(struct platform_device *pdev)
> +{
> + struct lm3533 *lm3533;
> + struct lm3533_als_platform_data *pdata;
> + struct lm3533_als *als;
> + struct iio_dev *indio_dev;
> + int ret;
> +
> + dev_dbg(&pdev->dev, "%s\n", __func__);
> +
> + lm3533 = dev_get_drvdata(pdev->dev.parent);
> + if (!lm3533)
> + return -EINVAL;
> +
> + pdata = pdev->dev.platform_data;
> + if (!pdata) {
> + dev_err(&pdev->dev, "no platform data\n");
> + return -EINVAL;
> + }
> +
> + indio_dev = iio_device_alloc(sizeof(*als));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + indio_dev->info = &lm3533_als_info;
> + indio_dev->channels = lm3533_als_channels;
> + indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
> + indio_dev->name = "lm3533-als";
> + indio_dev->dev.parent = pdev->dev.parent;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> +
> + als = iio_priv(indio_dev);
> + als->lm3533 = lm3533;
> + als->irq = lm3533->irq;
> + als->pwm_mode = pdata->pwm_mode;
> + atomic_set(&als->zone, 0);
> + mutex_init(&als->thresh_mutex);
> +
> + if (als->irq) {
> + ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
> + IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> + indio_dev->name, indio_dev);
> + if (ret) {
> + dev_err(&pdev->dev, "failed to request irq %d\n",
> + als->irq);
> + goto err_free_dev;
> + }
> + }
> +
> + platform_set_drvdata(pdev, indio_dev);
> +
amongst other things this register does the creation of the
sysfs attributes. Is there a race here if a read on one of
those occurs before the setup stuff below?
Normally I'd expect this call to the last one in the probe
function. Why did you chose this ordering?
> + ret = iio_device_register(indio_dev);
> + if (ret) {
> + dev_err(&pdev->dev, "failed to register ALS\n");
> + goto err_free_irq;
> + }
> +
> + ret = lm3533_als_setup(indio_dev, pdata);
> + if (ret)
> + goto err_unregister;
> +
> + ret = lm3533_als_enable(indio_dev);
> + if (ret)
> + goto err_unregister;
> +
> + return 0;
> +
> +err_unregister:
> + iio_device_unregister(indio_dev);
> +err_free_irq:
> + if (als->irq)
> + free_irq(als->irq, indio_dev);
> +err_free_dev:
> + iio_device_free(indio_dev);
> +
> + return ret;
> +}
> +
> +static int __devexit lm3533_als_remove(struct platform_device *pdev)
> +{
> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> + struct lm3533_als *als = iio_priv(indio_dev);
> +
> + dev_dbg(&pdev->dev, "%s\n", __func__);
> +
> + lm3533_als_disable(indio_dev);
> + iio_device_unregister(indio_dev);
> + if (als->irq)
> + free_irq(als->irq, indio_dev);
> + iio_device_free(indio_dev);
> +
> + return 0;
> +}
> +
> +static struct platform_driver lm3533_als_driver = {
> + .driver = {
> + .name = "lm3533-als",
> + .owner = THIS_MODULE,
> + },
> + .probe = lm3533_als_probe,
> + .remove = __devexit_p(lm3533_als_remove),
> +};
> +module_platform_driver(lm3533_als_driver);
> +
> +MODULE_AUTHOR("Johan Hovold <[email protected]>");
> +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:lm3533-als");
> diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
> index 9660feb..594bc59 100644
> --- a/include/linux/mfd/lm3533.h
> +++ b/include/linux/mfd/lm3533.h
> @@ -43,6 +43,7 @@ struct lm3533_ctrlbank {
>
> struct lm3533_als_platform_data {
> unsigned pwm_mode:1; /* PWM input mode (default analog) */
> + u8 r_select; /* 1 - 127 (ignored in PWM-mode) */
> };
>
> struct lm3533_bl_platform_data {
On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
> On 05/18/2012 02:07 PM, Johan Hovold wrote:
> > Add sub-driver for the ambient-light-sensor interface on National
> > Semiconductor / TI LM3533 lighting power chips.
> >
> > The sensor interface can be used to control the LEDs and backlights of
> > the chip through defining five light zones and three sets of
> > corresponding brightness target levels.
> >
> > The driver provides raw and mean adc readings along with the current
> > light zone through sysfs. A threshold event can be generated on zone
> > changes.
>
> Hi Johan,
>
> I hate to be a pain with this one, but it's a complex beast and I'd
> really like to get the interface right first time - particularly as
> you are going in after the move out of staging.
>
>
> Queries for you.
> 1) Ordering in the probe function. Normally expect iio_device_register
> to be the last call. Why not here?
> 2) Worth combining enable / disable into one as very similar functions?
> 3) Suspicious code in als_set_input_mode
Good points. See my answers inline below.
> Naming of the target values. I think we can make the naming of these
> fit in much better with the normal abi which is going to be all for the
> good in the long run. They are basically current output channels
> with a controllable set of steps (where we don't have direct control
> of which one we are in). This is very similar to the frequency controls
> on some of Analog's dds that we have a well defined interface for.
>
> More detail below, but in summary.
>
> out_currentX_currentY_raw for channel X value for zone Y.
Interesting. I'll have to think this through and get back to you.
Thanks for looking at this so quickly!
Johan
> Jonathan
> >
> > Signed-off-by: Johan Hovold <[email protected]>
> > ---
> >
> > Note that addition of r_select to the platform data probably needs to go
> > in via mfd.
> >
> >
> > v2:
> > - reimplement using iio
> > - add sysfs-ABI documentation
> > v3
> > - use indexed channel
> > - fix sysfs-ABI documentation typo and style
> > - replace gain attribute with in_illuminance0_calibscale
> > - add calibscale to platform data
> > - fix adc register definitions
> > - replace to_lm3533_dev_attr macro with inline function
> > - fix device used for error reporting at irq allocation
> > - use iio device for error reporting during setup/enable
> > - rebase on staging-next
> > - fix header include paths
> > - use dev_to_iio_dev
> > - add IIO_CHAN_INFO_RAW to info mask
> > - use iio_device_{alloc,free}
> > v4
> > - move to driver/iio/light
> > - add events/in_illuminance0_threshY_hysteresis attributes
> > - fix device zone-boundary quirkiness
> > - clean up attribute handling
> > - replace calibscale with device-specific r_select attribute
> >
> >
> > .../ABI/testing/sysfs-bus-iio-light-lm3533-als | 64 ++
> > drivers/iio/Kconfig | 1 +
> > drivers/iio/Makefile | 1 +
> > drivers/iio/light/Kconfig | 22 +
> > drivers/iio/light/Makefile | 5 +
> > drivers/iio/light/lm3533-als.c | 941 ++++++++++++++++++++
> > include/linux/mfd/lm3533.h | 1 +
> > 7 files changed, 1035 insertions(+), 0 deletions(-)
> > create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > create mode 100644 drivers/iio/light/Kconfig
> > create mode 100644 drivers/iio/light/Makefile
> > create mode 100644 drivers/iio/light/lm3533-als.c
> >
> > diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > new file mode 100644
> > index 0000000..7ea1770
> > --- /dev/null
> > +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > @@ -0,0 +1,64 @@
> > +What: /sys/bus/iio/devices/iio:deviceX/r_select
> > +Date: April 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold <[email protected]>
> > +Description:
> > + Set the ALS internal pull-down resistor for analog input mode
> > + (1..127), such that,
> > +
> > + R_als = 200000 / r_select (ohm)
> > +
> > + This setting is ignored in PWM-mode (input is always high
> > + impedance in PWM-mode).
> > +
> > +What: /sys/.../events/in_illuminance0_thresh_either_en
> > +Date: April 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold <[email protected]>
> > +Description:
> > + Event generated when channel passes one of the four thresholds
> > + in each direction (rising|falling) and a zone change occurs.
> > + The corresponding light zone can be read from
> > + in_illuminance0_zone.
> > +
> > +What: /sys/.../events/in_illuminance0_threshY_hysteresis
> > +Date: May 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold <[email protected]>
> > +Description:
> > + Get the hysteresis for thresholds Y, that is,
> > +
> > + threshY_hysteresis = threshY_raising - threshY_falling
> > +
> > +What: /sys/.../events/illuminance_threshY_falling_value
> > +What: /sys/.../events/illuminance_threshY_raising_value
> > +Date: April 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold <[email protected]>
> > +Description:
> > + Specifies the value of threshold that the device is
> > + comparing against for the events enabled by
> > + in_illuminance0_thresh_either_en, where Y in 0..3.
> > +
> > + Note that threshY_falling must be less than or equal to
> > + threshY_raising.
> > +
> > + These thresholds correspond to the eight zone-boundary
> > + registers (boundaryY_{low,high}) and defines the five light
> > + zones.
> > +
> > +What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> > +Date: April 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold <[email protected]>
> > +Description:
> > + Get the current light zone (0..4) as defined by the
> > + in_illuminance0_threshY_{falling,rising} thresholds.
> > +
> > +What: /sys/bus/iio/devices/iio:deviceX/targetY_Z
> > +Date: April 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold <[email protected]>
> > +Description:
> > + Set the target brightness for ALS-mapper Y in light zone Z
> > + (0..255), where Y in 1..3 and Z in 0..4.
>
> What are the units of this? Also arguably is it not the als that this
> is related to, but rather the light source? A quick datasheet browse says
> that these are current targets? If so I wonder if we can make that
> explicit... Could treat them as 3 output channels and have indexed values
> like we do for frequencies in dds devices (where external hardware is
> controlling them.
>
> Hmm. lets see.
>
> out_currentX_currentY_raw
> (the double naming is a bit clunky but corresponds to frequency devices
> where we have
> out_altvoltageX_frequencyY_raw
>
> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
> as indexed values they can take 0,1,2,3,4
> out_currentX_raw is not read only and gives you the current for whichever
> zone the device is currently in.
>
> This may seem convoluted but I'd really rather have something generalizable
> for this if we possibly can. We'd still need documentation to say what is
> controlling these, but at least they'd fit within our more general abi.
>
> What do you think?
I'll get back to you on this shortly.
[...]
> > +struct lm3533_als {
> > + struct lm3533 *lm3533;
> > +
> > + unsigned long flags;
> > + int irq;
> > +
> Boolean might be better as it's not a though this will save
> space!
I've used bit fields consistently for such flags in lm3533, although the
type below should have been unsigned. The space required is the same
either way in this case. I'll go with bool.
> > + int pwm_mode:1;
> > +
> > + atomic_t zone;
> > + struct mutex thresh_mutex;
> > +};
> Rarely a reason for more than one blank line in my opinion...
Now you're picky. :)
Separating defines, declarations and definitions using an additional
blank line should be pretty uncontroversial. But again, if you insist,
I'll drop it.
> > +
> > +
> May be roll this into it's one call site. will make for marginally
> less code I think..
I did before, but separated it out when I added calibscale. I prefer to
keep it separate for readability reasons (especially if we're going to
add output channels).
> > +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
> > + int *adc)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 reg;
> > + u8 val;
> > + int ret;
> > +
> > + if (average)
> > + reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
> > + else
> > + reg = LM3533_REG_ALS_READ_ADC_RAW;
> > +
> > + ret = lm3533_read(als->lm3533, reg, &val);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to read adc\n");
> > + return ret;
> > + }
> > +
> > + *adc = val;
> > +
> > + return 0;
> > +}
> > +
> > +static int lm3533_als_read_raw(struct iio_dev *indio_dev,
> > + struct iio_chan_spec const *chan,
> > + int *val, int *val2, long mask)
> > +{
> > + int ret;
> > +
> > + switch (mask) {
> > + case 0:
> > + ret = lm3533_als_get_adc(indio_dev, false, val);
> > + break;
> > + case IIO_CHAN_INFO_AVERAGE_RAW:
> > + ret = lm3533_als_get_adc(indio_dev, true, val);
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + if (ret)
> > + return ret;
> > +
> > + return IIO_VAL_INT;
> > +}
> > +
> Why have an array? Just use the address and set the num_channels = 1;
For consistency. If you ever add channels you'd need to turn it into an
array again anyway (and we are considering output channels now).
This is a pretty common practise for example in board files.
> > +static const struct iio_chan_spec lm3533_als_channels[] = {
> > + {
> > + .type = IIO_LIGHT,
> > + .channel = 0,
> > + .indexed = 1,
> > + .info_mask = (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
> > + IIO_CHAN_INFO_RAW_SEPARATE_BIT),
> > + }
> > +};
> > +
> > +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 val;
> > + int ret;
> > +
> > + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to read zone\n");
> > + return ret;
> > + }
> > +
> > + val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
> > + *zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
> > +
> > + return 0;
> > +}
> > +
> > +static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
> > +{
> > +
> > + struct iio_dev *indio_dev = dev_id;
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 zone;
> > + int ret;
> > +
> > + /* Clear interrupt by reading the ALS zone register. */
> > + ret = lm3533_als_get_zone(indio_dev, &zone);
> > + if (ret)
> > + goto out;
> > +
> > + atomic_set(&als->zone, zone);
> > +
> > + iio_push_event(indio_dev,
> > + IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
> > + 0,
> > + IIO_EV_TYPE_THRESH,
> > + IIO_EV_DIR_EITHER),
> > + iio_get_time_ns());
> > +out:
> > + return IRQ_HANDLED;
> > +}
> > +
> could just roll this into the one call point, it's not exactly complex.
For readability and consistency reasons I'd like to keep it separate. If
you look at the call point in store_thresh_either_en it really makes
sense to break this one out. The consistency argument is that most
register accesses have their dedicated set/get functions.
[ The reordering of probe / remove discussed below will also introduce
a second call point. ]
> > +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> > + u8 val;
> > + int ret;
> > +
> > + if (enable)
> > + val = mask;
> > + else
> > + val = 0;
> > +
> > + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to set int mode %d\n",
> > + enable);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> > + u8 val;
> > + int ret;
> > +
> > + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to get int mode\n");
> > + return ret;
> > + }
> > +
> > + *enable = !!(val & mask);
> > +
> > + return 0;
> > +}
> > +
> Given only accessed from one place, why not just roll it in there?
I've dropped this one completely along with the r_select sysfs attribute
as it is now write only (from platform data).
> > +static int lm3533_als_get_resistor(struct iio_dev *indio_dev, u8 *val)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + int ret;
> > +
> > + if (als->pwm_mode) {
> > + *val = 0; /* always high impedance */
> > + return 0;
> > + }
> > +
> > + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to get resistor\n");
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lm3533_als_set_resistor(struct iio_dev *indio_dev, u8 val)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + int ret;
> > +
> > + if (als->pwm_mode)
> > + return -EPERM; /* always high impedance */
> > +
> > + if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX)
> > + return -EINVAL;
> > +
> > + ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to set resistor\n");
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
[...]
> > +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned nr,
> > + unsigned zone, u8 val)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 reg;
> > + int ret;
> > +
> > + if (nr < LM3533_ALS_MAPPER_MIN || nr > LM3533_ALS_MAPPER_MAX)
> > + return -EINVAL;
> > +
> > + if (zone > LM3533_ALS_ZONE_MAX)
> > + return -EINVAL;
> > +
> > + reg = lm3533_als_get_target_reg(nr, zone);
> > + ret = lm3533_write(als->lm3533, reg, val);
> I wouldn't bother with the intermediate but up to you...
>
> ret = lm3533_write(als->lm3533, lm3533_als_get_target_reg(nr, zone), val);
I prefer the expanded form.
> > + if (ret)
> > + dev_err(&indio_dev->dev, "failed to set target brightness\n");
> > +
> > + return ret;
> > +}
> > +
> > +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
> > +{
> > + u8 offset;
> > +
> offset = !raising;
Ok.
> > + if (raising)
> > + offset = 0;
> > + else
> > + offset = 1;
> > +
> > + return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
> > +}
> > +
> > +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
> > + bool raising, u8 *val)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 reg;
> > + int ret;
> > +
> > + if (nr > LM3533_ALS_THRESH_MAX)
> > + return -EINVAL;
> > +
> > + reg = lm3533_als_get_threshold_reg(nr, raising);
> > + ret = lm3533_read(als->lm3533, reg, val);
> > + if (ret)
> > + dev_err(&indio_dev->dev, "failed to get threshold\n");
> > +
> > + return ret;
> > +}
> > +
> > +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
> > + bool raising, u8 val)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 val2;
> > + u8 reg;
> > + u8 reg2;
> u8 val2, reg, reg2; Shorter and still obvious.
I try to avoid doing multiple declarations on a single line, but in this
case, perhaps
u8 val2;
u8 reg, reg2;
would be consistent with the rest of the code.
> > + int ret;
> > +
> > + if (nr > LM3533_ALS_THRESH_MAX)
> > + return -EINVAL;
> > +
> > + reg = lm3533_als_get_threshold_reg(nr, raising);
> > + reg2 = lm3533_als_get_threshold_reg(nr, !raising);
> > +
> > + mutex_lock(&als->thresh_mutex);
> > + ret = lm3533_read(als->lm3533, reg2, &val2);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to get threshold\n");
> > + goto out;
> > + }
> > + /*
> > + * This device does not allow negative hysteresis (in fact, it uses
> > + * whichever value is smaller as the lower bound) so we need to make
> > + * sure that thresh_falling <= thresh_raising.
> > + */
> > + if ((raising && (val < val2)) || (!raising && (val > val2))) {
> > + ret = -EINVAL;
> > + goto out;
> > + }
> > +
> > + ret = lm3533_write(als->lm3533, reg, val);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to set threshold\n");
> > + goto out;
> > + }
> > +out:
> > + mutex_unlock(&als->thresh_mutex);
> > +
> > + return ret;
> > +}
[...]
> > +static ssize_t store_als_attr(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t len)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> > + u8 val;
> > + int ret;
> > +
> > + if (kstrtou8(buf, 0, &val))
> > + return -EINVAL;
> > +
> > + switch (als_attr->type) {
> > + case LM3533_ATTR_TYPE_TARGET:
> > + ret = lm3533_als_set_target(indio_dev, als_attr->val1,
> > + als_attr->val2, val);
> > + break;
> > + case LM3533_ATTR_TYPE_THRESH_FALLING:
> > + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> > + false, val);
> > + break;
> > + case LM3533_ATTR_TYPE_THRESH_RAISING:
> > + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> > + true, val);
> > + break;
> > + default:
> I'd be tempted to drop this. It is easy to verify whether it will occur.
Drop the WARN and simply return -ENXIO, you mean?
> > + WARN(1, "%s - bad attribute type %d\n", __func__,
> > + als_attr->type);
> > + ret = -ENXIO;
> > + }
> > +
> > + if (ret)
> > + return ret;
> > +
> > + return len;
> > +}
[...]
> > +static struct attribute *lm3533_als_event_attributes[] = {
> > + &dev_attr_in_illuminance0_thresh_either_en.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
> Just to verify the hysteresis applies to bother thresh0_falling and
> thresh0_rising?
Yes, we have the following situation (just to reiterate):
...
zone 1
thresh0_raising 52
thresh0_falling 50
zone 0
If the ALS is in zone 0 and the (8-bit) average input raises above
thresh0_raising (52) it enters zone 1. The ALS will not re-enter zone 0 until
the input drops below thresh0_falling (50). The hysteresis is the
difference between the two thresholds, that is, 52 - 50 = 2 in this
case.
> > + &lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
> > + NULL
> > +};
> > +
> > +static struct attribute_group lm3533_als_event_attribute_group = {
> > + .attrs = lm3533_als_event_attributes
> > +};
> > +
> > +static struct attribute *lm3533_als_attributes[] = {
> > + &dev_attr_r_select.attr,
> Just to keep info in one place. We agreed in other branch
> of thread that r_select would be done with platform data.
Indeed. I've dropped it from sysfs.
> I wonder if we can make the naming a little clearer for these.. hmm.
> > + &lm3533_als_attr_target1_0.dev_attr.attr,
> > + &lm3533_als_attr_target1_1.dev_attr.attr,
> > + &lm3533_als_attr_target1_2.dev_attr.attr,
> > + &lm3533_als_attr_target1_3.dev_attr.attr,
> > + &lm3533_als_attr_target1_4.dev_attr.attr,
> > + &lm3533_als_attr_target2_0.dev_attr.attr,
> > + &lm3533_als_attr_target2_1.dev_attr.attr,
> > + &lm3533_als_attr_target2_2.dev_attr.attr,
> > + &lm3533_als_attr_target2_3.dev_attr.attr,
> > + &lm3533_als_attr_target2_4.dev_attr.attr,
> > + &lm3533_als_attr_target3_0.dev_attr.attr,
> > + &lm3533_als_attr_target3_1.dev_attr.attr,
> > + &lm3533_als_attr_target3_2.dev_attr.attr,
> > + &lm3533_als_attr_target3_3.dev_attr.attr,
> > + &lm3533_als_attr_target3_4.dev_attr.attr,
> > + &dev_attr_in_illuminance0_zone.attr,
> > + NULL
> > +};
> > +
> > +static struct attribute_group lm3533_als_attribute_group = {
> > + .attrs = lm3533_als_attributes
> > +};
> > +
> > +static int __devinit lm3533_als_set_input_mode(struct iio_dev *indio_dev,
> > + int pwm_mode)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 mask = LM3533_ALS_INPUT_MODE_MASK;
> > + u8 val;
> Just use mask directly, why introduce val as well
> (particularly as you don't use it ;)
> > + int ret;
> > +
> > + if (pwm_mode)
> > + val = mask; /* pwm input */
> > + else
> > + val = 0; /* analog input */
> > +
> Why have val?
> > + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
This is supposed to read lm3533_update(..., val, mask). Thanks for
catching this.
> > + if (ret) {
> > + dev_err(&indio_dev->dev,
> > + "failed to set input mode %d\n", pwm_mode);
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static int __devinit lm3533_als_setup(struct iio_dev *indio_dev,
> > + struct lm3533_als_platform_data *pdata)
> > +{
> > + int ret;
> > +
> > + ret = lm3533_als_set_input_mode(indio_dev, pdata->pwm_mode);
> > + if (ret)
> > + return ret;
> > +
> > + /* ALS input is always high impedance in PWM-mode. */
> > + if (!pdata->pwm_mode) {
> > + ret = lm3533_als_set_resistor(indio_dev, pdata->r_select);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> This enable / disable pair does rather look like it could
> be combined into one function and save a few lines of repeated
> code
They could but at the expense of some readability (als_enable(...,
false) rather than als_disable(...)) and really not much gain in terms
of fewer line of code as you'd need to add conditionals both for the
register value and error message.
This is the style I've used in the other drivers so if you don't mind
terribly, I'd suggest not merging them if only for consistency.
> > +static int __devinit lm3533_als_enable(struct iio_dev *indio_dev)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 mask = LM3533_ALS_ENABLE_MASK;
> > + int ret;
> > +
> > + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
> > + if (ret)
> > + dev_err(&indio_dev->dev, "failed to enable ALS\n");
> > +
> > + return ret;
> > +}
> > +
> > +static int __devexit lm3533_als_disable(struct iio_dev *indio_dev)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 mask = LM3533_ALS_ENABLE_MASK;
> > + int ret;
> > +
> > + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
> > + if (ret)
> > + dev_err(&indio_dev->dev, "failed to disable ALS\n");
> > +
> > + return ret;
> > +}
> > +
> > +static const struct iio_info lm3533_als_info = {
> > + .attrs = &lm3533_als_attribute_group,
> > + .event_attrs = &lm3533_als_event_attribute_group,
> > + .driver_module = THIS_MODULE,
> > + .read_raw = &lm3533_als_read_raw,
> > +};
> > +
> > +static int __devinit lm3533_als_probe(struct platform_device *pdev)
> > +{
> > + struct lm3533 *lm3533;
> > + struct lm3533_als_platform_data *pdata;
> > + struct lm3533_als *als;
> > + struct iio_dev *indio_dev;
> > + int ret;
> > +
> > + dev_dbg(&pdev->dev, "%s\n", __func__);
> > +
> > + lm3533 = dev_get_drvdata(pdev->dev.parent);
> > + if (!lm3533)
> > + return -EINVAL;
> > +
> > + pdata = pdev->dev.platform_data;
> > + if (!pdata) {
> > + dev_err(&pdev->dev, "no platform data\n");
> > + return -EINVAL;
> > + }
> > +
> > + indio_dev = iio_device_alloc(sizeof(*als));
> > + if (!indio_dev)
> > + return -ENOMEM;
> > +
> > + indio_dev->info = &lm3533_als_info;
> > + indio_dev->channels = lm3533_als_channels;
> > + indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
> > + indio_dev->name = "lm3533-als";
> > + indio_dev->dev.parent = pdev->dev.parent;
> > + indio_dev->modes = INDIO_DIRECT_MODE;
> > +
> > + als = iio_priv(indio_dev);
> > + als->lm3533 = lm3533;
> > + als->irq = lm3533->irq;
> > + als->pwm_mode = pdata->pwm_mode;
> > + atomic_set(&als->zone, 0);
> > + mutex_init(&als->thresh_mutex);
> > +
> > + if (als->irq) {
> > + ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
> > + IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> > + indio_dev->name, indio_dev);
> > + if (ret) {
> > + dev_err(&pdev->dev, "failed to request irq %d\n",
> > + als->irq);
> > + goto err_free_dev;
> > + }
> > + }
> > +
> > + platform_set_drvdata(pdev, indio_dev);
> > +
> amongst other things this register does the creation of the
> sysfs attributes. Is there a race here if a read on one of
> those occurs before the setup stuff below?
The enable call simply enables the adc so, for example, target or
threshold values could be updated before. Worst case is that you get a
zero reading from the adc before the adc is enabled. The zone algorithm
takes some time to settle either way.
> Normally I'd expect this call to the last one in the probe
> function. Why did you chose this ordering?
The main reason in this case was to be able to use the iio device for
error reporting. The setup function called set_resistor which was also
possible to set from sysfs (and for consistency all error reporting
after probe is done using the iio device).
But since we've now dropped the r_select attribute, I could use the
platform device for reporting all errors during setup and make sure the
iio device is registered last. I'll do this.
> > + ret = iio_device_register(indio_dev);
> > + if (ret) {
> > + dev_err(&pdev->dev, "failed to register ALS\n");
> > + goto err_free_irq;
> > + }
> > +
> > + ret = lm3533_als_setup(indio_dev, pdata);
> > + if (ret)
> > + goto err_unregister;
> > +
> > + ret = lm3533_als_enable(indio_dev);
> > + if (ret)
> > + goto err_unregister;
> > +
> > + return 0;
> > +
> > +err_unregister:
> > + iio_device_unregister(indio_dev);
> > +err_free_irq:
> > + if (als->irq)
> > + free_irq(als->irq, indio_dev);
> > +err_free_dev:
> > + iio_device_free(indio_dev);
> > +
> > + return ret;
> > +}
> > +
> > +static int __devexit lm3533_als_remove(struct platform_device *pdev)
> > +{
> > + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > +
> > + dev_dbg(&pdev->dev, "%s\n", __func__);
> > +
> > + lm3533_als_disable(indio_dev);
> > + iio_device_unregister(indio_dev);
> > + if (als->irq)
> > + free_irq(als->irq, indio_dev);
> > + iio_device_free(indio_dev);
> > +
> > + return 0;
> > +}
> > +
> > +static struct platform_driver lm3533_als_driver = {
> > + .driver = {
> > + .name = "lm3533-als",
> > + .owner = THIS_MODULE,
> > + },
> > + .probe = lm3533_als_probe,
> > + .remove = __devexit_p(lm3533_als_remove),
> > +};
> > +module_platform_driver(lm3533_als_driver);
> > +
> > +MODULE_AUTHOR("Johan Hovold <[email protected]>");
> > +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:lm3533-als");
> > diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
> > index 9660feb..594bc59 100644
> > --- a/include/linux/mfd/lm3533.h
> > +++ b/include/linux/mfd/lm3533.h
> > @@ -43,6 +43,7 @@ struct lm3533_ctrlbank {
> >
> > struct lm3533_als_platform_data {
> > unsigned pwm_mode:1; /* PWM input mode (default analog) */
> > + u8 r_select; /* 1 - 127 (ignored in PWM-mode) */
> > };
> >
> > struct lm3533_bl_platform_data {
>
On 05/19/2012 05:30 PM, Johan Hovold wrote:
> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>> Add sub-driver for the ambient-light-sensor interface on National
>>> Semiconductor / TI LM3533 lighting power chips.
>>>
>>> The sensor interface can be used to control the LEDs and backlights of
>>> the chip through defining five light zones and three sets of
>>> corresponding brightness target levels.
>>>
>>> The driver provides raw and mean adc readings along with the current
>>> light zone through sysfs. A threshold event can be generated on zone
>>> changes.
>>
>> Hi Johan,
>>
>> I hate to be a pain with this one, but it's a complex beast and I'd
>> really like to get the interface right first time - particularly as
>> you are going in after the move out of staging.
>>
>>
>> Queries for you.
>> 1) Ordering in the probe function. Normally expect iio_device_register
>> to be the last call. Why not here?
>> 2) Worth combining enable / disable into one as very similar functions?
>> 3) Suspicious code in als_set_input_mode
>
> Good points. See my answers inline below.
>
>> Naming of the target values. I think we can make the naming of these
>> fit in much better with the normal abi which is going to be all for the
>> good in the long run. They are basically current output channels
>> with a controllable set of steps (where we don't have direct control
>> of which one we are in). This is very similar to the frequency controls
>> on some of Analog's dds that we have a well defined interface for.
>>
>> More detail below, but in summary.
>>
>> out_currentX_currentY_raw for channel X value for zone Y.
>
> Interesting. I'll have to think this through and get back to you.
>
> Thanks for looking at this so quickly!
>
> Johan
>
>> Jonathan
>>>
>>> Signed-off-by: Johan Hovold <[email protected]>
>>> ---
>>>
>>> Note that addition of r_select to the platform data probably needs to go
>>> in via mfd.
>>>
>>>
>>> v2:
>>> - reimplement using iio
>>> - add sysfs-ABI documentation
>>> v3
>>> - use indexed channel
>>> - fix sysfs-ABI documentation typo and style
>>> - replace gain attribute with in_illuminance0_calibscale
>>> - add calibscale to platform data
>>> - fix adc register definitions
>>> - replace to_lm3533_dev_attr macro with inline function
>>> - fix device used for error reporting at irq allocation
>>> - use iio device for error reporting during setup/enable
>>> - rebase on staging-next
>>> - fix header include paths
>>> - use dev_to_iio_dev
>>> - add IIO_CHAN_INFO_RAW to info mask
>>> - use iio_device_{alloc,free}
>>> v4
>>> - move to driver/iio/light
>>> - add events/in_illuminance0_threshY_hysteresis attributes
>>> - fix device zone-boundary quirkiness
>>> - clean up attribute handling
>>> - replace calibscale with device-specific r_select attribute
>>>
>>>
>>> .../ABI/testing/sysfs-bus-iio-light-lm3533-als | 64 ++
>>> drivers/iio/Kconfig | 1 +
>>> drivers/iio/Makefile | 1 +
>>> drivers/iio/light/Kconfig | 22 +
>>> drivers/iio/light/Makefile | 5 +
>>> drivers/iio/light/lm3533-als.c | 941 ++++++++++++++++++++
>>> include/linux/mfd/lm3533.h | 1 +
>>> 7 files changed, 1035 insertions(+), 0 deletions(-)
>>> create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>> create mode 100644 drivers/iio/light/Kconfig
>>> create mode 100644 drivers/iio/light/Makefile
>>> create mode 100644 drivers/iio/light/lm3533-als.c
>>>
>>> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>> new file mode 100644
>>> index 0000000..7ea1770
>>> --- /dev/null
>>> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>> @@ -0,0 +1,64 @@
>>> +What: /sys/bus/iio/devices/iio:deviceX/r_select
>>> +Date: April 2012
>>> +KernelVersion: 3.5
>>> +Contact: Johan Hovold <[email protected]>
>>> +Description:
>>> + Set the ALS internal pull-down resistor for analog input mode
>>> + (1..127), such that,
>>> +
>>> + R_als = 200000 / r_select (ohm)
>>> +
>>> + This setting is ignored in PWM-mode (input is always high
>>> + impedance in PWM-mode).
>>> +
>>> +What: /sys/.../events/in_illuminance0_thresh_either_en
>>> +Date: April 2012
>>> +KernelVersion: 3.5
>>> +Contact: Johan Hovold <[email protected]>
>>> +Description:
>>> + Event generated when channel passes one of the four thresholds
>>> + in each direction (rising|falling) and a zone change occurs.
>>> + The corresponding light zone can be read from
>>> + in_illuminance0_zone.
>>> +
>>> +What: /sys/.../events/in_illuminance0_threshY_hysteresis
>>> +Date: May 2012
>>> +KernelVersion: 3.5
>>> +Contact: Johan Hovold <[email protected]>
>>> +Description:
>>> + Get the hysteresis for thresholds Y, that is,
>>> +
>>> + threshY_hysteresis = threshY_raising - threshY_falling
>>> +
>>> +What: /sys/.../events/illuminance_threshY_falling_value
>>> +What: /sys/.../events/illuminance_threshY_raising_value
>>> +Date: April 2012
>>> +KernelVersion: 3.5
>>> +Contact: Johan Hovold <[email protected]>
>>> +Description:
>>> + Specifies the value of threshold that the device is
>>> + comparing against for the events enabled by
>>> + in_illuminance0_thresh_either_en, where Y in 0..3.
>>> +
>>> + Note that threshY_falling must be less than or equal to
>>> + threshY_raising.
>>> +
>>> + These thresholds correspond to the eight zone-boundary
>>> + registers (boundaryY_{low,high}) and defines the five light
>>> + zones.
>>> +
>>> +What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
>>> +Date: April 2012
>>> +KernelVersion: 3.5
>>> +Contact: Johan Hovold <[email protected]>
>>> +Description:
>>> + Get the current light zone (0..4) as defined by the
>>> + in_illuminance0_threshY_{falling,rising} thresholds.
>>> +
>>> +What: /sys/bus/iio/devices/iio:deviceX/targetY_Z
>>> +Date: April 2012
>>> +KernelVersion: 3.5
>>> +Contact: Johan Hovold <[email protected]>
>>> +Description:
>>> + Set the target brightness for ALS-mapper Y in light zone Z
>>> + (0..255), where Y in 1..3 and Z in 0..4.
>>
>> What are the units of this? Also arguably is it not the als that this
>> is related to, but rather the light source? A quick datasheet browse says
>> that these are current targets? If so I wonder if we can make that
>> explicit... Could treat them as 3 output channels and have indexed values
>> like we do for frequencies in dds devices (where external hardware is
>> controlling them.
>>
>> Hmm. lets see.
>>
>> out_currentX_currentY_raw
>> (the double naming is a bit clunky but corresponds to frequency devices
>> where we have
>> out_altvoltageX_frequencyY_raw
>>
>> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
>> as indexed values they can take 0,1,2,3,4
>> out_currentX_raw is not read only and gives you the current for whichever
>> zone the device is currently in.
>>
>> This may seem convoluted but I'd really rather have something generalizable
>> for this if we possibly can. We'd still need documentation to say what is
>> controlling these, but at least they'd fit within our more general abi.
>>
>> What do you think?
>
> I'll get back to you on this shortly.
>
> [...]
>
>>> +struct lm3533_als {
>>> + struct lm3533 *lm3533;
>>> +
>>> + unsigned long flags;
>>> + int irq;
>>> +
>> Boolean might be better as it's not a though this will save
>> space!
>
> I've used bit fields consistently for such flags in lm3533, although the
> type below should have been unsigned. The space required is the same
> either way in this case. I'll go with bool.
indeed it should be unsigned. It's fine if you want to keep it
as a bitfield for consistency (as good an arguement as any!)
>
>>> + int pwm_mode:1;
>>> +
>>> + atomic_t zone;
>>> + struct mutex thresh_mutex;
>>> +};
>> Rarely a reason for more than one blank line in my opinion...
>
> Now you're picky. :)
>
> Separating defines, declarations and definitions using an additional
> blank line should be pretty uncontroversial. But again, if you insist,
> I'll drop it.
I'm not insisting (hence opinion bit ;)
>
>>> +
>>> +
>> May be roll this into it's one call site. will make for marginally
>> less code I think..
>
> I did before, but separated it out when I added calibscale. I prefer to
> keep it separate for readability reasons (especially if we're going to
> add output channels).
fair enough.
>
>>> +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
>>> + int *adc)
>>> +{
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> + u8 reg;
>>> + u8 val;
>>> + int ret;
>>> +
>>> + if (average)
>>> + reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
>>> + else
>>> + reg = LM3533_REG_ALS_READ_ADC_RAW;
>>> +
>>> + ret = lm3533_read(als->lm3533, reg, &val);
>>> + if (ret) {
>>> + dev_err(&indio_dev->dev, "failed to read adc\n");
>>> + return ret;
>>> + }
>>> +
>>> + *adc = val;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int lm3533_als_read_raw(struct iio_dev *indio_dev,
>>> + struct iio_chan_spec const *chan,
>>> + int *val, int *val2, long mask)
>>> +{
>>> + int ret;
>>> +
>>> + switch (mask) {
>>> + case 0:
>>> + ret = lm3533_als_get_adc(indio_dev, false, val);
>>> + break;
>>> + case IIO_CHAN_INFO_AVERAGE_RAW:
>>> + ret = lm3533_als_get_adc(indio_dev, true, val);
>>> + break;
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> +
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return IIO_VAL_INT;
>>> +}
>>> +
>> Why have an array? Just use the address and set the num_channels = 1;
>
> For consistency. If you ever add channels you'd need to turn it into an
> array again anyway (and we are considering output channels now).
>
> This is a pretty common practise for example in board files.
fair enough
>
>>> +static const struct iio_chan_spec lm3533_als_channels[] = {
>>> + {
>>> + .type = IIO_LIGHT,
>>> + .channel = 0,
>>> + .indexed = 1,
>>> + .info_mask = (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
>>> + IIO_CHAN_INFO_RAW_SEPARATE_BIT),
>>> + }
>>> +};
>>> +
>>> +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
>>> +{
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> + u8 val;
>>> + int ret;
>>> +
>>> + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
>>> + if (ret) {
>>> + dev_err(&indio_dev->dev, "failed to read zone\n");
>>> + return ret;
>>> + }
>>> +
>>> + val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
>>> + *zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
>>> +{
>>> +
>>> + struct iio_dev *indio_dev = dev_id;
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> + u8 zone;
>>> + int ret;
>>> +
>>> + /* Clear interrupt by reading the ALS zone register. */
>>> + ret = lm3533_als_get_zone(indio_dev, &zone);
>>> + if (ret)
>>> + goto out;
>>> +
>>> + atomic_set(&als->zone, zone);
>>> +
>>> + iio_push_event(indio_dev,
>>> + IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
>>> + 0,
>>> + IIO_EV_TYPE_THRESH,
>>> + IIO_EV_DIR_EITHER),
>>> + iio_get_time_ns());
>>> +out:
>>> + return IRQ_HANDLED;
>>> +}
>>> +
>> could just roll this into the one call point, it's not exactly complex.
>
> For readability and consistency reasons I'd like to keep it separate. If
> you look at the call point in store_thresh_either_en it really makes
> sense to break this one out. The consistency argument is that most
> register accesses have their dedicated set/get functions.
>
> [ The reordering of probe / remove discussed below will also introduce
> a second call point. ]
>
>>> +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
>>> +{
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> + u8 mask = LM3533_ALS_INT_ENABLE_MASK;
>>> + u8 val;
>>> + int ret;
>>> +
>>> + if (enable)
>>> + val = mask;
>>> + else
>>> + val = 0;
>>> +
>>> + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
>>> + if (ret) {
>>> + dev_err(&indio_dev->dev, "failed to set int mode %d\n",
>>> + enable);
>>> + return ret;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
>>> +{
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> + u8 mask = LM3533_ALS_INT_ENABLE_MASK;
>>> + u8 val;
>>> + int ret;
>>> +
>>> + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
>>> + if (ret) {
>>> + dev_err(&indio_dev->dev, "failed to get int mode\n");
>>> + return ret;
>>> + }
>>> +
>>> + *enable = !!(val & mask);
>>> +
>>> + return 0;
>>> +}
>>> +
>> Given only accessed from one place, why not just roll it in there?
>
> I've dropped this one completely along with the r_select sysfs attribute
> as it is now write only (from platform data).
>
>>> +static int lm3533_als_get_resistor(struct iio_dev *indio_dev, u8 *val)
>>> +{
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> + int ret;
>>> +
>>> + if (als->pwm_mode) {
>>> + *val = 0; /* always high impedance */
>>> + return 0;
>>> + }
>>> +
>>> + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
>>> + if (ret) {
>>> + dev_err(&indio_dev->dev, "failed to get resistor\n");
>>> + return ret;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int lm3533_als_set_resistor(struct iio_dev *indio_dev, u8 val)
>>> +{
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> + int ret;
>>> +
>>> + if (als->pwm_mode)
>>> + return -EPERM; /* always high impedance */
>>> +
>>> + if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX)
>>> + return -EINVAL;
>>> +
>>> + ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
>>> + if (ret) {
>>> + dev_err(&indio_dev->dev, "failed to set resistor\n");
>>> + return ret;
>>> + }
>>> +
>>> + return 0;
>>> +}
>
> [...]
>
>>> +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned nr,
>>> + unsigned zone, u8 val)
>>> +{
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> + u8 reg;
>>> + int ret;
>>> +
>>> + if (nr < LM3533_ALS_MAPPER_MIN || nr > LM3533_ALS_MAPPER_MAX)
>>> + return -EINVAL;
>>> +
>>> + if (zone > LM3533_ALS_ZONE_MAX)
>>> + return -EINVAL;
>>> +
>>> + reg = lm3533_als_get_target_reg(nr, zone);
>>> + ret = lm3533_write(als->lm3533, reg, val);
>> I wouldn't bother with the intermediate but up to you...
>>
>> ret = lm3533_write(als->lm3533, lm3533_als_get_target_reg(nr, zone), val);
>
> I prefer the expanded form.
>
>>> + if (ret)
>>> + dev_err(&indio_dev->dev, "failed to set target brightness\n");
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
>>> +{
>>> + u8 offset;
>>> +
>> offset = !raising;
>
> Ok.
>
>>> + if (raising)
>>> + offset = 0;
>>> + else
>>> + offset = 1;
>>> +
>>> + return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
>>> +}
>>> +
>>> +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
>>> + bool raising, u8 *val)
>>> +{
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> + u8 reg;
>>> + int ret;
>>> +
>>> + if (nr > LM3533_ALS_THRESH_MAX)
>>> + return -EINVAL;
>>> +
>>> + reg = lm3533_als_get_threshold_reg(nr, raising);
>>> + ret = lm3533_read(als->lm3533, reg, val);
>>> + if (ret)
>>> + dev_err(&indio_dev->dev, "failed to get threshold\n");
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
>>> + bool raising, u8 val)
>>> +{
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> + u8 val2;
>>> + u8 reg;
>>> + u8 reg2;
>> u8 val2, reg, reg2; Shorter and still obvious.
>
> I try to avoid doing multiple declarations on a single line, but in this
> case, perhaps
>
> u8 val2;
> u8 reg, reg2;
>
> would be consistent with the rest of the code.
>
>>> + int ret;
>>> +
>>> + if (nr > LM3533_ALS_THRESH_MAX)
>>> + return -EINVAL;
>>> +
>>> + reg = lm3533_als_get_threshold_reg(nr, raising);
>>> + reg2 = lm3533_als_get_threshold_reg(nr, !raising);
>>> +
>>> + mutex_lock(&als->thresh_mutex);
>>> + ret = lm3533_read(als->lm3533, reg2, &val2);
>>> + if (ret) {
>>> + dev_err(&indio_dev->dev, "failed to get threshold\n");
>>> + goto out;
>>> + }
>>> + /*
>>> + * This device does not allow negative hysteresis (in fact, it uses
>>> + * whichever value is smaller as the lower bound) so we need to make
>>> + * sure that thresh_falling <= thresh_raising.
>>> + */
>>> + if ((raising && (val < val2)) || (!raising && (val > val2))) {
>>> + ret = -EINVAL;
>>> + goto out;
>>> + }
>>> +
>>> + ret = lm3533_write(als->lm3533, reg, val);
>>> + if (ret) {
>>> + dev_err(&indio_dev->dev, "failed to set threshold\n");
>>> + goto out;
>>> + }
>>> +out:
>>> + mutex_unlock(&als->thresh_mutex);
>>> +
>>> + return ret;
>>> +}
>
> [...]
>
>>> +static ssize_t store_als_attr(struct device *dev,
>>> + struct device_attribute *attr,
>>> + const char *buf, size_t len)
>>> +{
>>> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
>>> + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
>>> + u8 val;
>>> + int ret;
>>> +
>>> + if (kstrtou8(buf, 0, &val))
>>> + return -EINVAL;
>>> +
>>> + switch (als_attr->type) {
>>> + case LM3533_ATTR_TYPE_TARGET:
>>> + ret = lm3533_als_set_target(indio_dev, als_attr->val1,
>>> + als_attr->val2, val);
>>> + break;
>>> + case LM3533_ATTR_TYPE_THRESH_FALLING:
>>> + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
>>> + false, val);
>>> + break;
>>> + case LM3533_ATTR_TYPE_THRESH_RAISING:
>>> + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
>>> + true, val);
>>> + break;
>>> + default:
>> I'd be tempted to drop this. It is easy to verify whether it will occur.
>
> Drop the WARN and simply return -ENXIO, you mean?
yup
>
>>> + WARN(1, "%s - bad attribute type %d\n", __func__,
>>> + als_attr->type);
>>> + ret = -ENXIO;
>>> + }
>>> +
>>> + if (ret)
>>> + return ret;
>>> +
>>> + return len;
>>> +}
>
> [...]
>
>>> +static struct attribute *lm3533_als_event_attributes[] = {
>>> + &dev_attr_in_illuminance0_thresh_either_en.attr,
>>> + &lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
>>> + &lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
>> Just to verify the hysteresis applies to bother thresh0_falling and
>> thresh0_rising?
>
> Yes, we have the following situation (just to reiterate):
>
> ...
> zone 1
>
> thresh0_raising 52
>
> thresh0_falling 50
>
> zone 0
>
> If the ALS is in zone 0 and the (8-bit) average input raises above
> thresh0_raising (52) it enters zone 1. The ALS will not re-enter zone 0 until
> the input drops below thresh0_falling (50). The hysteresis is the
> difference between the two thresholds, that is, 52 - 50 = 2 in this
> case.
>
>>> + &lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
>>> + &lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
>>> + &lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
>>> + &lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
>>> + &lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
>>> + &lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
>>> + &lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
>>> + &lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
>>> + &lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
>>> + &lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
>>> + NULL
>>> +};
>>> +
>>> +static struct attribute_group lm3533_als_event_attribute_group = {
>>> + .attrs = lm3533_als_event_attributes
>>> +};
>>> +
>>> +static struct attribute *lm3533_als_attributes[] = {
>>> + &dev_attr_r_select.attr,
>> Just to keep info in one place. We agreed in other branch
>> of thread that r_select would be done with platform data.
>
> Indeed. I've dropped it from sysfs.
>
>> I wonder if we can make the naming a little clearer for these.. hmm.
>>> + &lm3533_als_attr_target1_0.dev_attr.attr,
>>> + &lm3533_als_attr_target1_1.dev_attr.attr,
>>> + &lm3533_als_attr_target1_2.dev_attr.attr,
>>> + &lm3533_als_attr_target1_3.dev_attr.attr,
>>> + &lm3533_als_attr_target1_4.dev_attr.attr,
>>> + &lm3533_als_attr_target2_0.dev_attr.attr,
>>> + &lm3533_als_attr_target2_1.dev_attr.attr,
>>> + &lm3533_als_attr_target2_2.dev_attr.attr,
>>> + &lm3533_als_attr_target2_3.dev_attr.attr,
>>> + &lm3533_als_attr_target2_4.dev_attr.attr,
>>> + &lm3533_als_attr_target3_0.dev_attr.attr,
>>> + &lm3533_als_attr_target3_1.dev_attr.attr,
>>> + &lm3533_als_attr_target3_2.dev_attr.attr,
>>> + &lm3533_als_attr_target3_3.dev_attr.attr,
>>> + &lm3533_als_attr_target3_4.dev_attr.attr,
>>> + &dev_attr_in_illuminance0_zone.attr,
>>> + NULL
>>> +};
>>> +
>>> +static struct attribute_group lm3533_als_attribute_group = {
>>> + .attrs = lm3533_als_attributes
>>> +};
>>> +
>>> +static int __devinit lm3533_als_set_input_mode(struct iio_dev *indio_dev,
>>> + int pwm_mode)
>>> +{
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> + u8 mask = LM3533_ALS_INPUT_MODE_MASK;
>>> + u8 val;
>> Just use mask directly, why introduce val as well
>> (particularly as you don't use it ;)
>>> + int ret;
>>> +
>>> + if (pwm_mode)
>>> + val = mask; /* pwm input */
>>> + else
>>> + val = 0; /* analog input */
>>> +
>> Why have val?
>>> + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
>
> This is supposed to read lm3533_update(..., val, mask). Thanks for
> catching this.
>
>>> + if (ret) {
>>> + dev_err(&indio_dev->dev,
>>> + "failed to set input mode %d\n", pwm_mode);
>>> + }
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int __devinit lm3533_als_setup(struct iio_dev *indio_dev,
>>> + struct lm3533_als_platform_data *pdata)
>>> +{
>>> + int ret;
>>> +
>>> + ret = lm3533_als_set_input_mode(indio_dev, pdata->pwm_mode);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + /* ALS input is always high impedance in PWM-mode. */
>>> + if (!pdata->pwm_mode) {
>>> + ret = lm3533_als_set_resistor(indio_dev, pdata->r_select);
>>> + if (ret)
>>> + return ret;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>> This enable / disable pair does rather look like it could
>> be combined into one function and save a few lines of repeated
>> code
>
> They could but at the expense of some readability (als_enable(...,
> false) rather than als_disable(...)) and really not much gain in terms
> of fewer line of code as you'd need to add conditionals both for the
> register value and error message.
>
> This is the style I've used in the other drivers so if you don't mind
> terribly, I'd suggest not merging them if only for consistency.
I don't really care. Just like to kill off code duplication.
>
>>> +static int __devinit lm3533_als_enable(struct iio_dev *indio_dev)
>>> +{
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> + u8 mask = LM3533_ALS_ENABLE_MASK;
>>> + int ret;
>>> +
>>> + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
>>> + if (ret)
>>> + dev_err(&indio_dev->dev, "failed to enable ALS\n");
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int __devexit lm3533_als_disable(struct iio_dev *indio_dev)
>>> +{
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> + u8 mask = LM3533_ALS_ENABLE_MASK;
>>> + int ret;
>>> +
>>> + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
>>> + if (ret)
>>> + dev_err(&indio_dev->dev, "failed to disable ALS\n");
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static const struct iio_info lm3533_als_info = {
>>> + .attrs = &lm3533_als_attribute_group,
>>> + .event_attrs = &lm3533_als_event_attribute_group,
>>> + .driver_module = THIS_MODULE,
>>> + .read_raw = &lm3533_als_read_raw,
>>> +};
>>> +
>>> +static int __devinit lm3533_als_probe(struct platform_device *pdev)
>>> +{
>>> + struct lm3533 *lm3533;
>>> + struct lm3533_als_platform_data *pdata;
>>> + struct lm3533_als *als;
>>> + struct iio_dev *indio_dev;
>>> + int ret;
>>> +
>>> + dev_dbg(&pdev->dev, "%s\n", __func__);
>>> +
>>> + lm3533 = dev_get_drvdata(pdev->dev.parent);
>>> + if (!lm3533)
>>> + return -EINVAL;
>>> +
>>> + pdata = pdev->dev.platform_data;
>>> + if (!pdata) {
>>> + dev_err(&pdev->dev, "no platform data\n");
>>> + return -EINVAL;
>>> + }
>>> +
>>> + indio_dev = iio_device_alloc(sizeof(*als));
>>> + if (!indio_dev)
>>> + return -ENOMEM;
>>> +
>>> + indio_dev->info = &lm3533_als_info;
>>> + indio_dev->channels = lm3533_als_channels;
>>> + indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
>>> + indio_dev->name = "lm3533-als";
>>> + indio_dev->dev.parent = pdev->dev.parent;
>>> + indio_dev->modes = INDIO_DIRECT_MODE;
>>> +
>>> + als = iio_priv(indio_dev);
>>> + als->lm3533 = lm3533;
>>> + als->irq = lm3533->irq;
>>> + als->pwm_mode = pdata->pwm_mode;
>>> + atomic_set(&als->zone, 0);
>>> + mutex_init(&als->thresh_mutex);
>>> +
>>> + if (als->irq) {
>>> + ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
>>> + IRQF_TRIGGER_LOW | IRQF_ONESHOT,
>>> + indio_dev->name, indio_dev);
>>> + if (ret) {
>>> + dev_err(&pdev->dev, "failed to request irq %d\n",
>>> + als->irq);
>>> + goto err_free_dev;
>>> + }
>>> + }
>>> +
>>> + platform_set_drvdata(pdev, indio_dev);
>>> +
>> amongst other things this register does the creation of the
>> sysfs attributes. Is there a race here if a read on one of
>> those occurs before the setup stuff below?
>
> The enable call simply enables the adc so, for example, target or
> threshold values could be updated before. Worst case is that you get a
> zero reading from the adc before the adc is enabled. The zone algorithm
> takes some time to settle either way.
>
>> Normally I'd expect this call to the last one in the probe
>> function. Why did you chose this ordering?
>
> The main reason in this case was to be able to use the iio device for
> error reporting. The setup function called set_resistor which was also
> possible to set from sysfs (and for consistency all error reporting
> after probe is done using the iio device).
fair enough on the error reporting. This is the one fiddly corner.
Personally I verge in the direction of far fewer error messages
partly because I'm forever trying to unify drivers and it's a lot
easier if you don't have lots of error messages about!
>
> But since we've now dropped the r_select attribute, I could use the
> platform device for reporting all errors during setup and make sure the
> iio device is registered last. I'll do this.
>
>>> + ret = iio_device_register(indio_dev);
>>> + if (ret) {
>>> + dev_err(&pdev->dev, "failed to register ALS\n");
>>> + goto err_free_irq;
>>> + }
>>> +
>>> + ret = lm3533_als_setup(indio_dev, pdata);
>>> + if (ret)
>>> + goto err_unregister;
>>> +
>>> + ret = lm3533_als_enable(indio_dev);
>>> + if (ret)
>>> + goto err_unregister;
>>> +
>>> + return 0;
>>> +
>>> +err_unregister:
>>> + iio_device_unregister(indio_dev);
>>> +err_free_irq:
>>> + if (als->irq)
>>> + free_irq(als->irq, indio_dev);
>>> +err_free_dev:
>>> + iio_device_free(indio_dev);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int __devexit lm3533_als_remove(struct platform_device *pdev)
>>> +{
>>> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>>> + struct lm3533_als *als = iio_priv(indio_dev);
>>> +
>>> + dev_dbg(&pdev->dev, "%s\n", __func__);
>>> +
>>> + lm3533_als_disable(indio_dev);
>>> + iio_device_unregister(indio_dev);
>>> + if (als->irq)
>>> + free_irq(als->irq, indio_dev);
>>> + iio_device_free(indio_dev);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static struct platform_driver lm3533_als_driver = {
>>> + .driver = {
>>> + .name = "lm3533-als",
>>> + .owner = THIS_MODULE,
>>> + },
>>> + .probe = lm3533_als_probe,
>>> + .remove = __devexit_p(lm3533_als_remove),
>>> +};
>>> +module_platform_driver(lm3533_als_driver);
>>> +
>>> +MODULE_AUTHOR("Johan Hovold <[email protected]>");
>>> +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
>>> +MODULE_LICENSE("GPL");
>>> +MODULE_ALIAS("platform:lm3533-als");
>>> diff --git a/include/linux/mfd/lm3533.h b/include/linux/mfd/lm3533.h
>>> index 9660feb..594bc59 100644
>>> --- a/include/linux/mfd/lm3533.h
>>> +++ b/include/linux/mfd/lm3533.h
>>> @@ -43,6 +43,7 @@ struct lm3533_ctrlbank {
>>>
>>> struct lm3533_als_platform_data {
>>> unsigned pwm_mode:1; /* PWM input mode (default analog) */
>>> + u8 r_select; /* 1 - 127 (ignored in PWM-mode) */
>>> };
>>>
>>> struct lm3533_bl_platform_data {
>>
On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
> On 05/18/2012 02:07 PM, Johan Hovold wrote:
> > Add sub-driver for the ambient-light-sensor interface on National
> > Semiconductor / TI LM3533 lighting power chips.
> >
> > The sensor interface can be used to control the LEDs and backlights of
> > the chip through defining five light zones and three sets of
> > corresponding brightness target levels.
> >
> > The driver provides raw and mean adc readings along with the current
> > light zone through sysfs. A threshold event can be generated on zone
> > changes.
>
> Hi Johan,
>
> I hate to be a pain with this one, but it's a complex beast and I'd
> really like to get the interface right first time - particularly as
> you are going in after the move out of staging.
>
>
> Queries for you.
> 1) Ordering in the probe function. Normally expect iio_device_register
> to be the last call. Why not here?
> 2) Worth combining enable / disable into one as very similar functions?
> 3) Suspicious code in als_set_input_mode
>
> Naming of the target values. I think we can make the naming of these
> fit in much better with the normal abi which is going to be all for the
> good in the long run. They are basically current output channels
> with a controllable set of steps (where we don't have direct control
> of which one we are in). This is very similar to the frequency controls
> on some of Analog's dds that we have a well defined interface for.
>
> More detail below, but in summary.
>
> out_currentX_currentY_raw for channel X value for zone Y.
>
> Jonathan
> >
> > Signed-off-by: Johan Hovold <[email protected]>
> > ---
> >
> > Note that addition of r_select to the platform data probably needs to go
> > in via mfd.
> >
> >
> > v2:
> > - reimplement using iio
> > - add sysfs-ABI documentation
> > v3
> > - use indexed channel
> > - fix sysfs-ABI documentation typo and style
> > - replace gain attribute with in_illuminance0_calibscale
> > - add calibscale to platform data
> > - fix adc register definitions
> > - replace to_lm3533_dev_attr macro with inline function
> > - fix device used for error reporting at irq allocation
> > - use iio device for error reporting during setup/enable
> > - rebase on staging-next
> > - fix header include paths
> > - use dev_to_iio_dev
> > - add IIO_CHAN_INFO_RAW to info mask
> > - use iio_device_{alloc,free}
> > v4
> > - move to driver/iio/light
> > - add events/in_illuminance0_threshY_hysteresis attributes
> > - fix device zone-boundary quirkiness
> > - clean up attribute handling
> > - replace calibscale with device-specific r_select attribute
> >
> >
> > .../ABI/testing/sysfs-bus-iio-light-lm3533-als | 64 ++
> > drivers/iio/Kconfig | 1 +
> > drivers/iio/Makefile | 1 +
> > drivers/iio/light/Kconfig | 22 +
> > drivers/iio/light/Makefile | 5 +
> > drivers/iio/light/lm3533-als.c | 941 ++++++++++++++++++++
> > include/linux/mfd/lm3533.h | 1 +
> > 7 files changed, 1035 insertions(+), 0 deletions(-)
> > create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > create mode 100644 drivers/iio/light/Kconfig
> > create mode 100644 drivers/iio/light/Makefile
> > create mode 100644 drivers/iio/light/lm3533-als.c
> >
> > diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > new file mode 100644
> > index 0000000..7ea1770
> > --- /dev/null
> > +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > @@ -0,0 +1,64 @@
> > +What: /sys/bus/iio/devices/iio:deviceX/r_select
> > +Date: April 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold <[email protected]>
> > +Description:
> > + Set the ALS internal pull-down resistor for analog input mode
> > + (1..127), such that,
> > +
> > + R_als = 200000 / r_select (ohm)
> > +
> > + This setting is ignored in PWM-mode (input is always high
> > + impedance in PWM-mode).
> > +
> > +What: /sys/.../events/in_illuminance0_thresh_either_en
> > +Date: April 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold <[email protected]>
> > +Description:
> > + Event generated when channel passes one of the four thresholds
> > + in each direction (rising|falling) and a zone change occurs.
> > + The corresponding light zone can be read from
> > + in_illuminance0_zone.
> > +
> > +What: /sys/.../events/in_illuminance0_threshY_hysteresis
> > +Date: May 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold <[email protected]>
> > +Description:
> > + Get the hysteresis for thresholds Y, that is,
> > +
> > + threshY_hysteresis = threshY_raising - threshY_falling
> > +
> > +What: /sys/.../events/illuminance_threshY_falling_value
> > +What: /sys/.../events/illuminance_threshY_raising_value
> > +Date: April 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold <[email protected]>
> > +Description:
> > + Specifies the value of threshold that the device is
> > + comparing against for the events enabled by
> > + in_illuminance0_thresh_either_en, where Y in 0..3.
> > +
> > + Note that threshY_falling must be less than or equal to
> > + threshY_raising.
> > +
> > + These thresholds correspond to the eight zone-boundary
> > + registers (boundaryY_{low,high}) and defines the five light
> > + zones.
> > +
> > +What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> > +Date: April 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold <[email protected]>
> > +Description:
> > + Get the current light zone (0..4) as defined by the
> > + in_illuminance0_threshY_{falling,rising} thresholds.
> > +
> > +What: /sys/bus/iio/devices/iio:deviceX/targetY_Z
> > +Date: April 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold <[email protected]>
> > +Description:
> > + Set the target brightness for ALS-mapper Y in light zone Z
> > + (0..255), where Y in 1..3 and Z in 0..4.
>
> What are the units of this?
The datasheet reads "percent of the full-scale current" (actually depends
somewhat on whether the als is in linear or exponential mode). When the
leds or backlights are in PWM-mode (not the ALS necessarily), these
values are interpreted as a scale factor which is applied to the output
current determined by the PWM-signal.
Either way it could indeed be considered a raw output current (which
could be manipulated later by various factors).
> Also arguably is it not the als that this is related to, but rather
> the light source?
Well, it would be a raw output, mapping the measured LUX.
> A quick datasheet browse says that these are current targets? If so I
> wonder if we can make that explicit... Could treat them as 3 output
> channels and have indexed values like we do for frequencies in dds
> devices (where external hardware is controlling them.
I think I like this.
> Hmm. lets see.
>
> out_currentX_currentY_raw
> (the double naming is a bit clunky but corresponds to frequency devices
> where we have
> out_altvoltageX_frequencyY_raw
>
> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
> as indexed values they can take 0,1,2,3,4
> out_currentX_raw is not read only and gives you the current for whichever
> zone the device is currently in.
I take it you mean "out_currentX_raw is read only".
> This may seem convoluted but I'd really rather have something generalizable
> for this if we possibly can. We'd still need documentation to say what is
> controlling these, but at least they'd fit within our more general abi.
>
> What do you think?
I like it. From a user perspective it's mainly a change of names (and
indexes). But conceptually it's perhaps more clear: the als maps it's
input to an output current, which, just like a PWM-signal, could be used
as an input to the LEDs and backlights to determine their outputs.
I'd have to modify the LED and backlight interfaces somewhat to reflect
the changed indexes and terminology (e.g. "output channel" rather
than "ALS mapper"). Something like:
als_en -- enable als input mode (0,1)
als_channel -- which out_currentX to use as input (0..2) in
als input mode
So to summarise, we get the following new sysfs-entries for the ALS
(where the first set replace targetX_Y):
out_currentX_currentY_raw r/w, (0..255), X in 0..2, Y in 0..4
out_currentX_raw ro (0..255), X in 0..2
Is there any support in core for the first set or should I simply
rename my target attributes?
Thanks,
Johan
Add sub-driver for the ambient-light-sensor interface on National
Semiconductor / TI LM3533 lighting power chips.
The sensor interface can be used to control the LEDs and backlights of
the chip through defining five light zones and three sets of
corresponding output-current values.
The driver provides raw and mean adc readings along with the current
light zone through sysfs. A threshold event can be generated on zone
changes. The ALS-control output values can be set per zone for the three
current output channels.
Signed-off-by: Johan Hovold <[email protected]>
---
v2:
- reimplement using iio
- add sysfs-ABI documentation
v3
- use indexed channel
- fix sysfs-ABI documentation typo and style
- replace gain attribute with in_illuminance0_calibscale
- add calibscale to platform data
- fix adc register definitions
- replace to_lm3533_dev_attr macro with inline function
- fix device used for error reporting at irq allocation
- use iio device for error reporting during setup/enable
- rebase on staging-next
- fix header include paths
- use dev_to_iio_dev
- add IIO_CHAN_INFO_RAW to info mask
- use iio_device_{alloc,free}
v4
- move to driver/iio/light
- add events/in_illuminance0_threshY_hysteresis attributes
- fix device zone-boundary quirkiness
- clean up attribute handling
- replace calibscale with device-specific r_select attribute
v5
- drop r_select from sysfs ABI
- fix set_input_mode
- minor style changes
- register iio device last at probe
- use dev_name for iio_dev and irq name
- add out_currentY channels
.../ABI/testing/sysfs-bus-iio-light-lm3533-als | 62 ++
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/light/Kconfig | 22 +
drivers/iio/light/Makefile | 5 +
drivers/iio/light/lm3533-als.c | 932 ++++++++++++++++++++
6 files changed, 1023 insertions(+), 0 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
create mode 100644 drivers/iio/light/Kconfig
create mode 100644 drivers/iio/light/Makefile
create mode 100644 drivers/iio/light/lm3533-als.c
diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
new file mode 100644
index 0000000..694a52c
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
@@ -0,0 +1,62 @@
+What: /sys/.../events/in_illuminance0_thresh_either_en
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Event generated when channel passes one of the four thresholds
+ in each direction (rising|falling) and a zone change occurs.
+ The corresponding light zone can be read from
+ in_illuminance0_zone.
+
+What: /sys/.../events/in_illuminance0_threshY_hysteresis
+Date: May 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Get the hysteresis for thresholds Y, that is,
+
+ threshY_hysteresis = threshY_raising - threshY_falling
+
+What: /sys/.../events/illuminance_threshY_falling_value
+What: /sys/.../events/illuminance_threshY_raising_value
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Specifies the value of threshold that the device is comparing
+ against for the events enabled by
+ in_illuminance0_thresh_either_en (0..255), where Y in 0..3.
+
+ Note that threshY_falling must be less than or equal to
+ threshY_raising.
+
+ These thresholds correspond to the eight zone-boundary
+ registers (boundaryY_{low,high}) and defines the five light
+ zones.
+
+What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
+Date: April 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Get the current light zone (0..4) as defined by the
+ in_illuminance0_threshY_{falling,rising} thresholds.
+
+What: /sys/bus/iio/devices/iio:deviceX/out_currentY_raw
+Date: May 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Get output current for channel Y (0..255), that is,
+ out_currentY_currentZ_raw, where Z is the current zone.
+
+What: /sys/bus/iio/devices/iio:deviceX/out_currentY_currentZ_raw
+Date: May 2012
+KernelVersion: 3.5
+Contact: Johan Hovold <[email protected]>
+Description:
+ Set the output current for channel out_currentY when in zone
+ Z (0..255), where Y in 0..2 and Z in 0..4.
+
+ These values correspond to the ALS-mapper target registers for
+ ALS-mapper Y + 1.
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 56eecef..cacc74d 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -50,5 +50,6 @@ config IIO_CONSUMERS_PER_TRIGGER
source "drivers/iio/adc/Kconfig"
source "drivers/iio/amplifiers/Kconfig"
+source "drivers/iio/light/Kconfig"
endif # IIO
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index e425afd..060b674 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o
obj-y += adc/
obj-y += amplifiers/
+obj-y += light/
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
new file mode 100644
index 0000000..db5618e
--- /dev/null
+++ b/drivers/iio/light/Kconfig
@@ -0,0 +1,22 @@
+#
+# Light sensors
+#
+menu "Light sensors"
+
+config SENSORS_LM3533
+ tristate "LM3533 ambient light sensor"
+ depends on MFD_LM3533
+ help
+ If you say yes here you get support for the ambient light sensor
+ interface on National Semiconductor / TI LM3533 Lighting Power
+ chips.
+
+ The sensor interface can be used to control the LEDs and backlights
+ of the chip through defining five light zones and three sets of
+ corresponding output-current values.
+
+ The driver provides raw and mean adc readings along with the current
+ light zone through sysfs. A threshold event can be generated on zone
+ changes. The ALS-control output values can be set per zone for the
+ three current output channels.
+endmenu
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
new file mode 100644
index 0000000..c1c23a0
--- /dev/null
+++ b/drivers/iio/light/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for IIO Light sensors
+#
+
+obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o
diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
new file mode 100644
index 0000000..c3e7bac
--- /dev/null
+++ b/drivers/iio/light/lm3533-als.c
@@ -0,0 +1,932 @@
+/*
+ * lm3533-als.c -- LM3533 Ambient Light Sensor driver
+ *
+ * Copyright (C) 2011-2012 Texas Instruments
+ *
+ * Author: Johan Hovold <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/atomic.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/lm3533.h>
+
+
+#define LM3533_ALS_RESISTOR_MIN 1
+#define LM3533_ALS_RESISTOR_MAX 127
+#define LM3533_ALS_CHANNEL_CURRENT_MAX 2
+#define LM3533_ALS_THRESH_MAX 3
+#define LM3533_ALS_ZONE_MAX 4
+
+#define LM3533_REG_ALS_RESISTOR_SELECT 0x30
+#define LM3533_REG_ALS_CONF 0x31
+#define LM3533_REG_ALS_ZONE_INFO 0x34
+#define LM3533_REG_ALS_READ_ADC_RAW 0x37
+#define LM3533_REG_ALS_READ_ADC_AVERAGE 0x38
+#define LM3533_REG_ALS_BOUNDARY_BASE 0x50
+#define LM3533_REG_ALS_TARGET_BASE 0x60
+
+#define LM3533_ALS_ENABLE_MASK 0x01
+#define LM3533_ALS_INPUT_MODE_MASK 0x02
+#define LM3533_ALS_INT_ENABLE_MASK 0x01
+
+#define LM3533_ALS_ZONE_SHIFT 2
+#define LM3533_ALS_ZONE_MASK 0x1c
+
+#define LM3533_ALS_FLAG_INT_ENABLED 1
+
+
+struct lm3533_als {
+ struct lm3533 *lm3533;
+ struct platform_device *pdev;
+
+ unsigned long flags;
+ int irq;
+
+ atomic_t zone;
+ struct mutex thresh_mutex;
+};
+
+
+static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
+ int *adc)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 reg;
+ u8 val;
+ int ret;
+
+ if (average)
+ reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
+ else
+ reg = LM3533_REG_ALS_READ_ADC_RAW;
+
+ ret = lm3533_read(als->lm3533, reg, &val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to read adc\n");
+ return ret;
+ }
+
+ *adc = val;
+
+ return 0;
+}
+
+static int _lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 val;
+ int ret;
+
+ ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to read zone\n");
+ return ret;
+ }
+
+ val = (val & LM3533_ALS_ZONE_MASK) >> LM3533_ALS_ZONE_SHIFT;
+ *zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
+
+ return 0;
+}
+
+static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ int ret;
+
+ if (test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags)) {
+ *zone = atomic_read(&als->zone);
+ } else {
+ ret = _lm3533_als_get_zone(indio_dev, zone);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * channel output channel 0..2
+ * zone zone 0..4
+ */
+static inline u8 lm3533_als_get_target_reg(unsigned channel, unsigned zone)
+{
+ return LM3533_REG_ALS_TARGET_BASE + 5 * channel + zone;
+}
+
+static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned channel,
+ unsigned zone, u8 *val)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 reg;
+ int ret;
+
+ if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX)
+ return -EINVAL;
+
+ if (zone > LM3533_ALS_ZONE_MAX)
+ return -EINVAL;
+
+ reg = lm3533_als_get_target_reg(channel, zone);
+ ret = lm3533_read(als->lm3533, reg, val);
+ if (ret)
+ dev_err(&indio_dev->dev, "failed to get target current\n");
+
+ return ret;
+}
+
+static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned channel,
+ unsigned zone, u8 val)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 reg;
+ int ret;
+
+ if (channel > LM3533_ALS_CHANNEL_CURRENT_MAX)
+ return -EINVAL;
+
+ if (zone > LM3533_ALS_ZONE_MAX)
+ return -EINVAL;
+
+ reg = lm3533_als_get_target_reg(channel, zone);
+ ret = lm3533_write(als->lm3533, reg, val);
+ if (ret)
+ dev_err(&indio_dev->dev, "failed to set target current\n");
+
+ return ret;
+}
+
+static int lm3533_als_get_current(struct iio_dev *indio_dev, unsigned channel,
+ int *val)
+{
+ u8 zone;
+ u8 target;
+ int ret;
+
+ ret = lm3533_als_get_zone(indio_dev, &zone);
+ if (ret)
+ return ret;
+
+ ret = lm3533_als_get_target(indio_dev, channel, zone, &target);
+ if (ret)
+ return ret;
+
+ *val = target;
+
+ return 0;
+}
+
+static int lm3533_als_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ int ret;
+
+ switch (mask) {
+ case 0:
+ switch (chan->type) {
+ case IIO_LIGHT:
+ ret = lm3533_als_get_adc(indio_dev, false, val);
+ break;
+ case IIO_CURRENT:
+ ret = lm3533_als_get_current(indio_dev, chan->channel,
+ val);
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case IIO_CHAN_INFO_AVERAGE_RAW:
+ ret = lm3533_als_get_adc(indio_dev, true, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret)
+ return ret;
+
+ return IIO_VAL_INT;
+}
+
+#define CHANNEL_CURRENT(_channel) \
+ { \
+ .type = IIO_CURRENT, \
+ .channel = _channel, \
+ .indexed = true, \
+ .output = true, \
+ .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT, \
+ }
+
+static const struct iio_chan_spec lm3533_als_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .channel = 0,
+ .indexed = true,
+ .info_mask = (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
+ IIO_CHAN_INFO_RAW_SEPARATE_BIT),
+ },
+ CHANNEL_CURRENT(0),
+ CHANNEL_CURRENT(1),
+ CHANNEL_CURRENT(2),
+};
+
+static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
+{
+
+ struct iio_dev *indio_dev = dev_id;
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 zone;
+ int ret;
+
+ /* Clear interrupt by reading the ALS zone register. */
+ ret = _lm3533_als_get_zone(indio_dev, &zone);
+ if (ret)
+ goto out;
+
+ atomic_set(&als->zone, zone);
+
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
+ 0,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns());
+out:
+ return IRQ_HANDLED;
+}
+
+static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+ u8 val;
+ int ret;
+
+ if (enable)
+ val = mask;
+ else
+ val = 0;
+
+ ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to set int mode %d\n",
+ enable);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+ u8 val;
+ int ret;
+
+ ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO, &val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to get int mode\n");
+ return ret;
+ }
+
+ *enable = !!(val & mask);
+
+ return 0;
+}
+
+static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
+{
+ u8 offset = !raising;
+
+ return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
+}
+
+static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
+ bool raising, u8 *val)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 reg;
+ int ret;
+
+ if (nr > LM3533_ALS_THRESH_MAX)
+ return -EINVAL;
+
+ reg = lm3533_als_get_threshold_reg(nr, raising);
+ ret = lm3533_read(als->lm3533, reg, val);
+ if (ret)
+ dev_err(&indio_dev->dev, "failed to get threshold\n");
+
+ return ret;
+}
+
+static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
+ bool raising, u8 val)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 val2;
+ u8 reg, reg2;
+ int ret;
+
+ if (nr > LM3533_ALS_THRESH_MAX)
+ return -EINVAL;
+
+ reg = lm3533_als_get_threshold_reg(nr, raising);
+ reg2 = lm3533_als_get_threshold_reg(nr, !raising);
+
+ mutex_lock(&als->thresh_mutex);
+ ret = lm3533_read(als->lm3533, reg2, &val2);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to get threshold\n");
+ goto out;
+ }
+ /*
+ * This device does not allow negative hysteresis (in fact, it uses
+ * whichever value is smaller as the lower bound) so we need to make
+ * sure that thresh_falling <= thresh_raising.
+ */
+ if ((raising && (val < val2)) || (!raising && (val > val2))) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = lm3533_write(als->lm3533, reg, val);
+ if (ret) {
+ dev_err(&indio_dev->dev, "failed to set threshold\n");
+ goto out;
+ }
+out:
+ mutex_unlock(&als->thresh_mutex);
+
+ return ret;
+}
+
+static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr,
+ u8 *val)
+{
+ struct lm3533_als *als = iio_priv(indio_dev);
+ u8 falling;
+ u8 raising;
+ int ret;
+
+ if (nr > LM3533_ALS_THRESH_MAX)
+ return -EINVAL;
+
+ mutex_lock(&als->thresh_mutex);
+ ret = lm3533_als_get_threshold(indio_dev, nr, false, &falling);
+ if (ret)
+ goto out;
+ ret = lm3533_als_get_threshold(indio_dev, nr, true, &raising);
+ if (ret)
+ goto out;
+
+ *val = raising - falling;
+out:
+ mutex_unlock(&als->thresh_mutex);
+
+ return ret;
+}
+
+static int show_thresh_either_en(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als *als = iio_priv(indio_dev);
+ int enable;
+ int ret;
+
+ if (als->irq) {
+ ret = lm3533_als_get_int_mode(indio_dev, &enable);
+ if (ret)
+ return ret;
+ } else {
+ enable = 0;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
+}
+
+static int store_thresh_either_en(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als *als = iio_priv(indio_dev);
+ unsigned long enable;
+ bool int_enabled;
+ u8 zone;
+ int ret;
+
+ if (!als->irq)
+ return -EBUSY;
+
+ if (kstrtoul(buf, 0, &enable))
+ return -EINVAL;
+
+ int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+ if (enable && !int_enabled) {
+ ret = lm3533_als_get_zone(indio_dev, &zone);
+ if (ret)
+ return ret;
+
+ atomic_set(&als->zone, zone);
+
+ set_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+ }
+
+ ret = lm3533_als_set_int_mode(indio_dev, enable);
+ if (ret) {
+ if (!int_enabled)
+ clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+ return ret;
+ }
+
+ if (!enable)
+ clear_bit(LM3533_ALS_FLAG_INT_ENABLED, &als->flags);
+
+ return len;
+}
+
+static ssize_t show_zone(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ u8 zone;
+ int ret;
+
+ ret = lm3533_als_get_zone(indio_dev, &zone);
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
+}
+
+enum lm3533_als_attribute_type {
+ LM3533_ATTR_TYPE_HYSTERESIS,
+ LM3533_ATTR_TYPE_TARGET,
+ LM3533_ATTR_TYPE_THRESH_FALLING,
+ LM3533_ATTR_TYPE_THRESH_RAISING,
+};
+
+struct lm3533_als_attribute {
+ struct device_attribute dev_attr;
+ enum lm3533_als_attribute_type type;
+ u8 val1;
+ u8 val2;
+};
+
+static inline struct lm3533_als_attribute *
+to_lm3533_als_attr(struct device_attribute *attr)
+{
+ return container_of(attr, struct lm3533_als_attribute, dev_attr);
+}
+
+static ssize_t show_als_attr(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
+ u8 val;
+ int ret;
+
+ switch (als_attr->type) {
+ case LM3533_ATTR_TYPE_HYSTERESIS:
+ ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1,
+ &val);
+ break;
+ case LM3533_ATTR_TYPE_TARGET:
+ ret = lm3533_als_get_target(indio_dev, als_attr->val1,
+ als_attr->val2, &val);
+ break;
+ case LM3533_ATTR_TYPE_THRESH_FALLING:
+ ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
+ false, &val);
+ break;
+ case LM3533_ATTR_TYPE_THRESH_RAISING:
+ ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
+ true, &val);
+ break;
+ default:
+ ret = -ENXIO;
+ }
+
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_als_attr(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
+ u8 val;
+ int ret;
+
+ if (kstrtou8(buf, 0, &val))
+ return -EINVAL;
+
+ switch (als_attr->type) {
+ case LM3533_ATTR_TYPE_TARGET:
+ ret = lm3533_als_set_target(indio_dev, als_attr->val1,
+ als_attr->val2, val);
+ break;
+ case LM3533_ATTR_TYPE_THRESH_FALLING:
+ ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
+ false, val);
+ break;
+ case LM3533_ATTR_TYPE_THRESH_RAISING:
+ ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
+ true, val);
+ break;
+ default:
+ ret = -ENXIO;
+ }
+
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
+ { .dev_attr = __ATTR(_name, _mode, _show, _store), \
+ .type = _type, \
+ .val1 = _val1, \
+ .val2 = _val2 }
+
+#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
+ struct lm3533_als_attribute lm3533_als_attr_##_name = \
+ ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)
+
+#define ALS_TARGET_ATTR_RW(_channel, _zone) \
+ LM3533_ALS_ATTR(out_current##_channel##_current##_zone##_raw, \
+ S_IRUGO | S_IWUSR, \
+ show_als_attr, store_als_attr, \
+ LM3533_ATTR_TYPE_TARGET, _channel, _zone)
+/*
+ * ALS output current values (ALS mapper targets)
+ *
+ * out_current[0-2]_current[0-4]_raw 0-255
+ */
+static ALS_TARGET_ATTR_RW(0, 0);
+static ALS_TARGET_ATTR_RW(0, 1);
+static ALS_TARGET_ATTR_RW(0, 2);
+static ALS_TARGET_ATTR_RW(0, 3);
+static ALS_TARGET_ATTR_RW(0, 4);
+
+static ALS_TARGET_ATTR_RW(1, 0);
+static ALS_TARGET_ATTR_RW(1, 1);
+static ALS_TARGET_ATTR_RW(1, 2);
+static ALS_TARGET_ATTR_RW(1, 3);
+static ALS_TARGET_ATTR_RW(1, 4);
+
+static ALS_TARGET_ATTR_RW(2, 0);
+static ALS_TARGET_ATTR_RW(2, 1);
+static ALS_TARGET_ATTR_RW(2, 2);
+static ALS_TARGET_ATTR_RW(2, 3);
+static ALS_TARGET_ATTR_RW(2, 4);
+
+#define ALS_THRESH_FALLING_ATTR_RW(_nr) \
+ LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value, \
+ S_IRUGO | S_IWUSR, \
+ show_als_attr, store_als_attr, \
+ LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0)
+
+#define ALS_THRESH_RAISING_ATTR_RW(_nr) \
+ LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value, \
+ S_IRUGO | S_IWUSR, \
+ show_als_attr, store_als_attr, \
+ LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0)
+/*
+ * ALS Zone thresholds (boundaries)
+ *
+ * in_illuminance0_thresh[0-3]_falling_value 0-255
+ * in_illuminance0_thresh[0-3]_raising_value 0-255
+ */
+static ALS_THRESH_FALLING_ATTR_RW(0);
+static ALS_THRESH_FALLING_ATTR_RW(1);
+static ALS_THRESH_FALLING_ATTR_RW(2);
+static ALS_THRESH_FALLING_ATTR_RW(3);
+
+static ALS_THRESH_RAISING_ATTR_RW(0);
+static ALS_THRESH_RAISING_ATTR_RW(1);
+static ALS_THRESH_RAISING_ATTR_RW(2);
+static ALS_THRESH_RAISING_ATTR_RW(3);
+
+#define ALS_HYSTERESIS_ATTR_RO(_nr) \
+ LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis, \
+ S_IRUGO, show_als_attr, NULL, \
+ LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0)
+/*
+ * ALS Zone threshold hysteresis
+ *
+ * threshY_hysteresis = threshY_raising - threshY_falling
+ *
+ * in_illuminance0_thresh[0-3]_hysteresis 0-255
+ * in_illuminance0_thresh[0-3]_hysteresis 0-255
+ */
+static ALS_HYSTERESIS_ATTR_RO(0);
+static ALS_HYSTERESIS_ATTR_RO(1);
+static ALS_HYSTERESIS_ATTR_RO(2);
+static ALS_HYSTERESIS_ATTR_RO(3);
+
+#define ILLUMINANCE_ATTR_RO(_name) \
+ DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
+#define ILLUMINANCE_ATTR_RW(_name) \
+ DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
+ show_##_name, store_##_name)
+/*
+ * ALS Zone threshold-event enable
+ *
+ * in_illuminance0_thresh_either_en 0,1
+ */
+static ILLUMINANCE_ATTR_RW(thresh_either_en);
+
+/*
+ * ALS Current Zone
+ *
+ * in_illuminance0_zone 0-4
+ */
+static ILLUMINANCE_ATTR_RO(zone);
+
+static struct attribute *lm3533_als_event_attributes[] = {
+ &dev_attr_in_illuminance0_thresh_either_en.attr,
+ &lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
+ &lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
+ NULL
+};
+
+static struct attribute_group lm3533_als_event_attribute_group = {
+ .attrs = lm3533_als_event_attributes
+};
+
+static struct attribute *lm3533_als_attributes[] = {
+ &dev_attr_in_illuminance0_zone.attr,
+ &lm3533_als_attr_out_current0_current0_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current0_current1_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current0_current2_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current0_current3_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current0_current4_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current1_current0_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current1_current1_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current1_current2_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current1_current3_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current1_current4_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current2_current0_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current2_current1_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current2_current2_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current2_current3_raw.dev_attr.attr,
+ &lm3533_als_attr_out_current2_current4_raw.dev_attr.attr,
+ NULL
+};
+
+static struct attribute_group lm3533_als_attribute_group = {
+ .attrs = lm3533_als_attributes
+};
+
+static int __devinit lm3533_als_set_input_mode(struct lm3533_als *als,
+ bool pwm_mode)
+{
+ u8 mask = LM3533_ALS_INPUT_MODE_MASK;
+ u8 val;
+ int ret;
+
+ if (pwm_mode)
+ val = mask; /* pwm input */
+ else
+ val = 0; /* analog input */
+
+ ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, val, mask);
+ if (ret) {
+ dev_err(&als->pdev->dev, "failed to set input mode %d\n",
+ pwm_mode);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devinit lm3533_als_set_resistor(struct lm3533_als *als, u8 val)
+{
+ int ret;
+
+ if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX)
+ return -EINVAL;
+
+ ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
+ if (ret) {
+ dev_err(&als->pdev->dev, "failed to set resistor\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devinit lm3533_als_setup(struct lm3533_als *als,
+ struct lm3533_als_platform_data *pdata)
+{
+ int ret;
+
+ ret = lm3533_als_set_input_mode(als, pdata->pwm_mode);
+ if (ret)
+ return ret;
+
+ /* ALS input is always high impedance in PWM-mode. */
+ if (!pdata->pwm_mode) {
+ ret = lm3533_als_set_resistor(als, pdata->r_select);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devinit lm3533_als_setup_irq(struct lm3533_als *als, void *dev)
+{
+ u8 mask = LM3533_ALS_INT_ENABLE_MASK;
+ int ret;
+
+ /* Make sure interrupts are disabled. */
+ ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, 0, mask);
+ if (ret) {
+ dev_err(&als->pdev->dev, "failed to disable interrupts\n");
+ return ret;
+ }
+
+ ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ dev_name(&als->pdev->dev), dev);
+ if (ret) {
+ dev_err(&als->pdev->dev, "failed to request irq %d\n",
+ als->irq);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devinit lm3533_als_enable(struct lm3533_als *als)
+{
+ u8 mask = LM3533_ALS_ENABLE_MASK;
+ int ret;
+
+ ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
+ if (ret)
+ dev_err(&als->pdev->dev, "failed to enable ALS\n");
+
+ return ret;
+}
+
+static int lm3533_als_disable(struct lm3533_als *als)
+{
+ u8 mask = LM3533_ALS_ENABLE_MASK;
+ int ret;
+
+ ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
+ if (ret)
+ dev_err(&als->pdev->dev, "failed to disable ALS\n");
+
+ return ret;
+}
+
+static const struct iio_info lm3533_als_info = {
+ .attrs = &lm3533_als_attribute_group,
+ .event_attrs = &lm3533_als_event_attribute_group,
+ .driver_module = THIS_MODULE,
+ .read_raw = &lm3533_als_read_raw,
+};
+
+static int __devinit lm3533_als_probe(struct platform_device *pdev)
+{
+ struct lm3533 *lm3533;
+ struct lm3533_als_platform_data *pdata;
+ struct lm3533_als *als;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ lm3533 = dev_get_drvdata(pdev->dev.parent);
+ if (!lm3533)
+ return -EINVAL;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ indio_dev = iio_device_alloc(sizeof(*als));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ indio_dev->info = &lm3533_als_info;
+ indio_dev->channels = lm3533_als_channels;
+ indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
+ indio_dev->name = dev_name(&pdev->dev);
+ indio_dev->dev.parent = pdev->dev.parent;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ als = iio_priv(indio_dev);
+ als->lm3533 = lm3533;
+ als->pdev = pdev;
+ als->irq = lm3533->irq;
+ atomic_set(&als->zone, 0);
+ mutex_init(&als->thresh_mutex);
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ if (als->irq) {
+ ret = lm3533_als_setup_irq(als, indio_dev);
+ if (ret)
+ goto err_free_dev;
+ }
+
+ ret = lm3533_als_setup(als, pdata);
+ if (ret)
+ goto err_free_irq;
+
+ ret = lm3533_als_enable(als);
+ if (ret)
+ goto err_free_irq;
+
+ ret = iio_device_register(indio_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register ALS\n");
+ goto err_disable;
+ }
+
+ return 0;
+
+err_disable:
+ lm3533_als_disable(als);
+err_free_irq:
+ if (als->irq)
+ free_irq(als->irq, indio_dev);
+err_free_dev:
+ iio_device_free(indio_dev);
+
+ return ret;
+}
+
+static int __devexit lm3533_als_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct lm3533_als *als = iio_priv(indio_dev);
+
+ lm3533_als_set_int_mode(indio_dev, false);
+ iio_device_unregister(indio_dev);
+ lm3533_als_disable(als);
+ if (als->irq)
+ free_irq(als->irq, indio_dev);
+ iio_device_free(indio_dev);
+
+ return 0;
+}
+
+static struct platform_driver lm3533_als_driver = {
+ .driver = {
+ .name = "lm3533-als",
+ .owner = THIS_MODULE,
+ },
+ .probe = lm3533_als_probe,
+ .remove = __devexit_p(lm3533_als_remove),
+};
+module_platform_driver(lm3533_als_driver);
+
+MODULE_AUTHOR("Johan Hovold <[email protected]>");
+MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-als");
--
1.7.8.5
Michael cc'd for comments on core support of some stuff that is also
in frequency drivers down the end of the email.
On 05/21/2012 10:50 AM, Johan Hovold wrote:
> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>> Add sub-driver for the ambient-light-sensor interface on National
>>> Semiconductor / TI LM3533 lighting power chips.
>>>
>>> The sensor interface can be used to control the LEDs and backlights of
>>> the chip through defining five light zones and three sets of
>>> corresponding brightness target levels.
>>>
>>> The driver provides raw and mean adc readings along with the current
>>> light zone through sysfs. A threshold event can be generated on zone
>>> changes.
>>
>> Hi Johan,
>>
>> I hate to be a pain with this one, but it's a complex beast and I'd
>> really like to get the interface right first time - particularly as
>> you are going in after the move out of staging.
>>
>>
>> Queries for you.
>> 1) Ordering in the probe function. Normally expect iio_device_register
>> to be the last call. Why not here?
>> 2) Worth combining enable / disable into one as very similar functions?
>> 3) Suspicious code in als_set_input_mode
>>
>> Naming of the target values. I think we can make the naming of these
>> fit in much better with the normal abi which is going to be all for the
>> good in the long run. They are basically current output channels
>> with a controllable set of steps (where we don't have direct control
>> of which one we are in). This is very similar to the frequency controls
>> on some of Analog's dds that we have a well defined interface for.
>>
>> More detail below, but in summary.
>>
>> out_currentX_currentY_raw for channel X value for zone Y.
>>
>> Jonathan
>>>
>>> Signed-off-by: Johan Hovold <[email protected]>
>>> ---
>>>
>>> Note that addition of r_select to the platform data probably needs to go
>>> in via mfd.
>>>
>>>
>>> v2:
>>> - reimplement using iio
>>> - add sysfs-ABI documentation
>>> v3
>>> - use indexed channel
>>> - fix sysfs-ABI documentation typo and style
>>> - replace gain attribute with in_illuminance0_calibscale
>>> - add calibscale to platform data
>>> - fix adc register definitions
>>> - replace to_lm3533_dev_attr macro with inline function
>>> - fix device used for error reporting at irq allocation
>>> - use iio device for error reporting during setup/enable
>>> - rebase on staging-next
>>> - fix header include paths
>>> - use dev_to_iio_dev
>>> - add IIO_CHAN_INFO_RAW to info mask
>>> - use iio_device_{alloc,free}
>>> v4
>>> - move to driver/iio/light
>>> - add events/in_illuminance0_threshY_hysteresis attributes
>>> - fix device zone-boundary quirkiness
>>> - clean up attribute handling
>>> - replace calibscale with device-specific r_select attribute
>>>
>>>
>>> .../ABI/testing/sysfs-bus-iio-light-lm3533-als | 64 ++
>>> drivers/iio/Kconfig | 1 +
>>> drivers/iio/Makefile | 1 +
>>> drivers/iio/light/Kconfig | 22 +
>>> drivers/iio/light/Makefile | 5 +
>>> drivers/iio/light/lm3533-als.c | 941 ++++++++++++++++++++
>>> include/linux/mfd/lm3533.h | 1 +
>>> 7 files changed, 1035 insertions(+), 0 deletions(-)
>>> create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>> create mode 100644 drivers/iio/light/Kconfig
>>> create mode 100644 drivers/iio/light/Makefile
>>> create mode 100644 drivers/iio/light/lm3533-als.c
>>>
>>> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>> new file mode 100644
>>> index 0000000..7ea1770
>>> --- /dev/null
>>> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>> @@ -0,0 +1,64 @@
>>> +What: /sys/bus/iio/devices/iio:deviceX/r_select
>>> +Date: April 2012
>>> +KernelVersion: 3.5
>>> +Contact: Johan Hovold <[email protected]>
>>> +Description:
>>> + Set the ALS internal pull-down resistor for analog input mode
>>> + (1..127), such that,
>>> +
>>> + R_als = 200000 / r_select (ohm)
>>> +
>>> + This setting is ignored in PWM-mode (input is always high
>>> + impedance in PWM-mode).
>>> +
>>> +What: /sys/.../events/in_illuminance0_thresh_either_en
>>> +Date: April 2012
>>> +KernelVersion: 3.5
>>> +Contact: Johan Hovold <[email protected]>
>>> +Description:
>>> + Event generated when channel passes one of the four thresholds
>>> + in each direction (rising|falling) and a zone change occurs.
>>> + The corresponding light zone can be read from
>>> + in_illuminance0_zone.
>>> +
>>> +What: /sys/.../events/in_illuminance0_threshY_hysteresis
>>> +Date: May 2012
>>> +KernelVersion: 3.5
>>> +Contact: Johan Hovold <[email protected]>
>>> +Description:
>>> + Get the hysteresis for thresholds Y, that is,
>>> +
>>> + threshY_hysteresis = threshY_raising - threshY_falling
>>> +
>>> +What: /sys/.../events/illuminance_threshY_falling_value
>>> +What: /sys/.../events/illuminance_threshY_raising_value
>>> +Date: April 2012
>>> +KernelVersion: 3.5
>>> +Contact: Johan Hovold <[email protected]>
>>> +Description:
>>> + Specifies the value of threshold that the device is
>>> + comparing against for the events enabled by
>>> + in_illuminance0_thresh_either_en, where Y in 0..3.
>>> +
>>> + Note that threshY_falling must be less than or equal to
>>> + threshY_raising.
>>> +
>>> + These thresholds correspond to the eight zone-boundary
>>> + registers (boundaryY_{low,high}) and defines the five light
>>> + zones.
>>> +
>>> +What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
>>> +Date: April 2012
>>> +KernelVersion: 3.5
>>> +Contact: Johan Hovold <[email protected]>
>>> +Description:
>>> + Get the current light zone (0..4) as defined by the
>>> + in_illuminance0_threshY_{falling,rising} thresholds.
>>> +
>>> +What: /sys/bus/iio/devices/iio:deviceX/targetY_Z
>>> +Date: April 2012
>>> +KernelVersion: 3.5
>>> +Contact: Johan Hovold <[email protected]>
>>> +Description:
>>> + Set the target brightness for ALS-mapper Y in light zone Z
>>> + (0..255), where Y in 1..3 and Z in 0..4.
>>
>> What are the units of this?
>
> The datasheet reads "percent of the full-scale current" (actually depends
> somewhat on whether the als is in linear or exponential mode). When the
> leds or backlights are in PWM-mode (not the ALS necessarily), these
> values are interpreted as a scale factor which is applied to the output
> current determined by the PWM-signal.
>
> Either way it could indeed be considered a raw output current (which
> could be manipulated later by various factors).
Fine.... Theoretically if at all possible we'd want the conversion
factors to get it to an actual current (in amps) to be available though.
(tend to relax that if there are unknowable elements or they aren't
specified by board file etc).
>
>> Also arguably is it not the als that this is related to, but rather
>> the light source?
>
> Well, it would be a raw output, mapping the measured LUX.
Fair enough, though I wonder if we are stepping on led / backlight
classes stuff with this.
>
>> A quick datasheet browse says that these are current targets? If so I
>> wonder if we can make that explicit... Could treat them as 3 output
>> channels and have indexed values like we do for frequencies in dds
>> devices (where external hardware is controlling them.
>
> I think I like this.
>
>> Hmm. lets see.
>>
>> out_currentX_currentY_raw
>> (the double naming is a bit clunky but corresponds to frequency devices
>> where we have
>> out_altvoltageX_frequencyY_raw
>>
>> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
>> as indexed values they can take 0,1,2,3,4
>> out_currentX_raw is not read only and gives you the current for whichever
>> zone the device is currently in.
>
> I take it you mean "out_currentX_raw is read only".
yes. I do indeed. oops.
>
>> This may seem convoluted but I'd really rather have something generalizable
>> for this if we possibly can. We'd still need documentation to say what is
>> controlling these, but at least they'd fit within our more general abi.
>>
>> What do you think?
>
> I like it. From a user perspective it's mainly a change of names (and
> indexes). But conceptually it's perhaps more clear: the als maps it's
> input to an output current, which, just like a PWM-signal, could be used
> as an input to the LEDs and backlights to determine their outputs.
>
> I'd have to modify the LED and backlight interfaces somewhat to reflect
> the changed indexes and terminology (e.g. "output channel" rather
> than "ALS mapper"). Something like:
>
> als_en -- enable als input mode (0,1)
> als_channel -- which out_currentX to use as input (0..2) in
> als input mode
Not entirely sure I'm happy with this. Would rather it was done
on a per channel basis, so in_illuminance0_ *
>From point of view of sensors I don't really care if it is an als or
measuring something active (hence inherently not ambient!)
Silly question but how is the out_current related to the input in als
mode?
>
> So to summarise, we get the following new sysfs-entries for the ALS
> (where the first set replace targetX_Y):
>
> out_currentX_currentY_raw r/w, (0..255), X in 0..2, Y in 0..4
> out_currentX_raw ro (0..255), X in 0..2
>
> Is there any support in core for the first set or should I simply
> rename my target attributes?
No support in the core yet for this sort of thing..
Michael, any thoughts on this? In a sense it's very similar to
out_altvoltageX_frequencyY_raw etc...
>
> Thanks,
> Johan
On Mon, May 21, 2012 at 05:37:11PM +0100, Jonathan Cameron wrote:
> Michael cc'd for comments on core support of some stuff that is also
> in frequency drivers down the end of the email.
>
> On 05/21/2012 10:50 AM, Johan Hovold wrote:
> > On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
> >> On 05/18/2012 02:07 PM, Johan Hovold wrote:
> >>> Add sub-driver for the ambient-light-sensor interface on National
> >>> Semiconductor / TI LM3533 lighting power chips.
> >>>
> >>> The sensor interface can be used to control the LEDs and backlights of
> >>> the chip through defining five light zones and three sets of
> >>> corresponding brightness target levels.
> >>>
> >>> The driver provides raw and mean adc readings along with the current
> >>> light zone through sysfs. A threshold event can be generated on zone
> >>> changes.
> >>
> >> Hi Johan,
> >>
> >> I hate to be a pain with this one, but it's a complex beast and I'd
> >> really like to get the interface right first time - particularly as
> >> you are going in after the move out of staging.
> >>
> >>
> >> Queries for you.
> >> 1) Ordering in the probe function. Normally expect iio_device_register
> >> to be the last call. Why not here?
> >> 2) Worth combining enable / disable into one as very similar functions?
> >> 3) Suspicious code in als_set_input_mode
> >>
> >> Naming of the target values. I think we can make the naming of these
> >> fit in much better with the normal abi which is going to be all for the
> >> good in the long run. They are basically current output channels
> >> with a controllable set of steps (where we don't have direct control
> >> of which one we are in). This is very similar to the frequency controls
> >> on some of Analog's dds that we have a well defined interface for.
> >>
> >> More detail below, but in summary.
> >>
> >> out_currentX_currentY_raw for channel X value for zone Y.
> >>
> >> Jonathan
> >>>
> >>> Signed-off-by: Johan Hovold <[email protected]>
> >>> ---
> >>>
> >>> Note that addition of r_select to the platform data probably needs to go
> >>> in via mfd.
> >>>
> >>>
> >>> v2:
> >>> - reimplement using iio
> >>> - add sysfs-ABI documentation
> >>> v3
> >>> - use indexed channel
> >>> - fix sysfs-ABI documentation typo and style
> >>> - replace gain attribute with in_illuminance0_calibscale
> >>> - add calibscale to platform data
> >>> - fix adc register definitions
> >>> - replace to_lm3533_dev_attr macro with inline function
> >>> - fix device used for error reporting at irq allocation
> >>> - use iio device for error reporting during setup/enable
> >>> - rebase on staging-next
> >>> - fix header include paths
> >>> - use dev_to_iio_dev
> >>> - add IIO_CHAN_INFO_RAW to info mask
> >>> - use iio_device_{alloc,free}
> >>> v4
> >>> - move to driver/iio/light
> >>> - add events/in_illuminance0_threshY_hysteresis attributes
> >>> - fix device zone-boundary quirkiness
> >>> - clean up attribute handling
> >>> - replace calibscale with device-specific r_select attribute
> >>>
> >>>
> >>> .../ABI/testing/sysfs-bus-iio-light-lm3533-als | 64 ++
> >>> drivers/iio/Kconfig | 1 +
> >>> drivers/iio/Makefile | 1 +
> >>> drivers/iio/light/Kconfig | 22 +
> >>> drivers/iio/light/Makefile | 5 +
> >>> drivers/iio/light/lm3533-als.c | 941 ++++++++++++++++++++
> >>> include/linux/mfd/lm3533.h | 1 +
> >>> 7 files changed, 1035 insertions(+), 0 deletions(-)
> >>> create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >>> create mode 100644 drivers/iio/light/Kconfig
> >>> create mode 100644 drivers/iio/light/Makefile
> >>> create mode 100644 drivers/iio/light/lm3533-als.c
> >>>
> >>> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >>> new file mode 100644
> >>> index 0000000..7ea1770
> >>> --- /dev/null
> >>> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >>> @@ -0,0 +1,64 @@
> >>> +What: /sys/bus/iio/devices/iio:deviceX/r_select
> >>> +Date: April 2012
> >>> +KernelVersion: 3.5
> >>> +Contact: Johan Hovold <[email protected]>
> >>> +Description:
> >>> + Set the ALS internal pull-down resistor for analog input mode
> >>> + (1..127), such that,
> >>> +
> >>> + R_als = 200000 / r_select (ohm)
> >>> +
> >>> + This setting is ignored in PWM-mode (input is always high
> >>> + impedance in PWM-mode).
> >>> +
> >>> +What: /sys/.../events/in_illuminance0_thresh_either_en
> >>> +Date: April 2012
> >>> +KernelVersion: 3.5
> >>> +Contact: Johan Hovold <[email protected]>
> >>> +Description:
> >>> + Event generated when channel passes one of the four thresholds
> >>> + in each direction (rising|falling) and a zone change occurs.
> >>> + The corresponding light zone can be read from
> >>> + in_illuminance0_zone.
> >>> +
> >>> +What: /sys/.../events/in_illuminance0_threshY_hysteresis
> >>> +Date: May 2012
> >>> +KernelVersion: 3.5
> >>> +Contact: Johan Hovold <[email protected]>
> >>> +Description:
> >>> + Get the hysteresis for thresholds Y, that is,
> >>> +
> >>> + threshY_hysteresis = threshY_raising - threshY_falling
> >>> +
> >>> +What: /sys/.../events/illuminance_threshY_falling_value
> >>> +What: /sys/.../events/illuminance_threshY_raising_value
> >>> +Date: April 2012
> >>> +KernelVersion: 3.5
> >>> +Contact: Johan Hovold <[email protected]>
> >>> +Description:
> >>> + Specifies the value of threshold that the device is
> >>> + comparing against for the events enabled by
> >>> + in_illuminance0_thresh_either_en, where Y in 0..3.
> >>> +
> >>> + Note that threshY_falling must be less than or equal to
> >>> + threshY_raising.
> >>> +
> >>> + These thresholds correspond to the eight zone-boundary
> >>> + registers (boundaryY_{low,high}) and defines the five light
> >>> + zones.
> >>> +
> >>> +What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> >>> +Date: April 2012
> >>> +KernelVersion: 3.5
> >>> +Contact: Johan Hovold <[email protected]>
> >>> +Description:
> >>> + Get the current light zone (0..4) as defined by the
> >>> + in_illuminance0_threshY_{falling,rising} thresholds.
> >>> +
> >>> +What: /sys/bus/iio/devices/iio:deviceX/targetY_Z
> >>> +Date: April 2012
> >>> +KernelVersion: 3.5
> >>> +Contact: Johan Hovold <[email protected]>
> >>> +Description:
> >>> + Set the target brightness for ALS-mapper Y in light zone Z
> >>> + (0..255), where Y in 1..3 and Z in 0..4.
> >>
> >> What are the units of this?
> >
> > The datasheet reads "percent of the full-scale current" (actually depends
> > somewhat on whether the als is in linear or exponential mode). When the
> > leds or backlights are in PWM-mode (not the ALS necessarily), these
> > values are interpreted as a scale factor which is applied to the output
> > current determined by the PWM-signal.
> >
> > Either way it could indeed be considered a raw output current (which
> > could be manipulated later by various factors).
> Fine.... Theoretically if at all possible we'd want the conversion
> factors to get it to an actual current (in amps) to be available though.
> (tend to relax that if there are unknowable elements or they aren't
> specified by board file etc).
Hmmm. I'm starting to get a feeling that we're over-doing this. The ALS
on the lm3533 isn't a general purpose sensor. It's simply a way to
control the leds and backlights of that device. So what you do is to
determine the full-scale current (max current at maximum brightness 0xff
in this case -- set in board file). Then the ALS input range is divided
in 5 zones, and for each zone you set a brightness as a percent of the
full-scale current. You relly don't care about amps (except for the
maximum determined by the setup).
The equation's for determining the current are available in the
datasheets however, but they depend on which mapping mode (linear or
exponential) and can also be effected by PWM-input duty cycle etc. For
this particular device, I really don't see the point in trying to
determine actual current in amps in all these settings.
Note also that the actual output current cannot be determined in the
ALS as the required factors are only set/known in the led/backlight
devices (mapping mode, pwm-mode).
> >> Also arguably is it not the als that this is related to, but rather
> >> the light source?
> >
> > Well, it would be a raw output, mapping the measured LUX.
> Fair enough, though I wonder if we are stepping on led / backlight
> classes stuff with this.
Keeping the target sets and the mapper terminology could still be an
option.
ALS input mode is a special mode of the LEDs and backlights which
overrides the direct current control. It's indeed a special-purpose
device.
> >> A quick datasheet browse says that these are current targets? If so I
> >> wonder if we can make that explicit... Could treat them as 3 output
> >> channels and have indexed values like we do for frequencies in dds
> >> devices (where external hardware is controlling them.
> >
> > I think I like this.
> >
> >> Hmm. lets see.
> >>
> >> out_currentX_currentY_raw
> >> (the double naming is a bit clunky but corresponds to frequency devices
> >> where we have
> >> out_altvoltageX_frequencyY_raw
> >>
> >> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
> >> as indexed values they can take 0,1,2,3,4
> >> out_currentX_raw is not read only and gives you the current for whichever
> >> zone the device is currently in.
> >
> > I take it you mean "out_currentX_raw is read only".
> yes. I do indeed. oops.
> >
> >> This may seem convoluted but I'd really rather have something generalizable
> >> for this if we possibly can. We'd still need documentation to say what is
> >> controlling these, but at least they'd fit within our more general abi.
> >>
> >> What do you think?
> >
> > I like it. From a user perspective it's mainly a change of names (and
> > indexes). But conceptually it's perhaps more clear: the als maps it's
> > input to an output current, which, just like a PWM-signal, could be used
> > as an input to the LEDs and backlights to determine their outputs.
> >
> > I'd have to modify the LED and backlight interfaces somewhat to reflect
> > the changed indexes and terminology (e.g. "output channel" rather
> > than "ALS mapper"). Something like:
> >
> > als_en -- enable als input mode (0,1)
> > als_channel -- which out_currentX to use as input (0..2) in
> > als input mode
> Not entirely sure I'm happy with this. Would rather it was done
> on a per channel basis, so in_illuminance0_ *
> From point of view of sensors I don't really care if it is an als or
> measuring something active (hence inherently not ambient!)
Not sure I understood that. What is it you don't like about it? You need
to keep in mind what is actually there; three sets of target values per
zone of which one set is dedicated to the first backlight device. That
means, that the ALS mapper (or channel if we want to use that
terminology) needs to be set in the actual devices and not the other way
round. [ The als_en and als_channel attributes would belong to the
led/backlight devices. ]
What did you mean by "per channel basis, so in_illuminance0_ *"?
Again it's a special purpose device -- the lm3533 leds and backlights
are controlled in hw by the on-chip als interface. We can't just use any
generic iio device for this.
> Silly question but how is the out_current related to the input in als
> mode?
1. raw adc input is averaged
2. mean adc input is mapped to zone using zone registers ("thresholds")
3. zone is mapped to a percentage using ALS mapper registers, that is,
three sets of 8-bit values per zone. Which set is used can be set on
a per-device (led, backlight) basis
4. the percentage is applied to the per-device full-scale current to
determine the actual output. How this mapping is done depends on if
linear or exponential mapping mode is set (also per-device).
[ And things can get even more complicated if the devices are in
PWM-mode, but this is roughly the full picture. ]
And all of the above is done in hw.
So, we're only using out_current channels because it maybe fits iio
better. For anyone familiar with the lm3533 this may even just confuse
things.
There are only the three tables of values that maps a zone to an output
percentage (e.g. half brightness in zone 2).
> > So to summarise, we get the following new sysfs-entries for the ALS
> > (where the first set replace targetX_Y):
> >
> > out_currentX_currentY_raw r/w, (0..255), X in 0..2, Y in 0..4
> > out_currentX_raw ro (0..255), X in 0..2
> >
> > Is there any support in core for the first set or should I simply
> > rename my target attributes?
> No support in the core yet for this sort of thing..
> Michael, any thoughts on this? In a sense it's very similar to
> out_altvoltageX_frequencyY_raw etc...
> >
> > Thanks,
> > Johan
>
On 5/21/2012 11:07 PM, Johan Hovold wrote:
> On Mon, May 21, 2012 at 05:37:11PM +0100, Jonathan Cameron wrote:
>> Michael cc'd for comments on core support of some stuff that is also
>> in frequency drivers down the end of the email.
>>
>> On 05/21/2012 10:50 AM, Johan Hovold wrote:
>>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>>>> Add sub-driver for the ambient-light-sensor interface on National
>>>>> Semiconductor / TI LM3533 lighting power chips.
>>>>>
>>>>> The sensor interface can be used to control the LEDs and backlights of
>>>>> the chip through defining five light zones and three sets of
>>>>> corresponding brightness target levels.
>>>>>
>>>>> The driver provides raw and mean adc readings along with the current
>>>>> light zone through sysfs. A threshold event can be generated on zone
>>>>> changes.
>>>>
>>>> Hi Johan,
>>>>
>>>> I hate to be a pain with this one, but it's a complex beast and I'd
>>>> really like to get the interface right first time - particularly as
>>>> you are going in after the move out of staging.
>>>>
>>>>
>>>> Queries for you.
>>>> 1) Ordering in the probe function. Normally expect iio_device_register
>>>> to be the last call. Why not here?
>>>> 2) Worth combining enable / disable into one as very similar functions?
>>>> 3) Suspicious code in als_set_input_mode
>>>>
>>>> Naming of the target values. I think we can make the naming of these
>>>> fit in much better with the normal abi which is going to be all for the
>>>> good in the long run. They are basically current output channels
>>>> with a controllable set of steps (where we don't have direct control
>>>> of which one we are in). This is very similar to the frequency controls
>>>> on some of Analog's dds that we have a well defined interface for.
>>>>
>>>> More detail below, but in summary.
>>>>
>>>> out_currentX_currentY_raw for channel X value for zone Y.
>>>>
>>>> Jonathan
>>>>>
>>>>> Signed-off-by: Johan Hovold<[email protected]>
>>>>> ---
>>>>>
>>>>> Note that addition of r_select to the platform data probably needs to go
>>>>> in via mfd.
>>>>>
>>>>>
>>>>> v2:
>>>>> - reimplement using iio
>>>>> - add sysfs-ABI documentation
>>>>> v3
>>>>> - use indexed channel
>>>>> - fix sysfs-ABI documentation typo and style
>>>>> - replace gain attribute with in_illuminance0_calibscale
>>>>> - add calibscale to platform data
>>>>> - fix adc register definitions
>>>>> - replace to_lm3533_dev_attr macro with inline function
>>>>> - fix device used for error reporting at irq allocation
>>>>> - use iio device for error reporting during setup/enable
>>>>> - rebase on staging-next
>>>>> - fix header include paths
>>>>> - use dev_to_iio_dev
>>>>> - add IIO_CHAN_INFO_RAW to info mask
>>>>> - use iio_device_{alloc,free}
>>>>> v4
>>>>> - move to driver/iio/light
>>>>> - add events/in_illuminance0_threshY_hysteresis attributes
>>>>> - fix device zone-boundary quirkiness
>>>>> - clean up attribute handling
>>>>> - replace calibscale with device-specific r_select attribute
>>>>>
>>>>>
>>>>> .../ABI/testing/sysfs-bus-iio-light-lm3533-als | 64 ++
>>>>> drivers/iio/Kconfig | 1 +
>>>>> drivers/iio/Makefile | 1 +
>>>>> drivers/iio/light/Kconfig | 22 +
>>>>> drivers/iio/light/Makefile | 5 +
>>>>> drivers/iio/light/lm3533-als.c | 941 ++++++++++++++++++++
>>>>> include/linux/mfd/lm3533.h | 1 +
>>>>> 7 files changed, 1035 insertions(+), 0 deletions(-)
>>>>> create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>>> create mode 100644 drivers/iio/light/Kconfig
>>>>> create mode 100644 drivers/iio/light/Makefile
>>>>> create mode 100644 drivers/iio/light/lm3533-als.c
>>>>>
>>>>> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>>> new file mode 100644
>>>>> index 0000000..7ea1770
>>>>> --- /dev/null
>>>>> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>>> @@ -0,0 +1,64 @@
>>>>> +What: /sys/bus/iio/devices/iio:deviceX/r_select
>>>>> +Date: April 2012
>>>>> +KernelVersion: 3.5
>>>>> +Contact: Johan Hovold<[email protected]>
>>>>> +Description:
>>>>> + Set the ALS internal pull-down resistor for analog input mode
>>>>> + (1..127), such that,
>>>>> +
>>>>> + R_als = 200000 / r_select (ohm)
>>>>> +
>>>>> + This setting is ignored in PWM-mode (input is always high
>>>>> + impedance in PWM-mode).
>>>>> +
>>>>> +What: /sys/.../events/in_illuminance0_thresh_either_en
>>>>> +Date: April 2012
>>>>> +KernelVersion: 3.5
>>>>> +Contact: Johan Hovold<[email protected]>
>>>>> +Description:
>>>>> + Event generated when channel passes one of the four thresholds
>>>>> + in each direction (rising|falling) and a zone change occurs.
>>>>> + The corresponding light zone can be read from
>>>>> + in_illuminance0_zone.
>>>>> +
>>>>> +What: /sys/.../events/in_illuminance0_threshY_hysteresis
>>>>> +Date: May 2012
>>>>> +KernelVersion: 3.5
>>>>> +Contact: Johan Hovold<[email protected]>
>>>>> +Description:
>>>>> + Get the hysteresis for thresholds Y, that is,
>>>>> +
>>>>> + threshY_hysteresis = threshY_raising - threshY_falling
>>>>> +
>>>>> +What: /sys/.../events/illuminance_threshY_falling_value
>>>>> +What: /sys/.../events/illuminance_threshY_raising_value
>>>>> +Date: April 2012
>>>>> +KernelVersion: 3.5
>>>>> +Contact: Johan Hovold<[email protected]>
>>>>> +Description:
>>>>> + Specifies the value of threshold that the device is
>>>>> + comparing against for the events enabled by
>>>>> + in_illuminance0_thresh_either_en, where Y in 0..3.
>>>>> +
>>>>> + Note that threshY_falling must be less than or equal to
>>>>> + threshY_raising.
>>>>> +
>>>>> + These thresholds correspond to the eight zone-boundary
>>>>> + registers (boundaryY_{low,high}) and defines the five light
>>>>> + zones.
>>>>> +
>>>>> +What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
>>>>> +Date: April 2012
>>>>> +KernelVersion: 3.5
>>>>> +Contact: Johan Hovold<[email protected]>
>>>>> +Description:
>>>>> + Get the current light zone (0..4) as defined by the
>>>>> + in_illuminance0_threshY_{falling,rising} thresholds.
>>>>> +
>>>>> +What: /sys/bus/iio/devices/iio:deviceX/targetY_Z
>>>>> +Date: April 2012
>>>>> +KernelVersion: 3.5
>>>>> +Contact: Johan Hovold<[email protected]>
>>>>> +Description:
>>>>> + Set the target brightness for ALS-mapper Y in light zone Z
>>>>> + (0..255), where Y in 1..3 and Z in 0..4.
>>>>
>>>> What are the units of this?
>>>
>>> The datasheet reads "percent of the full-scale current" (actually depends
>>> somewhat on whether the als is in linear or exponential mode). When the
>>> leds or backlights are in PWM-mode (not the ALS necessarily), these
>>> values are interpreted as a scale factor which is applied to the output
>>> current determined by the PWM-signal.
>>>
>>> Either way it could indeed be considered a raw output current (which
>>> could be manipulated later by various factors).
>> Fine.... Theoretically if at all possible we'd want the conversion
>> factors to get it to an actual current (in amps) to be available though.
>> (tend to relax that if there are unknowable elements or they aren't
>> specified by board file etc).
>
> Hmmm. I'm starting to get a feeling that we're over-doing this. The ALS
> on the lm3533 isn't a general purpose sensor. It's simply a way to
> control the leds and backlights of that device. So what you do is to
> determine the full-scale current (max current at maximum brightness 0xff
> in this case -- set in board file). Then the ALS input range is divided
> in 5 zones, and for each zone you set a brightness as a percent of the
> full-scale current. You relly don't care about amps (except for the
> maximum determined by the setup).
Then no need to provide scale etc.
>
> The equation's for determining the current are available in the
> datasheets however, but they depend on which mapping mode (linear or
> exponential) and can also be effected by PWM-input duty cycle etc. For
> this particular device, I really don't see the point in trying to
> determine actual current in amps in all these settings.
We can always add it later if anyone cares.
>
> Note also that the actual output current cannot be determined in the
> ALS as the required factors are only set/known in the led/backlight
> devices (mapping mode, pwm-mode).
Could query it back if it was useful, but sounds like probably not.
If we don't provide the information it can't be wrong...
I'd basically missunderstood where the division between the sensor and
the led/backlight drivers lay. I think I'm happy now with where you
have it.
>
>>>> Also arguably is it not the als that this is related to, but rather
>>>> the light source?
>>>
>>> Well, it would be a raw output, mapping the measured LUX.
>> Fair enough, though I wonder if we are stepping on led / backlight
>> classes stuff with this.
>
> Keeping the target sets and the mapper terminology could still be an
> option.
>
> ALS input mode is a special mode of the LEDs and backlights which
> overrides the direct current control. It's indeed a special-purpose
> device.
Yup, I understand you now!
>
>>>> A quick datasheet browse says that these are current targets? If so I
>>>> wonder if we can make that explicit... Could treat them as 3 output
>>>> channels and have indexed values like we do for frequencies in dds
>>>> devices (where external hardware is controlling them.
>>>
>>> I think I like this.
>>>
>>>> Hmm. lets see.
>>>>
>>>> out_currentX_currentY_raw
>>>> (the double naming is a bit clunky but corresponds to frequency devices
>>>> where we have
>>>> out_altvoltageX_frequencyY_raw
>>>>
>>>> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
>>>> as indexed values they can take 0,1,2,3,4
>>>> out_currentX_raw is not read only and gives you the current for whichever
>>>> zone the device is currently in.
>>>
>>> I take it you mean "out_currentX_raw is read only".
>> yes. I do indeed. oops.
>>>
>>>> This may seem convoluted but I'd really rather have something generalizable
>>>> for this if we possibly can. We'd still need documentation to say what is
>>>> controlling these, but at least they'd fit within our more general abi.
>>>>
>>>> What do you think?
>>>
>>> I like it. From a user perspective it's mainly a change of names (and
>>> indexes). But conceptually it's perhaps more clear: the als maps it's
>>> input to an output current, which, just like a PWM-signal, could be used
>>> as an input to the LEDs and backlights to determine their outputs.
>>>
>>> I'd have to modify the LED and backlight interfaces somewhat to reflect
>>> the changed indexes and terminology (e.g. "output channel" rather
>>> than "ALS mapper"). Something like:
>>>
>>> als_en -- enable als input mode (0,1)
>>> als_channel -- which out_currentX to use as input (0..2) in
>>> als input mode
>> Not entirely sure I'm happy with this. Would rather it was done
>> on a per channel basis, so in_illuminance0_ *
>> From point of view of sensors I don't really care if it is an als or
>> measuring something active (hence inherently not ambient!)
>
> Not sure I understood that. What is it you don't like about it?
You need
> to keep in mind what is actually there; three sets of target values per
> zone of which one set is dedicated to the first backlight device. That
> means, that the ALS mapper (or channel if we want to use that
> terminology) needs to be set in the actual devices and not the other way
> round. [ The als_en and als_channel attributes would belong to the
> led/backlight devices. ]
Ah. THat last bit in brackets is what I'd missed ;) That's fine with me
then.
>
> What did you mean by "per channel basis, so in_illuminance0_ *"?
>
> Again it's a special purpose device -- the lm3533 leds and backlights
> are controlled in hw by the on-chip als interface. We can't just use any
> generic iio device for this.
sure. Had missunderstood completely.
>
>> Silly question but how is the out_current related to the input in als
>> mode?
>
> 1. raw adc input is averaged
> 2. mean adc input is mapped to zone using zone registers ("thresholds")
> 3. zone is mapped to a percentage using ALS mapper registers, that is,
> three sets of 8-bit values per zone. Which set is used can be set on
> a per-device (led, backlight) basis
> 4. the percentage is applied to the per-device full-scale current to
> determine the actual output. How this mapping is done depends on if
> linear or exponential mapping mode is set (also per-device).
>
All makes sense now. Thanks for the clarification.
> [ And things can get even more complicated if the devices are in
> PWM-mode, but this is roughly the full picture. ]
>
> And all of the above is done in hw.
>
> So, we're only using out_current channels because it maybe fits iio
> better. For anyone familiar with the lm3533 this may even just confuse
> things.
I'm realy keen to do this primarily because we may well hit similar
devices at a later stage and it's much nicer to generalize earlier
than go through supporting the old method in parallel for years...
What we have here is general enough to support a wide range of possible
devices so lets go with it.
>
> There are only the three tables of values that maps a zone to an output
> percentage (e.g. half brightness in zone 2).
One last question. Are the als -> LEd /backlight mapping something a
device user would ever change? If not the most general option would be
to use the iio inkernel mapping interfaces to do the assignments. Gives
you a clean easy way to allow reading back of current brightness in the
led drivers etc...
>
>>> So to summarise, we get the following new sysfs-entries for the ALS
>>> (where the first set replace targetX_Y):
>>>
>>> out_currentX_currentY_raw r/w, (0..255), X in 0..2, Y in 0..4
>>> out_currentX_raw ro (0..255), X in 0..2
>>>
>>> Is there any support in core for the first set or should I simply
>>> rename my target attributes?
>> No support in the core yet for this sort of thing..
>> Michael, any thoughts on this? In a sense it's very similar to
>> out_altvoltageX_frequencyY_raw etc...
>>>
>>> Thanks,
>>> Johan
>>
On 05/21/2012 06:37 PM, Jonathan Cameron wrote:
> Michael cc'd for comments on core support of some stuff that is also
> in frequency drivers down the end of the email.
>
> On 05/21/2012 10:50 AM, Johan Hovold wrote:
>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>> So to summarise, we get the following new sysfs-entries for the ALS
>>> (where the first set replace targetX_Y):
>>>
>>> out_currentX_currentY_raw r/w, (0..255), X in 0..2, Y in 0..4
>>> out_currentX_raw ro (0..255), X in 0..2
>>>
>>> Is there any support in core for the first set or should I simply
>>> rename my target attributes?
> No support in the core yet for this sort of thing..
> Michael, any thoughts on this? In a sense it's very similar to
> out_altvoltageX_frequencyY_raw etc...
>
Hi,
Exactly it's similar to what we have on the DDS parts.
The DDS parts would also benefit from a second index.
Can someone think of an use case where we have 'differential' or 'modified'
together with this pattern?
If not we could use channel2 and introduce an new flag, but I fear
that we end up using channel2 for too many things.
So we should better introduce an new variable?
--
Greetings,
Michael
--
Analog Devices GmbH Wilhelm-Wagenfeld-Str. 6 80807 Muenchen
Sitz der Gesellschaft: Muenchen; Registergericht: Muenchen HRB 40368;
Geschaeftsfuehrer:Dr.Carsten Suckrow, Thomas Wessel, William A. Martin,
Margaret Seif
On 5/22/2012 8:45 AM, Michael Hennerich wrote:
> On 05/21/2012 06:37 PM, Jonathan Cameron wrote:
>> Michael cc'd for comments on core support of some stuff that is also
>> in frequency drivers down the end of the email.
>>
>> On 05/21/2012 10:50 AM, Johan Hovold wrote:
>>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>>> So to summarise, we get the following new sysfs-entries for the ALS
>>>> (where the first set replace targetX_Y):
>>>>
>>>> out_currentX_currentY_raw r/w, (0..255), X in 0..2, Y in 0..4
>>>> out_currentX_raw ro (0..255), X in 0..2
>>>>
>>>> Is there any support in core for the first set or should I simply
>>>> rename my target attributes?
>> No support in the core yet for this sort of thing..
>> Michael, any thoughts on this? In a sense it's very similar to
>> out_altvoltageX_frequencyY_raw etc...
>>
> Hi,
>
> Exactly it's similar to what we have on the DDS parts.
> The DDS parts would also benefit from a second index.
>
> Can someone think of an use case where we have 'differential' or 'modified'
> together with this pattern?
>
> If not we could use channel2 and introduce an new flag, but I fear
> that we end up using channel2 for too many things.
> So we should better introduce an new variable?
>
New variable. Tedious but channel2 is getting rather too overloaded.
On 05/22/2012 09:49 AM, Jonathan Cameron wrote:
> On 5/22/2012 8:45 AM, Michael Hennerich wrote:
>> On 05/21/2012 06:37 PM, Jonathan Cameron wrote:
>>> Michael cc'd for comments on core support of some stuff that is also
>>> in frequency drivers down the end of the email.
>>>
>>> On 05/21/2012 10:50 AM, Johan Hovold wrote:
>>>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>>>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>>>> So to summarise, we get the following new sysfs-entries for the ALS
>>>>> (where the first set replace targetX_Y):
>>>>>
>>>>> out_currentX_currentY_raw r/w, (0..255), X in 0..2, Y in 0..4
>>>>> out_currentX_raw ro (0..255), X in 0..2
>>>>>
>>>>> Is there any support in core for the first set or should I simply
>>>>> rename my target attributes?
>>> No support in the core yet for this sort of thing..
>>> Michael, any thoughts on this? In a sense it's very similar to
>>> out_altvoltageX_frequencyY_raw etc...
>>>
>> Hi,
>>
>> Exactly it's similar to what we have on the DDS parts.
>> The DDS parts would also benefit from a second index.
>>
>> Can someone think of an use case where we have 'differential' or 'modified'
>> together with this pattern?
>>
>> If not we could use channel2 and introduce an new flag, but I fear
>> that we end up using channel2 for too many things.
>> So we should better introduce an new variable?
>>
> New variable. Tedious but channel2 is getting rather too overloaded.
>
>
>
Blah channel2 is not an option at all.
We need to generate an index on the postfix.
And we can't pass this information via info_mask.
So ext_info could be your friend?
--
Greetings,
Michael
--
Analog Devices GmbH Wilhelm-Wagenfeld-Str. 6 80807 Muenchen
Sitz der Gesellschaft: Muenchen; Registergericht: Muenchen HRB 40368;
Geschaeftsfuehrer:Dr.Carsten Suckrow, Thomas Wessel, William A. Martin,
Margaret Seif
On 5/22/2012 9:11 AM, Michael Hennerich wrote:
> On 05/22/2012 09:49 AM, Jonathan Cameron wrote:
>> On 5/22/2012 8:45 AM, Michael Hennerich wrote:
>>> On 05/21/2012 06:37 PM, Jonathan Cameron wrote:
>>>> Michael cc'd for comments on core support of some stuff that is also
>>>> in frequency drivers down the end of the email.
>>>>
>>>> On 05/21/2012 10:50 AM, Johan Hovold wrote:
>>>>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>>>>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>>>>> So to summarise, we get the following new sysfs-entries for the ALS
>>>>>> (where the first set replace targetX_Y):
>>>>>>
>>>>>> out_currentX_currentY_raw r/w, (0..255), X in 0..2, Y in 0..4
>>>>>> out_currentX_raw ro (0..255), X in 0..2
>>>>>>
>>>>>> Is there any support in core for the first set or should I simply
>>>>>> rename my target attributes?
>>>> No support in the core yet for this sort of thing..
>>>> Michael, any thoughts on this? In a sense it's very similar to
>>>> out_altvoltageX_frequencyY_raw etc...
>>>>
>>> Hi,
>>>
>>> Exactly it's similar to what we have on the DDS parts.
>>> The DDS parts would also benefit from a second index.
>>>
>>> Can someone think of an use case where we have 'differential' or
>>> 'modified'
>>> together with this pattern?
>>>
>>> If not we could use channel2 and introduce an new flag, but I fear
>>> that we end up using channel2 for too many things.
>>> So we should better introduce an new variable?
>>>
>> New variable. Tedious but channel2 is getting rather too overloaded.
>>
>>
>>
> Blah channel2 is not an option at all.
Gah. Good point. Not enough coffee this morning...
> We need to generate an index on the postfix.
> And we can't pass this information via info_mask.
> So ext_info could be your friend?
Short term yes, though we may want more specific support for this
down the line (to make for coherent in kernel interface). Not yet
sure what form that will take though.
>
On Tue, May 22, 2012 at 08:13:12AM +0100, Jonathan Cameron wrote:
> On 5/21/2012 11:07 PM, Johan Hovold wrote:
> > On Mon, May 21, 2012 at 05:37:11PM +0100, Jonathan Cameron wrote:
> >> Michael cc'd for comments on core support of some stuff that is also
> >> in frequency drivers down the end of the email.
> >>
> >> On 05/21/2012 10:50 AM, Johan Hovold wrote:
> >>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
> >>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
> >>>>> Add sub-driver for the ambient-light-sensor interface on National
> >>>>> Semiconductor / TI LM3533 lighting power chips.
> >>>>>
> >>>>> The sensor interface can be used to control the LEDs and backlights of
> >>>>> the chip through defining five light zones and three sets of
> >>>>> corresponding brightness target levels.
> >>>>>
> >>>>> The driver provides raw and mean adc readings along with the current
> >>>>> light zone through sysfs. A threshold event can be generated on zone
> >>>>> changes.
> >>>>
> >>>> Hi Johan,
> >>>>
> >>>> I hate to be a pain with this one, but it's a complex beast and I'd
> >>>> really like to get the interface right first time - particularly as
> >>>> you are going in after the move out of staging.
> >>>>
> >>>>
> >>>> Queries for you.
> >>>> 1) Ordering in the probe function. Normally expect iio_device_register
> >>>> to be the last call. Why not here?
> >>>> 2) Worth combining enable / disable into one as very similar functions?
> >>>> 3) Suspicious code in als_set_input_mode
> >>>>
> >>>> Naming of the target values. I think we can make the naming of these
> >>>> fit in much better with the normal abi which is going to be all for the
> >>>> good in the long run. They are basically current output channels
> >>>> with a controllable set of steps (where we don't have direct control
> >>>> of which one we are in). This is very similar to the frequency controls
> >>>> on some of Analog's dds that we have a well defined interface for.
> >>>>
> >>>> More detail below, but in summary.
> >>>>
> >>>> out_currentX_currentY_raw for channel X value for zone Y.
> >>>>
> >>>> Jonathan
> >>>>>
> >>>>> Signed-off-by: Johan Hovold<[email protected]>
> >>>>> ---
> >>>>>
> >>>>> Note that addition of r_select to the platform data probably needs to go
> >>>>> in via mfd.
> >>>>>
> >>>>>
> >>>>> v2:
> >>>>> - reimplement using iio
> >>>>> - add sysfs-ABI documentation
> >>>>> v3
> >>>>> - use indexed channel
> >>>>> - fix sysfs-ABI documentation typo and style
> >>>>> - replace gain attribute with in_illuminance0_calibscale
> >>>>> - add calibscale to platform data
> >>>>> - fix adc register definitions
> >>>>> - replace to_lm3533_dev_attr macro with inline function
> >>>>> - fix device used for error reporting at irq allocation
> >>>>> - use iio device for error reporting during setup/enable
> >>>>> - rebase on staging-next
> >>>>> - fix header include paths
> >>>>> - use dev_to_iio_dev
> >>>>> - add IIO_CHAN_INFO_RAW to info mask
> >>>>> - use iio_device_{alloc,free}
> >>>>> v4
> >>>>> - move to driver/iio/light
> >>>>> - add events/in_illuminance0_threshY_hysteresis attributes
> >>>>> - fix device zone-boundary quirkiness
> >>>>> - clean up attribute handling
> >>>>> - replace calibscale with device-specific r_select attribute
> >>>>>
> >>>>>
> >>>>> .../ABI/testing/sysfs-bus-iio-light-lm3533-als | 64 ++
> >>>>> drivers/iio/Kconfig | 1 +
> >>>>> drivers/iio/Makefile | 1 +
> >>>>> drivers/iio/light/Kconfig | 22 +
> >>>>> drivers/iio/light/Makefile | 5 +
> >>>>> drivers/iio/light/lm3533-als.c | 941 ++++++++++++++++++++
> >>>>> include/linux/mfd/lm3533.h | 1 +
> >>>>> 7 files changed, 1035 insertions(+), 0 deletions(-)
> >>>>> create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >>>>> create mode 100644 drivers/iio/light/Kconfig
> >>>>> create mode 100644 drivers/iio/light/Makefile
> >>>>> create mode 100644 drivers/iio/light/lm3533-als.c
> >>>>>
> >>>>> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >>>>> new file mode 100644
> >>>>> index 0000000..7ea1770
> >>>>> --- /dev/null
> >>>>> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> >>>>> @@ -0,0 +1,64 @@
> >>>>> +What: /sys/bus/iio/devices/iio:deviceX/r_select
> >>>>> +Date: April 2012
> >>>>> +KernelVersion: 3.5
> >>>>> +Contact: Johan Hovold<[email protected]>
> >>>>> +Description:
> >>>>> + Set the ALS internal pull-down resistor for analog input mode
> >>>>> + (1..127), such that,
> >>>>> +
> >>>>> + R_als = 200000 / r_select (ohm)
> >>>>> +
> >>>>> + This setting is ignored in PWM-mode (input is always high
> >>>>> + impedance in PWM-mode).
> >>>>> +
> >>>>> +What: /sys/.../events/in_illuminance0_thresh_either_en
> >>>>> +Date: April 2012
> >>>>> +KernelVersion: 3.5
> >>>>> +Contact: Johan Hovold<[email protected]>
> >>>>> +Description:
> >>>>> + Event generated when channel passes one of the four thresholds
> >>>>> + in each direction (rising|falling) and a zone change occurs.
> >>>>> + The corresponding light zone can be read from
> >>>>> + in_illuminance0_zone.
> >>>>> +
> >>>>> +What: /sys/.../events/in_illuminance0_threshY_hysteresis
> >>>>> +Date: May 2012
> >>>>> +KernelVersion: 3.5
> >>>>> +Contact: Johan Hovold<[email protected]>
> >>>>> +Description:
> >>>>> + Get the hysteresis for thresholds Y, that is,
> >>>>> +
> >>>>> + threshY_hysteresis = threshY_raising - threshY_falling
> >>>>> +
> >>>>> +What: /sys/.../events/illuminance_threshY_falling_value
> >>>>> +What: /sys/.../events/illuminance_threshY_raising_value
> >>>>> +Date: April 2012
> >>>>> +KernelVersion: 3.5
> >>>>> +Contact: Johan Hovold<[email protected]>
> >>>>> +Description:
> >>>>> + Specifies the value of threshold that the device is
> >>>>> + comparing against for the events enabled by
> >>>>> + in_illuminance0_thresh_either_en, where Y in 0..3.
> >>>>> +
> >>>>> + Note that threshY_falling must be less than or equal to
> >>>>> + threshY_raising.
> >>>>> +
> >>>>> + These thresholds correspond to the eight zone-boundary
> >>>>> + registers (boundaryY_{low,high}) and defines the five light
> >>>>> + zones.
> >>>>> +
> >>>>> +What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> >>>>> +Date: April 2012
> >>>>> +KernelVersion: 3.5
> >>>>> +Contact: Johan Hovold<[email protected]>
> >>>>> +Description:
> >>>>> + Get the current light zone (0..4) as defined by the
> >>>>> + in_illuminance0_threshY_{falling,rising} thresholds.
> >>>>> +
> >>>>> +What: /sys/bus/iio/devices/iio:deviceX/targetY_Z
> >>>>> +Date: April 2012
> >>>>> +KernelVersion: 3.5
> >>>>> +Contact: Johan Hovold<[email protected]>
> >>>>> +Description:
> >>>>> + Set the target brightness for ALS-mapper Y in light zone Z
> >>>>> + (0..255), where Y in 1..3 and Z in 0..4.
> >>>>
> >>>> What are the units of this?
> >>>
> >>> The datasheet reads "percent of the full-scale current" (actually depends
> >>> somewhat on whether the als is in linear or exponential mode). When the
> >>> leds or backlights are in PWM-mode (not the ALS necessarily), these
> >>> values are interpreted as a scale factor which is applied to the output
> >>> current determined by the PWM-signal.
> >>>
> >>> Either way it could indeed be considered a raw output current (which
> >>> could be manipulated later by various factors).
> >> Fine.... Theoretically if at all possible we'd want the conversion
> >> factors to get it to an actual current (in amps) to be available though.
> >> (tend to relax that if there are unknowable elements or they aren't
> >> specified by board file etc).
> >
> > Hmmm. I'm starting to get a feeling that we're over-doing this. The ALS
> > on the lm3533 isn't a general purpose sensor. It's simply a way to
> > control the leds and backlights of that device. So what you do is to
> > determine the full-scale current (max current at maximum brightness 0xff
> > in this case -- set in board file). Then the ALS input range is divided
> > in 5 zones, and for each zone you set a brightness as a percent of the
> > full-scale current. You relly don't care about amps (except for the
> > maximum determined by the setup).
> Then no need to provide scale etc.
Good.
> > The equation's for determining the current are available in the
> > datasheets however, but they depend on which mapping mode (linear or
> > exponential) and can also be effected by PWM-input duty cycle etc. For
> > this particular device, I really don't see the point in trying to
> > determine actual current in amps in all these settings.
> We can always add it later if anyone cares.
> >
> > Note also that the actual output current cannot be determined in the
> > ALS as the required factors are only set/known in the led/backlight
> > devices (mapping mode, pwm-mode).
> Could query it back if it was useful, but sounds like probably not.
> If we don't provide the information it can't be wrong...
Problem is that we would have three out_currentY_raw channels generating
up to five different output currents depending on the configuration of
the five controlled devices...
> I'd basically missunderstood where the division between the sensor and
> the led/backlight drivers lay. I think I'm happy now with where you
> have it.
Great.
> >
> >>>> Also arguably is it not the als that this is related to, but rather
> >>>> the light source?
> >>>
> >>> Well, it would be a raw output, mapping the measured LUX.
> >> Fair enough, though I wonder if we are stepping on led / backlight
> >> classes stuff with this.
> >
> > Keeping the target sets and the mapper terminology could still be an
> > option.
> >
> > ALS input mode is a special mode of the LEDs and backlights which
> > overrides the direct current control. It's indeed a special-purpose
> > device.
> Yup, I understand you now!
> >
> >>>> A quick datasheet browse says that these are current targets? If so I
> >>>> wonder if we can make that explicit... Could treat them as 3 output
> >>>> channels and have indexed values like we do for frequencies in dds
> >>>> devices (where external hardware is controlling them.
> >>>
> >>> I think I like this.
> >>>
> >>>> Hmm. lets see.
> >>>>
> >>>> out_currentX_currentY_raw
> >>>> (the double naming is a bit clunky but corresponds to frequency devices
> >>>> where we have
> >>>> out_altvoltageX_frequencyY_raw
> >>>>
> >>>> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
> >>>> as indexed values they can take 0,1,2,3,4
> >>>> out_currentX_raw is not read only and gives you the current for whichever
> >>>> zone the device is currently in.
> >>>
> >>> I take it you mean "out_currentX_raw is read only".
> >> yes. I do indeed. oops.
> >>>
> >>>> This may seem convoluted but I'd really rather have something generalizable
> >>>> for this if we possibly can. We'd still need documentation to say what is
> >>>> controlling these, but at least they'd fit within our more general abi.
> >>>>
> >>>> What do you think?
> >>>
> >>> I like it. From a user perspective it's mainly a change of names (and
> >>> indexes). But conceptually it's perhaps more clear: the als maps it's
> >>> input to an output current, which, just like a PWM-signal, could be used
> >>> as an input to the LEDs and backlights to determine their outputs.
> >>>
> >>> I'd have to modify the LED and backlight interfaces somewhat to reflect
> >>> the changed indexes and terminology (e.g. "output channel" rather
> >>> than "ALS mapper"). Something like:
> >>>
> >>> als_en -- enable als input mode (0,1)
> >>> als_channel -- which out_currentX to use as input (0..2) in
> >>> als input mode
> >> Not entirely sure I'm happy with this. Would rather it was done
> >> on a per channel basis, so in_illuminance0_ *
> >> From point of view of sensors I don't really care if it is an als or
> >> measuring something active (hence inherently not ambient!)
> >
> > Not sure I understood that. What is it you don't like about it? You
> > need to keep in mind what is actually there; three sets of target
> > values per zone of which one set is dedicated to the first backlight
> > device. That means, that the ALS mapper (or channel if we want to
> > use that terminology) needs to be set in the actual devices and not
> > the other way round. [ The als_en and als_channel attributes would
> > belong to the led/backlight devices. ]
> Ah. THat last bit in brackets is what I'd missed ;) That's fine with me
> then.
Suspected that. ;)
> > What did you mean by "per channel basis, so in_illuminance0_ *"?
> >
> > Again it's a special purpose device -- the lm3533 leds and backlights
> > are controlled in hw by the on-chip als interface. We can't just use any
> > generic iio device for this.
> sure. Had missunderstood completely.
> >
> >> Silly question but how is the out_current related to the input in als
> >> mode?
> >
> > 1. raw adc input is averaged
> > 2. mean adc input is mapped to zone using zone registers ("thresholds")
> > 3. zone is mapped to a percentage using ALS mapper registers, that is,
> > three sets of 8-bit values per zone. Which set is used can be set on
> > a per-device (led, backlight) basis
> > 4. the percentage is applied to the per-device full-scale current to
> > determine the actual output. How this mapping is done depends on if
> > linear or exponential mapping mode is set (also per-device).
> >
> All makes sense now. Thanks for the clarification.
>
> > [ And things can get even more complicated if the devices are in
> > PWM-mode, but this is roughly the full picture. ]
> >
> > And all of the above is done in hw.
> >
> > So, we're only using out_current channels because it maybe fits iio
> > better. For anyone familiar with the lm3533 this may even just confuse
> > things.
> I'm realy keen to do this primarily because we may well hit similar
> devices at a later stage and it's much nicer to generalize earlier
> than go through supporting the old method in parallel for years...
> What we have here is general enough to support a wide range of possible
> devices so lets go with it.
Ok.
> > There are only the three tables of values that maps a zone to an output
> > percentage (e.g. half brightness in zone 2).
>
> One last question. Are the als -> LEd /backlight mapping something a
> device user would ever change? If not the most general option would be
> to use the iio inkernel mapping interfaces to do the assignments. Gives
> you a clean easy way to allow reading back of current brightness in the
> led drivers etc...
Are you referring to the als_channel settings above (i.e., which output
current is used as device input is ALS mode)? I think that may be the
case. You could have two sets of brightness values and quickly be able
to switch from one to another (e.g., for PM reasons).
Wouldn't it be possible to both allow users to change this and to add
support for iio in-kernel mappings later if needed?
The mapping has the following constraints by the way:
backlight.0 -> out_current0
backlight.1 -> out_current1
led.[0-4] -> out_current[1-2]
So it would only be possible to change the mapping for the leds and
only to select between two channels.
Is there anything preventing the led driver acting as IIO consumer to
set up a map to both channels and then decide which to read from
depending on als_channel?
> >>> So to summarise, we get the following new sysfs-entries for the ALS
> >>> (where the first set replace targetX_Y):
> >>>
> >>> out_currentX_currentY_raw r/w, (0..255), X in 0..2, Y in 0..4
> >>> out_currentX_raw ro (0..255), X in 0..2
> >>>
> >>> Is there any support in core for the first set or should I simply
> >>> rename my target attributes?
> >> No support in the core yet for this sort of thing..
> >> Michael, any thoughts on this? In a sense it's very similar to
> >> out_altvoltageX_frequencyY_raw etc...
Core support could be added later as long as we get the naming right in
lm3533, right?
I'm really keen to get this one into 3.5 (along with the rest of the
MFD-driver) and I know Greg usually sends his merge requests early...
But if I understand you correctly, it should be possible to apply
lm3533-als v5 now?
Thanks,
Johan
On 5/22/2012 10:09 AM, Johan Hovold wrote:
> On Tue, May 22, 2012 at 08:13:12AM +0100, Jonathan Cameron wrote:
>> On 5/21/2012 11:07 PM, Johan Hovold wrote:
>>> On Mon, May 21, 2012 at 05:37:11PM +0100, Jonathan Cameron wrote:
>>>> Michael cc'd for comments on core support of some stuff that is also
>>>> in frequency drivers down the end of the email.
>>>>
>>>> On 05/21/2012 10:50 AM, Johan Hovold wrote:
>>>>> On Sat, May 19, 2012 at 09:48:23AM +0100, Jonathan Cameron wrote:
>>>>>> On 05/18/2012 02:07 PM, Johan Hovold wrote:
>>>>>>> Add sub-driver for the ambient-light-sensor interface on National
>>>>>>> Semiconductor / TI LM3533 lighting power chips.
>>>>>>>
>>>>>>> The sensor interface can be used to control the LEDs and backlights of
>>>>>>> the chip through defining five light zones and three sets of
>>>>>>> corresponding brightness target levels.
>>>>>>>
>>>>>>> The driver provides raw and mean adc readings along with the current
>>>>>>> light zone through sysfs. A threshold event can be generated on zone
>>>>>>> changes.
>>>>>>
>>>>>> Hi Johan,
>>>>>>
>>>>>> I hate to be a pain with this one, but it's a complex beast and I'd
>>>>>> really like to get the interface right first time - particularly as
>>>>>> you are going in after the move out of staging.
>>>>>>
>>>>>>
>>>>>> Queries for you.
>>>>>> 1) Ordering in the probe function. Normally expect iio_device_register
>>>>>> to be the last call. Why not here?
>>>>>> 2) Worth combining enable / disable into one as very similar functions?
>>>>>> 3) Suspicious code in als_set_input_mode
>>>>>>
>>>>>> Naming of the target values. I think we can make the naming of these
>>>>>> fit in much better with the normal abi which is going to be all for the
>>>>>> good in the long run. They are basically current output channels
>>>>>> with a controllable set of steps (where we don't have direct control
>>>>>> of which one we are in). This is very similar to the frequency controls
>>>>>> on some of Analog's dds that we have a well defined interface for.
>>>>>>
>>>>>> More detail below, but in summary.
>>>>>>
>>>>>> out_currentX_currentY_raw for channel X value for zone Y.
>>>>>>
>>>>>> Jonathan
>>>>>>>
>>>>>>> Signed-off-by: Johan Hovold<[email protected]>
>>>>>>> ---
>>>>>>>
>>>>>>> Note that addition of r_select to the platform data probably needs to go
>>>>>>> in via mfd.
>>>>>>>
>>>>>>>
>>>>>>> v2:
>>>>>>> - reimplement using iio
>>>>>>> - add sysfs-ABI documentation
>>>>>>> v3
>>>>>>> - use indexed channel
>>>>>>> - fix sysfs-ABI documentation typo and style
>>>>>>> - replace gain attribute with in_illuminance0_calibscale
>>>>>>> - add calibscale to platform data
>>>>>>> - fix adc register definitions
>>>>>>> - replace to_lm3533_dev_attr macro with inline function
>>>>>>> - fix device used for error reporting at irq allocation
>>>>>>> - use iio device for error reporting during setup/enable
>>>>>>> - rebase on staging-next
>>>>>>> - fix header include paths
>>>>>>> - use dev_to_iio_dev
>>>>>>> - add IIO_CHAN_INFO_RAW to info mask
>>>>>>> - use iio_device_{alloc,free}
>>>>>>> v4
>>>>>>> - move to driver/iio/light
>>>>>>> - add events/in_illuminance0_threshY_hysteresis attributes
>>>>>>> - fix device zone-boundary quirkiness
>>>>>>> - clean up attribute handling
>>>>>>> - replace calibscale with device-specific r_select attribute
>>>>>>>
>>>>>>>
>>>>>>> .../ABI/testing/sysfs-bus-iio-light-lm3533-als | 64 ++
>>>>>>> drivers/iio/Kconfig | 1 +
>>>>>>> drivers/iio/Makefile | 1 +
>>>>>>> drivers/iio/light/Kconfig | 22 +
>>>>>>> drivers/iio/light/Makefile | 5 +
>>>>>>> drivers/iio/light/lm3533-als.c | 941 ++++++++++++++++++++
>>>>>>> include/linux/mfd/lm3533.h | 1 +
>>>>>>> 7 files changed, 1035 insertions(+), 0 deletions(-)
>>>>>>> create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>>>>> create mode 100644 drivers/iio/light/Kconfig
>>>>>>> create mode 100644 drivers/iio/light/Makefile
>>>>>>> create mode 100644 drivers/iio/light/lm3533-als.c
>>>>>>>
>>>>>>> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>>>>> new file mode 100644
>>>>>>> index 0000000..7ea1770
>>>>>>> --- /dev/null
>>>>>>> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
>>>>>>> @@ -0,0 +1,64 @@
>>>>>>> +What: /sys/bus/iio/devices/iio:deviceX/r_select
>>>>>>> +Date: April 2012
>>>>>>> +KernelVersion: 3.5
>>>>>>> +Contact: Johan Hovold<[email protected]>
>>>>>>> +Description:
>>>>>>> + Set the ALS internal pull-down resistor for analog input mode
>>>>>>> + (1..127), such that,
>>>>>>> +
>>>>>>> + R_als = 200000 / r_select (ohm)
>>>>>>> +
>>>>>>> + This setting is ignored in PWM-mode (input is always high
>>>>>>> + impedance in PWM-mode).
>>>>>>> +
>>>>>>> +What: /sys/.../events/in_illuminance0_thresh_either_en
>>>>>>> +Date: April 2012
>>>>>>> +KernelVersion: 3.5
>>>>>>> +Contact: Johan Hovold<[email protected]>
>>>>>>> +Description:
>>>>>>> + Event generated when channel passes one of the four thresholds
>>>>>>> + in each direction (rising|falling) and a zone change occurs.
>>>>>>> + The corresponding light zone can be read from
>>>>>>> + in_illuminance0_zone.
>>>>>>> +
>>>>>>> +What: /sys/.../events/in_illuminance0_threshY_hysteresis
>>>>>>> +Date: May 2012
>>>>>>> +KernelVersion: 3.5
>>>>>>> +Contact: Johan Hovold<[email protected]>
>>>>>>> +Description:
>>>>>>> + Get the hysteresis for thresholds Y, that is,
>>>>>>> +
>>>>>>> + threshY_hysteresis = threshY_raising - threshY_falling
>>>>>>> +
>>>>>>> +What: /sys/.../events/illuminance_threshY_falling_value
>>>>>>> +What: /sys/.../events/illuminance_threshY_raising_value
>>>>>>> +Date: April 2012
>>>>>>> +KernelVersion: 3.5
>>>>>>> +Contact: Johan Hovold<[email protected]>
>>>>>>> +Description:
>>>>>>> + Specifies the value of threshold that the device is
>>>>>>> + comparing against for the events enabled by
>>>>>>> + in_illuminance0_thresh_either_en, where Y in 0..3.
>>>>>>> +
>>>>>>> + Note that threshY_falling must be less than or equal to
>>>>>>> + threshY_raising.
>>>>>>> +
>>>>>>> + These thresholds correspond to the eight zone-boundary
>>>>>>> + registers (boundaryY_{low,high}) and defines the five light
>>>>>>> + zones.
>>>>>>> +
>>>>>>> +What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
>>>>>>> +Date: April 2012
>>>>>>> +KernelVersion: 3.5
>>>>>>> +Contact: Johan Hovold<[email protected]>
>>>>>>> +Description:
>>>>>>> + Get the current light zone (0..4) as defined by the
>>>>>>> + in_illuminance0_threshY_{falling,rising} thresholds.
>>>>>>> +
>>>>>>> +What: /sys/bus/iio/devices/iio:deviceX/targetY_Z
>>>>>>> +Date: April 2012
>>>>>>> +KernelVersion: 3.5
>>>>>>> +Contact: Johan Hovold<[email protected]>
>>>>>>> +Description:
>>>>>>> + Set the target brightness for ALS-mapper Y in light zone Z
>>>>>>> + (0..255), where Y in 1..3 and Z in 0..4.
>>>>>>
>>>>>> What are the units of this?
>>>>>
>>>>> The datasheet reads "percent of the full-scale current" (actually depends
>>>>> somewhat on whether the als is in linear or exponential mode). When the
>>>>> leds or backlights are in PWM-mode (not the ALS necessarily), these
>>>>> values are interpreted as a scale factor which is applied to the output
>>>>> current determined by the PWM-signal.
>>>>>
>>>>> Either way it could indeed be considered a raw output current (which
>>>>> could be manipulated later by various factors).
>>>> Fine.... Theoretically if at all possible we'd want the conversion
>>>> factors to get it to an actual current (in amps) to be available though.
>>>> (tend to relax that if there are unknowable elements or they aren't
>>>> specified by board file etc).
>>>
>>> Hmmm. I'm starting to get a feeling that we're over-doing this. The ALS
>>> on the lm3533 isn't a general purpose sensor. It's simply a way to
>>> control the leds and backlights of that device. So what you do is to
>>> determine the full-scale current (max current at maximum brightness 0xff
>>> in this case -- set in board file). Then the ALS input range is divided
>>> in 5 zones, and for each zone you set a brightness as a percent of the
>>> full-scale current. You relly don't care about amps (except for the
>>> maximum determined by the setup).
>> Then no need to provide scale etc.
>
> Good.
>
>>> The equation's for determining the current are available in the
>>> datasheets however, but they depend on which mapping mode (linear or
>>> exponential) and can also be effected by PWM-input duty cycle etc. For
>>> this particular device, I really don't see the point in trying to
>>> determine actual current in amps in all these settings.
>> We can always add it later if anyone cares.
>>>
>>> Note also that the actual output current cannot be determined in the
>>> ALS as the required factors are only set/known in the led/backlight
>>> devices (mapping mode, pwm-mode).
>> Could query it back if it was useful, but sounds like probably not.
>> If we don't provide the information it can't be wrong...
>
> Problem is that we would have three out_currentY_raw channels generating
> up to five different output currents depending on the configuration of
> the five controlled devices...
Gah. So if we did do this, we'd have to define all 5. What a pain.
Lest just skip that for now then.
>
>> I'd basically missunderstood where the division between the sensor and
>> the led/backlight drivers lay. I think I'm happy now with where you
>> have it.
>
> Great.
>
>>>
>>>>>> Also arguably is it not the als that this is related to, but rather
>>>>>> the light source?
>>>>>
>>>>> Well, it would be a raw output, mapping the measured LUX.
>>>> Fair enough, though I wonder if we are stepping on led / backlight
>>>> classes stuff with this.
>>>
>>> Keeping the target sets and the mapper terminology could still be an
>>> option.
>>>
>>> ALS input mode is a special mode of the LEDs and backlights which
>>> overrides the direct current control. It's indeed a special-purpose
>>> device.
>> Yup, I understand you now!
>>>
>>>>>> A quick datasheet browse says that these are current targets? If so I
>>>>>> wonder if we can make that explicit... Could treat them as 3 output
>>>>>> channels and have indexed values like we do for frequencies in dds
>>>>>> devices (where external hardware is controlling them.
>>>>>
>>>>> I think I like this.
>>>>>
>>>>>> Hmm. lets see.
>>>>>>
>>>>>> out_currentX_currentY_raw
>>>>>> (the double naming is a bit clunky but corresponds to frequency devices
>>>>>> where we have
>>>>>> out_altvoltageX_frequencyY_raw
>>>>>>
>>>>>> Hence we'd treat you 3 mapers as indexed channels 0,1,2 and the zones
>>>>>> as indexed values they can take 0,1,2,3,4
>>>>>> out_currentX_raw is not read only and gives you the current for whichever
>>>>>> zone the device is currently in.
>>>>>
>>>>> I take it you mean "out_currentX_raw is read only".
>>>> yes. I do indeed. oops.
>>>>>
>>>>>> This may seem convoluted but I'd really rather have something generalizable
>>>>>> for this if we possibly can. We'd still need documentation to say what is
>>>>>> controlling these, but at least they'd fit within our more general abi.
>>>>>>
>>>>>> What do you think?
>>>>>
>>>>> I like it. From a user perspective it's mainly a change of names (and
>>>>> indexes). But conceptually it's perhaps more clear: the als maps it's
>>>>> input to an output current, which, just like a PWM-signal, could be used
>>>>> as an input to the LEDs and backlights to determine their outputs.
>>>>>
>>>>> I'd have to modify the LED and backlight interfaces somewhat to reflect
>>>>> the changed indexes and terminology (e.g. "output channel" rather
>>>>> than "ALS mapper"). Something like:
>>>>>
>>>>> als_en -- enable als input mode (0,1)
>>>>> als_channel -- which out_currentX to use as input (0..2) in
>>>>> als input mode
>>>> Not entirely sure I'm happy with this. Would rather it was done
>>>> on a per channel basis, so in_illuminance0_ *
>>>> From point of view of sensors I don't really care if it is an als or
>>>> measuring something active (hence inherently not ambient!)
>>>
>>> Not sure I understood that. What is it you don't like about it? You
>>> need to keep in mind what is actually there; three sets of target
>>> values per zone of which one set is dedicated to the first backlight
>>> device. That means, that the ALS mapper (or channel if we want to
>>> use that terminology) needs to be set in the actual devices and not
>>> the other way round. [ The als_en and als_channel attributes would
>>> belong to the led/backlight devices. ]
>> Ah. THat last bit in brackets is what I'd missed ;) That's fine with me
>> then.
>
> Suspected that. ;)
>
>>> What did you mean by "per channel basis, so in_illuminance0_ *"?
>>>
>>> Again it's a special purpose device -- the lm3533 leds and backlights
>>> are controlled in hw by the on-chip als interface. We can't just use any
>>> generic iio device for this.
>> sure. Had missunderstood completely.
>>>
>>>> Silly question but how is the out_current related to the input in als
>>>> mode?
>>>
>>> 1. raw adc input is averaged
>>> 2. mean adc input is mapped to zone using zone registers ("thresholds")
>>> 3. zone is mapped to a percentage using ALS mapper registers, that is,
>>> three sets of 8-bit values per zone. Which set is used can be set on
>>> a per-device (led, backlight) basis
>>> 4. the percentage is applied to the per-device full-scale current to
>>> determine the actual output. How this mapping is done depends on if
>>> linear or exponential mapping mode is set (also per-device).
>>>
>> All makes sense now. Thanks for the clarification.
>>
>>> [ And things can get even more complicated if the devices are in
>>> PWM-mode, but this is roughly the full picture. ]
>>>
>>> And all of the above is done in hw.
>>>
>>> So, we're only using out_current channels because it maybe fits iio
>>> better. For anyone familiar with the lm3533 this may even just confuse
>>> things.
>> I'm realy keen to do this primarily because we may well hit similar
>> devices at a later stage and it's much nicer to generalize earlier
>> than go through supporting the old method in parallel for years...
>> What we have here is general enough to support a wide range of possible
>> devices so lets go with it.
>
> Ok.
>
>>> There are only the three tables of values that maps a zone to an output
>>> percentage (e.g. half brightness in zone 2).
>>
>> One last question. Are the als -> LEd /backlight mapping something a
>> device user would ever change? If not the most general option would be
>> to use the iio inkernel mapping interfaces to do the assignments. Gives
>> you a clean easy way to allow reading back of current brightness in the
>> led drivers etc...
>
> Are you referring to the als_channel settings above (i.e., which output
> current is used as device input is ALS mode)? I think that may be the
> case. You could have two sets of brightness values and quickly be able
> to switch from one to another (e.g., for PM reasons).
>
> Wouldn't it be possible to both allow users to change this and to add
> support for iio in-kernel mappings later if needed?
In theory yes. No core support for dynamically messing with this as
such but not to hard to do.
>
> The mapping has the following constraints by the way:
>
> backlight.0 -> out_current0
> backlight.1 -> out_current1
> led.[0-4] -> out_current[1-2]
>
> So it would only be possible to change the mapping for the leds and
> only to select between two channels.
>
> Is there anything preventing the led driver acting as IIO consumer to
> set up a map to both channels and then decide which to read from
> depending on als_channel?
hmm. That mapping would need to be at the consumer side I think.
>
>>>>> So to summarise, we get the following new sysfs-entries for the ALS
>>>>> (where the first set replace targetX_Y):
>>>>>
>>>>> out_currentX_currentY_raw r/w, (0..255), X in 0..2, Y in 0..4
>>>>> out_currentX_raw ro (0..255), X in 0..2
>>>>>
>>>>> Is there any support in core for the first set or should I simply
>>>>> rename my target attributes?
>>>> No support in the core yet for this sort of thing..
>>>> Michael, any thoughts on this? In a sense it's very similar to
>>>> out_altvoltageX_frequencyY_raw etc...
>
> Core support could be added later as long as we get the naming right in
> lm3533, right?
>
> I'm really keen to get this one into 3.5 (along with the rest of the
> MFD-driver) and I know Greg usually sends his merge requests early...
> But if I understand you correctly, it should be possible to apply
> lm3533-als v5 now?
Err. I'll try and have a quick last look shortly.
Jonathan
>
> Thanks,
> Johan
On 5/21/2012 1:18 PM, Johan Hovold wrote:
> Add sub-driver for the ambient-light-sensor interface on National
> Semiconductor / TI LM3533 lighting power chips.
>
> The sensor interface can be used to control the LEDs and backlights of
> the chip through defining five light zones and three sets of
> corresponding output-current values.
>
> The driver provides raw and mean adc readings along with the current
> light zone through sysfs. A threshold event can be generated on zone
> changes. The ALS-control output values can be set per zone for the three
> current output channels.
There are a few bits of documentation I'd rather were in
sysfs-bus-iio but we can move them any time.
I haven't compile tested as no machine availability till this evening.
Please do one last sanity check against staging-next.
Thanks,
>
> Signed-off-by: Johan Hovold<[email protected]>
Acked-by: Jonathan Cameron <[email protected]>
> ---
>
> v2:
> - reimplement using iio
> - add sysfs-ABI documentation
> v3
> - use indexed channel
> - fix sysfs-ABI documentation typo and style
> - replace gain attribute with in_illuminance0_calibscale
> - add calibscale to platform data
> - fix adc register definitions
> - replace to_lm3533_dev_attr macro with inline function
> - fix device used for error reporting at irq allocation
> - use iio device for error reporting during setup/enable
> - rebase on staging-next
> - fix header include paths
> - use dev_to_iio_dev
> - add IIO_CHAN_INFO_RAW to info mask
> - use iio_device_{alloc,free}
> v4
> - move to driver/iio/light
> - add events/in_illuminance0_threshY_hysteresis attributes
> - fix device zone-boundary quirkiness
> - clean up attribute handling
> - replace calibscale with device-specific r_select attribute
> v5
> - drop r_select from sysfs ABI
> - fix set_input_mode
> - minor style changes
> - register iio device last at probe
> - use dev_name for iio_dev and irq name
> - add out_currentY channels
>
>
> .../ABI/testing/sysfs-bus-iio-light-lm3533-als | 62 ++
> drivers/iio/Kconfig | 1 +
> drivers/iio/Makefile | 1 +
> drivers/iio/light/Kconfig | 22 +
> drivers/iio/light/Makefile | 5 +
> drivers/iio/light/lm3533-als.c | 932 ++++++++++++++++++++
> 6 files changed, 1023 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> create mode 100644 drivers/iio/light/Kconfig
> create mode 100644 drivers/iio/light/Makefile
> create mode 100644 drivers/iio/light/lm3533-als.c
>
> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> new file mode 100644
> index 0000000..694a52c
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> @@ -0,0 +1,62 @@
> +What: /sys/.../events/in_illuminance0_thresh_either_en
> +Date: April 2012
> +KernelVersion: 3.5
> +Contact: Johan Hovold<[email protected]>
> +Description:
> + Event generated when channel passes one of the four thresholds
> + in each direction (rising|falling) and a zone change occurs.
> + The corresponding light zone can be read from
> + in_illuminance0_zone.
> +
> +What: /sys/.../events/in_illuminance0_threshY_hysteresis
> +Date: May 2012
> +KernelVersion: 3.5
> +Contact: Johan Hovold<[email protected]>
> +Description:
> + Get the hysteresis for thresholds Y, that is,
> +
> + threshY_hysteresis = threshY_raising - threshY_falling
> +
> +What: /sys/.../events/illuminance_threshY_falling_value
> +What: /sys/.../events/illuminance_threshY_raising_value
> +Date: April 2012
> +KernelVersion: 3.5
> +Contact: Johan Hovold<[email protected]>
> +Description:
> + Specifies the value of threshold that the device is comparing
> + against for the events enabled by
> + in_illuminance0_thresh_either_en (0..255), where Y in 0..3.
> +
> + Note that threshY_falling must be less than or equal to
> + threshY_raising.
> +
> + These thresholds correspond to the eight zone-boundary
> + registers (boundaryY_{low,high}) and defines the five light
> + zones.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> +Date: April 2012
> +KernelVersion: 3.5
> +Contact: Johan Hovold<[email protected]>
> +Description:
> + Get the current light zone (0..4) as defined by the
> + in_illuminance0_threshY_{falling,rising} thresholds.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_currentY_raw
> +Date: May 2012
> +KernelVersion: 3.5
> +Contact: Johan Hovold<[email protected]>
> +Description:
> + Get output current for channel Y (0..255), that is,
> + out_currentY_currentZ_raw, where Z is the current zone.
Should be in core docs. Can fix that later though.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/out_currentY_currentZ_raw
> +Date: May 2012
> +KernelVersion: 3.5
> +Contact: Johan Hovold<[email protected]>
> +Description:
> + Set the output current for channel out_currentY when in zone
> + Z (0..255), where Y in 0..2 and Z in 0..4.
> +
> + These values correspond to the ALS-mapper target registers for
> + ALS-mapper Y + 1.
Hmm. tempted to move this to core docs too.
> diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
> index 56eecef..cacc74d 100644
> --- a/drivers/iio/Kconfig
> +++ b/drivers/iio/Kconfig
> @@ -50,5 +50,6 @@ config IIO_CONSUMERS_PER_TRIGGER
>
> source "drivers/iio/adc/Kconfig"
> source "drivers/iio/amplifiers/Kconfig"
> +source "drivers/iio/light/Kconfig"
>
> endif # IIO
> diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
> index e425afd..060b674 100644
> --- a/drivers/iio/Makefile
> +++ b/drivers/iio/Makefile
> @@ -11,3 +11,4 @@ obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o
>
> obj-y += adc/
> obj-y += amplifiers/
> +obj-y += light/
> diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> new file mode 100644
> index 0000000..db5618e
> --- /dev/null
> +++ b/drivers/iio/light/Kconfig
> @@ -0,0 +1,22 @@
> +#
> +# Light sensors
> +#
> +menu "Light sensors"
> +
> +config SENSORS_LM3533
> + tristate "LM3533 ambient light sensor"
> + depends on MFD_LM3533
> + help
> + If you say yes here you get support for the ambient light sensor
> + interface on National Semiconductor / TI LM3533 Lighting Power
> + chips.
> +
> + The sensor interface can be used to control the LEDs and backlights
> + of the chip through defining five light zones and three sets of
> + corresponding output-current values.
> +
> + The driver provides raw and mean adc readings along with the current
> + light zone through sysfs. A threshold event can be generated on zone
> + changes. The ALS-control output values can be set per zone for the
> + three current output channels.
> +endmenu
> diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
> new file mode 100644
> index 0000000..c1c23a0
> --- /dev/null
> +++ b/drivers/iio/light/Makefile
> @@ -0,0 +1,5 @@
> +#
> +# Makefile for IIO Light sensors
> +#
> +
> +obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o
> diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
> new file mode 100644
> index 0000000..c3e7bac
> --- /dev/null
> +++ b/drivers/iio/light/lm3533-als.c
> @@ -0,0 +1,932 @@
> +/*
> + * lm3533-als.c -- LM3533 Ambient Light Sensor driver
> + *
> + * Copyright (C) 2011-2012 Texas Instruments
> + *
> + * Author: Johan Hovold<[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; either version 2 of the License, or (at your
> + * option) any later version.
> + */
> +
> +#include<linux/atomic.h>
> +#include<linux/fs.h>
> +#include<linux/interrupt.h>
> +#include<linux/io.h>
> +#include<linux/iio/events.h>
> +#include<linux/iio/iio.h>
> +#include<linux/module.h>
> +#include<linux/mutex.h>
> +#include<linux/mfd/core.h>
> +#include<linux/platform_device.h>
> +#include<linux/slab.h>
> +#include<linux/uaccess.h>
> +
> +#include<linux/mfd/lm3533.h>
> +
> +
> +#define LM3533_ALS_RESISTOR_MIN 1
> +#define LM3533_ALS_RESISTOR_MAX 127
> +#define LM3533_ALS_CHANNEL_CURRENT_MAX 2
> +#define LM3533_ALS_THRESH_MAX 3
> +#define LM3533_ALS_ZONE_MAX 4
> +
> +#define LM3533_REG_ALS_RESISTOR_SELECT 0x30
> +#define LM3533_REG_ALS_CONF 0x31
> +#define LM3533_REG_ALS_ZONE_INFO 0x34
> +#define LM3533_REG_ALS_READ_ADC_RAW 0x37
> +#define LM3533_REG_ALS_READ_ADC_AVERAGE 0x38
> +#define LM3533_REG_ALS_BOUNDARY_BASE 0x50
> +#define LM3533_REG_ALS_TARGET_BASE 0x60
> +
> +#define LM3533_ALS_ENABLE_MASK 0x01
> +#define LM3533_ALS_INPUT_MODE_MASK 0x02
> +#define LM3533_ALS_INT_ENABLE_MASK 0x01
> +
> +#define LM3533_ALS_ZONE_SHIFT 2
> +#define LM3533_ALS_ZONE_MASK 0x1c
> +
> +#define LM3533_ALS_FLAG_INT_ENABLED 1
> +
> +
> +struct lm3533_als {
> + struct lm3533 *lm3533;
> + struct platform_device *pdev;
> +
> + unsigned long flags;
> + int irq;
> +
> + atomic_t zone;
> + struct mutex thresh_mutex;
> +};
> +
> +
> +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
> + int *adc)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 reg;
> + u8 val;
> + int ret;
> +
> + if (average)
> + reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
> + else
> + reg = LM3533_REG_ALS_READ_ADC_RAW;
> +
> + ret = lm3533_read(als->lm3533, reg,&val);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to read adc\n");
> + return ret;
> + }
> +
> + *adc = val;
> +
> + return 0;
> +}
> +
> +static int _lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 val;
> + int ret;
> +
> + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO,&val);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to read zone\n");
> + return ret;
> + }
> +
> + val = (val& LM3533_ALS_ZONE_MASK)>> LM3533_ALS_ZONE_SHIFT;
> + *zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
> +
> + return 0;
> +}
> +
> +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + int ret;
> +
> + if (test_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags)) {
> + *zone = atomic_read(&als->zone);
> + } else {
> + ret = _lm3533_als_get_zone(indio_dev, zone);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * channel output channel 0..2
> + * zone zone 0..4
> + */
> +static inline u8 lm3533_als_get_target_reg(unsigned channel, unsigned zone)
> +{
> + return LM3533_REG_ALS_TARGET_BASE + 5 * channel + zone;
> +}
> +
> +static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned channel,
> + unsigned zone, u8 *val)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 reg;
> + int ret;
> +
> + if (channel> LM3533_ALS_CHANNEL_CURRENT_MAX)
> + return -EINVAL;
> +
> + if (zone> LM3533_ALS_ZONE_MAX)
> + return -EINVAL;
> +
> + reg = lm3533_als_get_target_reg(channel, zone);
> + ret = lm3533_read(als->lm3533, reg, val);
> + if (ret)
> + dev_err(&indio_dev->dev, "failed to get target current\n");
> +
> + return ret;
> +}
> +
> +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned channel,
> + unsigned zone, u8 val)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 reg;
> + int ret;
> +
> + if (channel> LM3533_ALS_CHANNEL_CURRENT_MAX)
> + return -EINVAL;
> +
> + if (zone> LM3533_ALS_ZONE_MAX)
> + return -EINVAL;
> +
> + reg = lm3533_als_get_target_reg(channel, zone);
> + ret = lm3533_write(als->lm3533, reg, val);
> + if (ret)
> + dev_err(&indio_dev->dev, "failed to set target current\n");
> +
> + return ret;
> +}
> +
> +static int lm3533_als_get_current(struct iio_dev *indio_dev, unsigned channel,
> + int *val)
> +{
> + u8 zone;
> + u8 target;
> + int ret;
> +
> + ret = lm3533_als_get_zone(indio_dev,&zone);
> + if (ret)
> + return ret;
> +
> + ret = lm3533_als_get_target(indio_dev, channel, zone,&target);
> + if (ret)
> + return ret;
> +
> + *val = target;
> +
> + return 0;
> +}
> +
> +static int lm3533_als_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val, int *val2, long mask)
> +{
> + int ret;
> +
> + switch (mask) {
> + case 0:
> + switch (chan->type) {
> + case IIO_LIGHT:
> + ret = lm3533_als_get_adc(indio_dev, false, val);
> + break;
> + case IIO_CURRENT:
> + ret = lm3533_als_get_current(indio_dev, chan->channel,
> + val);
> + break;
> + default:
> + return -EINVAL;
> + }
> + break;
> + case IIO_CHAN_INFO_AVERAGE_RAW:
> + ret = lm3533_als_get_adc(indio_dev, true, val);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (ret)
> + return ret;
> +
> + return IIO_VAL_INT;
> +}
> +
> +#define CHANNEL_CURRENT(_channel) \
> + { \
> + .type = IIO_CURRENT, \
> + .channel = _channel, \
> + .indexed = true, \
> + .output = true, \
> + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT, \
> + }
> +
> +static const struct iio_chan_spec lm3533_als_channels[] = {
> + {
> + .type = IIO_LIGHT,
> + .channel = 0,
> + .indexed = true,
> + .info_mask = (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
> + IIO_CHAN_INFO_RAW_SEPARATE_BIT),
> + },
> + CHANNEL_CURRENT(0),
> + CHANNEL_CURRENT(1),
> + CHANNEL_CURRENT(2),
> +};
> +
> +static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
> +{
> +
> + struct iio_dev *indio_dev = dev_id;
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 zone;
> + int ret;
> +
> + /* Clear interrupt by reading the ALS zone register. */
> + ret = _lm3533_als_get_zone(indio_dev,&zone);
> + if (ret)
> + goto out;
> +
> + atomic_set(&als->zone, zone);
> +
> + iio_push_event(indio_dev,
> + IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
> + 0,
> + IIO_EV_TYPE_THRESH,
> + IIO_EV_DIR_EITHER),
> + iio_get_time_ns());
> +out:
> + return IRQ_HANDLED;
> +}
> +
> +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> + u8 val;
> + int ret;
> +
> + if (enable)
> + val = mask;
> + else
> + val = 0;
> +
> + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to set int mode %d\n",
> + enable);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> + u8 val;
> + int ret;
> +
> + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO,&val);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to get int mode\n");
> + return ret;
> + }
> +
> + *enable = !!(val& mask);
> +
> + return 0;
> +}
> +
> +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
> +{
> + u8 offset = !raising;
> +
> + return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
> +}
> +
> +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
> + bool raising, u8 *val)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 reg;
> + int ret;
> +
> + if (nr> LM3533_ALS_THRESH_MAX)
> + return -EINVAL;
> +
> + reg = lm3533_als_get_threshold_reg(nr, raising);
> + ret = lm3533_read(als->lm3533, reg, val);
> + if (ret)
> + dev_err(&indio_dev->dev, "failed to get threshold\n");
> +
> + return ret;
> +}
> +
> +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
> + bool raising, u8 val)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 val2;
> + u8 reg, reg2;
> + int ret;
> +
> + if (nr> LM3533_ALS_THRESH_MAX)
> + return -EINVAL;
> +
> + reg = lm3533_als_get_threshold_reg(nr, raising);
> + reg2 = lm3533_als_get_threshold_reg(nr, !raising);
> +
> + mutex_lock(&als->thresh_mutex);
> + ret = lm3533_read(als->lm3533, reg2,&val2);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to get threshold\n");
> + goto out;
> + }
> + /*
> + * This device does not allow negative hysteresis (in fact, it uses
> + * whichever value is smaller as the lower bound) so we need to make
> + * sure that thresh_falling<= thresh_raising.
> + */
> + if ((raising&& (val< val2)) || (!raising&& (val> val2))) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + ret = lm3533_write(als->lm3533, reg, val);
> + if (ret) {
> + dev_err(&indio_dev->dev, "failed to set threshold\n");
> + goto out;
> + }
> +out:
> + mutex_unlock(&als->thresh_mutex);
> +
> + return ret;
> +}
> +
> +static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr,
> + u8 *val)
> +{
> + struct lm3533_als *als = iio_priv(indio_dev);
> + u8 falling;
> + u8 raising;
> + int ret;
> +
> + if (nr> LM3533_ALS_THRESH_MAX)
> + return -EINVAL;
> +
> + mutex_lock(&als->thresh_mutex);
> + ret = lm3533_als_get_threshold(indio_dev, nr, false,&falling);
> + if (ret)
> + goto out;
> + ret = lm3533_als_get_threshold(indio_dev, nr, true,&raising);
> + if (ret)
> + goto out;
> +
> + *val = raising - falling;
> +out:
> + mutex_unlock(&als->thresh_mutex);
> +
> + return ret;
> +}
> +
> +static int show_thresh_either_en(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct lm3533_als *als = iio_priv(indio_dev);
> + int enable;
> + int ret;
> +
> + if (als->irq) {
> + ret = lm3533_als_get_int_mode(indio_dev,&enable);
> + if (ret)
> + return ret;
> + } else {
> + enable = 0;
> + }
> +
> + return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
> +}
> +
> +static int store_thresh_either_en(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct lm3533_als *als = iio_priv(indio_dev);
> + unsigned long enable;
> + bool int_enabled;
> + u8 zone;
> + int ret;
> +
> + if (!als->irq)
> + return -EBUSY;
> +
> + if (kstrtoul(buf, 0,&enable))
> + return -EINVAL;
> +
> + int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +
> + if (enable&& !int_enabled) {
> + ret = lm3533_als_get_zone(indio_dev,&zone);
> + if (ret)
> + return ret;
> +
> + atomic_set(&als->zone, zone);
> +
> + set_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> + }
> +
> + ret = lm3533_als_set_int_mode(indio_dev, enable);
> + if (ret) {
> + if (!int_enabled)
> + clear_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +
> + return ret;
> + }
> +
> + if (!enable)
> + clear_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> +
> + return len;
> +}
> +
> +static ssize_t show_zone(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + u8 zone;
> + int ret;
> +
> + ret = lm3533_als_get_zone(indio_dev,&zone);
> + if (ret)
> + return ret;
> +
> + return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
> +}
> +
> +enum lm3533_als_attribute_type {
> + LM3533_ATTR_TYPE_HYSTERESIS,
> + LM3533_ATTR_TYPE_TARGET,
> + LM3533_ATTR_TYPE_THRESH_FALLING,
> + LM3533_ATTR_TYPE_THRESH_RAISING,
> +};
> +
> +struct lm3533_als_attribute {
> + struct device_attribute dev_attr;
> + enum lm3533_als_attribute_type type;
> + u8 val1;
> + u8 val2;
> +};
> +
> +static inline struct lm3533_als_attribute *
> +to_lm3533_als_attr(struct device_attribute *attr)
> +{
> + return container_of(attr, struct lm3533_als_attribute, dev_attr);
> +}
> +
> +static ssize_t show_als_attr(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> + u8 val;
> + int ret;
> +
> + switch (als_attr->type) {
> + case LM3533_ATTR_TYPE_HYSTERESIS:
> + ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1,
> + &val);
> + break;
> + case LM3533_ATTR_TYPE_TARGET:
> + ret = lm3533_als_get_target(indio_dev, als_attr->val1,
> + als_attr->val2,&val);
> + break;
> + case LM3533_ATTR_TYPE_THRESH_FALLING:
> + ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
> + false,&val);
> + break;
> + case LM3533_ATTR_TYPE_THRESH_RAISING:
> + ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
> + true,&val);
> + break;
> + default:
> + ret = -ENXIO;
> + }
> +
> + if (ret)
> + return ret;
> +
> + return scnprintf(buf, PAGE_SIZE, "%u\n", val);
> +}
> +
> +static ssize_t store_als_attr(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> + u8 val;
> + int ret;
> +
> + if (kstrtou8(buf, 0,&val))
> + return -EINVAL;
> +
> + switch (als_attr->type) {
> + case LM3533_ATTR_TYPE_TARGET:
> + ret = lm3533_als_set_target(indio_dev, als_attr->val1,
> + als_attr->val2, val);
> + break;
> + case LM3533_ATTR_TYPE_THRESH_FALLING:
> + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> + false, val);
> + break;
> + case LM3533_ATTR_TYPE_THRESH_RAISING:
> + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> + true, val);
> + break;
> + default:
> + ret = -ENXIO;
> + }
> +
> + if (ret)
> + return ret;
> +
> + return len;
> +}
> +
> +#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
> + { .dev_attr = __ATTR(_name, _mode, _show, _store), \
> + .type = _type, \
> + .val1 = _val1, \
> + .val2 = _val2 }
> +
> +#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
> + struct lm3533_als_attribute lm3533_als_attr_##_name = \
> + ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)
> +
> +#define ALS_TARGET_ATTR_RW(_channel, _zone) \
> + LM3533_ALS_ATTR(out_current##_channel##_current##_zone##_raw, \
> + S_IRUGO | S_IWUSR, \
> + show_als_attr, store_als_attr, \
> + LM3533_ATTR_TYPE_TARGET, _channel, _zone)
> +/*
> + * ALS output current values (ALS mapper targets)
> + *
> + * out_current[0-2]_current[0-4]_raw 0-255
> + */
> +static ALS_TARGET_ATTR_RW(0, 0);
> +static ALS_TARGET_ATTR_RW(0, 1);
> +static ALS_TARGET_ATTR_RW(0, 2);
> +static ALS_TARGET_ATTR_RW(0, 3);
> +static ALS_TARGET_ATTR_RW(0, 4);
> +
> +static ALS_TARGET_ATTR_RW(1, 0);
> +static ALS_TARGET_ATTR_RW(1, 1);
> +static ALS_TARGET_ATTR_RW(1, 2);
> +static ALS_TARGET_ATTR_RW(1, 3);
> +static ALS_TARGET_ATTR_RW(1, 4);
> +
> +static ALS_TARGET_ATTR_RW(2, 0);
> +static ALS_TARGET_ATTR_RW(2, 1);
> +static ALS_TARGET_ATTR_RW(2, 2);
> +static ALS_TARGET_ATTR_RW(2, 3);
> +static ALS_TARGET_ATTR_RW(2, 4);
> +
> +#define ALS_THRESH_FALLING_ATTR_RW(_nr) \
> + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value, \
> + S_IRUGO | S_IWUSR, \
> + show_als_attr, store_als_attr, \
> + LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0)
> +
> +#define ALS_THRESH_RAISING_ATTR_RW(_nr) \
> + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value, \
> + S_IRUGO | S_IWUSR, \
> + show_als_attr, store_als_attr, \
> + LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0)
> +/*
> + * ALS Zone thresholds (boundaries)
> + *
> + * in_illuminance0_thresh[0-3]_falling_value 0-255
> + * in_illuminance0_thresh[0-3]_raising_value 0-255
> + */
> +static ALS_THRESH_FALLING_ATTR_RW(0);
> +static ALS_THRESH_FALLING_ATTR_RW(1);
> +static ALS_THRESH_FALLING_ATTR_RW(2);
> +static ALS_THRESH_FALLING_ATTR_RW(3);
> +
> +static ALS_THRESH_RAISING_ATTR_RW(0);
> +static ALS_THRESH_RAISING_ATTR_RW(1);
> +static ALS_THRESH_RAISING_ATTR_RW(2);
> +static ALS_THRESH_RAISING_ATTR_RW(3);
> +
> +#define ALS_HYSTERESIS_ATTR_RO(_nr) \
> + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis, \
> + S_IRUGO, show_als_attr, NULL, \
> + LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0)
> +/*
> + * ALS Zone threshold hysteresis
> + *
> + * threshY_hysteresis = threshY_raising - threshY_falling
> + *
> + * in_illuminance0_thresh[0-3]_hysteresis 0-255
> + * in_illuminance0_thresh[0-3]_hysteresis 0-255
> + */
> +static ALS_HYSTERESIS_ATTR_RO(0);
> +static ALS_HYSTERESIS_ATTR_RO(1);
> +static ALS_HYSTERESIS_ATTR_RO(2);
> +static ALS_HYSTERESIS_ATTR_RO(3);
> +
> +#define ILLUMINANCE_ATTR_RO(_name) \
> + DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
> +#define ILLUMINANCE_ATTR_RW(_name) \
> + DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
> + show_##_name, store_##_name)
> +/*
> + * ALS Zone threshold-event enable
> + *
> + * in_illuminance0_thresh_either_en 0,1
> + */
> +static ILLUMINANCE_ATTR_RW(thresh_either_en);
> +
> +/*
> + * ALS Current Zone
> + *
> + * in_illuminance0_zone 0-4
> + */
> +static ILLUMINANCE_ATTR_RO(zone);
> +
> +static struct attribute *lm3533_als_event_attributes[] = {
> + &dev_attr_in_illuminance0_thresh_either_en.attr,
> + &lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
> + &lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
> + NULL
> +};
> +
> +static struct attribute_group lm3533_als_event_attribute_group = {
> + .attrs = lm3533_als_event_attributes
> +};
> +
> +static struct attribute *lm3533_als_attributes[] = {
> + &dev_attr_in_illuminance0_zone.attr,
> + &lm3533_als_attr_out_current0_current0_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current0_current1_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current0_current2_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current0_current3_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current0_current4_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current1_current0_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current1_current1_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current1_current2_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current1_current3_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current1_current4_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current2_current0_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current2_current1_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current2_current2_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current2_current3_raw.dev_attr.attr,
> + &lm3533_als_attr_out_current2_current4_raw.dev_attr.attr,
> + NULL
> +};
> +
> +static struct attribute_group lm3533_als_attribute_group = {
> + .attrs = lm3533_als_attributes
> +};
> +
> +static int __devinit lm3533_als_set_input_mode(struct lm3533_als *als,
> + bool pwm_mode)
> +{
> + u8 mask = LM3533_ALS_INPUT_MODE_MASK;
> + u8 val;
> + int ret;
> +
> + if (pwm_mode)
> + val = mask; /* pwm input */
> + else
> + val = 0; /* analog input */
> +
> + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, val, mask);
> + if (ret) {
> + dev_err(&als->pdev->dev, "failed to set input mode %d\n",
> + pwm_mode);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int __devinit lm3533_als_set_resistor(struct lm3533_als *als, u8 val)
> +{
> + int ret;
> +
> + if (val< LM3533_ALS_RESISTOR_MIN || val> LM3533_ALS_RESISTOR_MAX)
> + return -EINVAL;
> +
> + ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
> + if (ret) {
> + dev_err(&als->pdev->dev, "failed to set resistor\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int __devinit lm3533_als_setup(struct lm3533_als *als,
> + struct lm3533_als_platform_data *pdata)
> +{
> + int ret;
> +
> + ret = lm3533_als_set_input_mode(als, pdata->pwm_mode);
> + if (ret)
> + return ret;
> +
> + /* ALS input is always high impedance in PWM-mode. */
> + if (!pdata->pwm_mode) {
> + ret = lm3533_als_set_resistor(als, pdata->r_select);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int __devinit lm3533_als_setup_irq(struct lm3533_als *als, void *dev)
> +{
> + u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> + int ret;
> +
> + /* Make sure interrupts are disabled. */
> + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, 0, mask);
> + if (ret) {
> + dev_err(&als->pdev->dev, "failed to disable interrupts\n");
> + return ret;
> + }
> +
> + ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
> + IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> + dev_name(&als->pdev->dev), dev);
> + if (ret) {
> + dev_err(&als->pdev->dev, "failed to request irq %d\n",
> + als->irq);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int __devinit lm3533_als_enable(struct lm3533_als *als)
> +{
> + u8 mask = LM3533_ALS_ENABLE_MASK;
> + int ret;
> +
> + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
> + if (ret)
> + dev_err(&als->pdev->dev, "failed to enable ALS\n");
> +
> + return ret;
> +}
> +
> +static int lm3533_als_disable(struct lm3533_als *als)
> +{
> + u8 mask = LM3533_ALS_ENABLE_MASK;
> + int ret;
> +
> + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
> + if (ret)
> + dev_err(&als->pdev->dev, "failed to disable ALS\n");
> +
> + return ret;
> +}
> +
> +static const struct iio_info lm3533_als_info = {
> + .attrs =&lm3533_als_attribute_group,
> + .event_attrs =&lm3533_als_event_attribute_group,
> + .driver_module = THIS_MODULE,
> + .read_raw =&lm3533_als_read_raw,
> +};
> +
> +static int __devinit lm3533_als_probe(struct platform_device *pdev)
> +{
> + struct lm3533 *lm3533;
> + struct lm3533_als_platform_data *pdata;
> + struct lm3533_als *als;
> + struct iio_dev *indio_dev;
> + int ret;
> +
> + lm3533 = dev_get_drvdata(pdev->dev.parent);
> + if (!lm3533)
> + return -EINVAL;
> +
> + pdata = pdev->dev.platform_data;
> + if (!pdata) {
> + dev_err(&pdev->dev, "no platform data\n");
> + return -EINVAL;
> + }
> +
> + indio_dev = iio_device_alloc(sizeof(*als));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + indio_dev->info =&lm3533_als_info;
> + indio_dev->channels = lm3533_als_channels;
> + indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
> + indio_dev->name = dev_name(&pdev->dev);
> + indio_dev->dev.parent = pdev->dev.parent;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> +
> + als = iio_priv(indio_dev);
> + als->lm3533 = lm3533;
> + als->pdev = pdev;
> + als->irq = lm3533->irq;
> + atomic_set(&als->zone, 0);
> + mutex_init(&als->thresh_mutex);
> +
> + platform_set_drvdata(pdev, indio_dev);
> +
> + if (als->irq) {
> + ret = lm3533_als_setup_irq(als, indio_dev);
> + if (ret)
> + goto err_free_dev;
> + }
> +
> + ret = lm3533_als_setup(als, pdata);
> + if (ret)
> + goto err_free_irq;
> +
> + ret = lm3533_als_enable(als);
> + if (ret)
> + goto err_free_irq;
> +
> + ret = iio_device_register(indio_dev);
> + if (ret) {
> + dev_err(&pdev->dev, "failed to register ALS\n");
> + goto err_disable;
> + }
> +
> + return 0;
> +
> +err_disable:
> + lm3533_als_disable(als);
> +err_free_irq:
> + if (als->irq)
> + free_irq(als->irq, indio_dev);
> +err_free_dev:
> + iio_device_free(indio_dev);
> +
> + return ret;
> +}
> +
> +static int __devexit lm3533_als_remove(struct platform_device *pdev)
> +{
> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> + struct lm3533_als *als = iio_priv(indio_dev);
> +
> + lm3533_als_set_int_mode(indio_dev, false);
> + iio_device_unregister(indio_dev);
> + lm3533_als_disable(als);
> + if (als->irq)
> + free_irq(als->irq, indio_dev);
> + iio_device_free(indio_dev);
> +
> + return 0;
> +}
> +
> +static struct platform_driver lm3533_als_driver = {
> + .driver = {
> + .name = "lm3533-als",
> + .owner = THIS_MODULE,
> + },
> + .probe = lm3533_als_probe,
> + .remove = __devexit_p(lm3533_als_remove),
> +};
> +module_platform_driver(lm3533_als_driver);
> +
> +MODULE_AUTHOR("Johan Hovold<[email protected]>");
> +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:lm3533-als");
On Tue, May 22, 2012 at 10:19:15AM +0100, Jonathan Cameron wrote:
> On 5/21/2012 1:18 PM, Johan Hovold wrote:
> > Add sub-driver for the ambient-light-sensor interface on National
> > Semiconductor / TI LM3533 lighting power chips.
> >
> > The sensor interface can be used to control the LEDs and backlights of
> > the chip through defining five light zones and three sets of
> > corresponding output-current values.
> >
> > The driver provides raw and mean adc readings along with the current
> > light zone through sysfs. A threshold event can be generated on zone
> > changes. The ALS-control output values can be set per zone for the three
> > current output channels.
> There are a few bits of documentation I'd rather were in
> sysfs-bus-iio but we can move them any time.
>
> I haven't compile tested as no machine availability till this evening.
> Please do one last sanity check against staging-next.
Tested against staging-next of today (c3c6cc91b0ae7b).
Greg, any chance you can pick this one up for 3.5?
Thanks,
Johan
>
> Thanks,
> >
> > Signed-off-by: Johan Hovold<[email protected]>
> Acked-by: Jonathan Cameron <[email protected]>
> > ---
> >
> > v2:
> > - reimplement using iio
> > - add sysfs-ABI documentation
> > v3
> > - use indexed channel
> > - fix sysfs-ABI documentation typo and style
> > - replace gain attribute with in_illuminance0_calibscale
> > - add calibscale to platform data
> > - fix adc register definitions
> > - replace to_lm3533_dev_attr macro with inline function
> > - fix device used for error reporting at irq allocation
> > - use iio device for error reporting during setup/enable
> > - rebase on staging-next
> > - fix header include paths
> > - use dev_to_iio_dev
> > - add IIO_CHAN_INFO_RAW to info mask
> > - use iio_device_{alloc,free}
> > v4
> > - move to driver/iio/light
> > - add events/in_illuminance0_threshY_hysteresis attributes
> > - fix device zone-boundary quirkiness
> > - clean up attribute handling
> > - replace calibscale with device-specific r_select attribute
> > v5
> > - drop r_select from sysfs ABI
> > - fix set_input_mode
> > - minor style changes
> > - register iio device last at probe
> > - use dev_name for iio_dev and irq name
> > - add out_currentY channels
> >
> >
> > .../ABI/testing/sysfs-bus-iio-light-lm3533-als | 62 ++
> > drivers/iio/Kconfig | 1 +
> > drivers/iio/Makefile | 1 +
> > drivers/iio/light/Kconfig | 22 +
> > drivers/iio/light/Makefile | 5 +
> > drivers/iio/light/lm3533-als.c | 932 ++++++++++++++++++++
> > 6 files changed, 1023 insertions(+), 0 deletions(-)
> > create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > create mode 100644 drivers/iio/light/Kconfig
> > create mode 100644 drivers/iio/light/Makefile
> > create mode 100644 drivers/iio/light/lm3533-als.c
> >
> > diff --git a/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > new file mode 100644
> > index 0000000..694a52c
> > --- /dev/null
> > +++ b/Documentation/ABI/testing/sysfs-bus-iio-light-lm3533-als
> > @@ -0,0 +1,62 @@
> > +What: /sys/.../events/in_illuminance0_thresh_either_en
> > +Date: April 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold<[email protected]>
> > +Description:
> > + Event generated when channel passes one of the four thresholds
> > + in each direction (rising|falling) and a zone change occurs.
> > + The corresponding light zone can be read from
> > + in_illuminance0_zone.
> > +
> > +What: /sys/.../events/in_illuminance0_threshY_hysteresis
> > +Date: May 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold<[email protected]>
> > +Description:
> > + Get the hysteresis for thresholds Y, that is,
> > +
> > + threshY_hysteresis = threshY_raising - threshY_falling
> > +
> > +What: /sys/.../events/illuminance_threshY_falling_value
> > +What: /sys/.../events/illuminance_threshY_raising_value
> > +Date: April 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold<[email protected]>
> > +Description:
> > + Specifies the value of threshold that the device is comparing
> > + against for the events enabled by
> > + in_illuminance0_thresh_either_en (0..255), where Y in 0..3.
> > +
> > + Note that threshY_falling must be less than or equal to
> > + threshY_raising.
> > +
> > + These thresholds correspond to the eight zone-boundary
> > + registers (boundaryY_{low,high}) and defines the five light
> > + zones.
> > +
> > +What: /sys/bus/iio/devices/iio:deviceX/in_illuminance0_zone
> > +Date: April 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold<[email protected]>
> > +Description:
> > + Get the current light zone (0..4) as defined by the
> > + in_illuminance0_threshY_{falling,rising} thresholds.
> > +
> > +What: /sys/bus/iio/devices/iio:deviceX/out_currentY_raw
> > +Date: May 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold<[email protected]>
> > +Description:
> > + Get output current for channel Y (0..255), that is,
> > + out_currentY_currentZ_raw, where Z is the current zone.
> Should be in core docs. Can fix that later though.
> > +
> > +What: /sys/bus/iio/devices/iio:deviceX/out_currentY_currentZ_raw
> > +Date: May 2012
> > +KernelVersion: 3.5
> > +Contact: Johan Hovold<[email protected]>
> > +Description:
> > + Set the output current for channel out_currentY when in zone
> > + Z (0..255), where Y in 0..2 and Z in 0..4.
> > +
> > + These values correspond to the ALS-mapper target registers for
> > + ALS-mapper Y + 1.
> Hmm. tempted to move this to core docs too.
> > diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
> > index 56eecef..cacc74d 100644
> > --- a/drivers/iio/Kconfig
> > +++ b/drivers/iio/Kconfig
> > @@ -50,5 +50,6 @@ config IIO_CONSUMERS_PER_TRIGGER
> >
> > source "drivers/iio/adc/Kconfig"
> > source "drivers/iio/amplifiers/Kconfig"
> > +source "drivers/iio/light/Kconfig"
> >
> > endif # IIO
> > diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
> > index e425afd..060b674 100644
> > --- a/drivers/iio/Makefile
> > +++ b/drivers/iio/Makefile
> > @@ -11,3 +11,4 @@ obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o
> >
> > obj-y += adc/
> > obj-y += amplifiers/
> > +obj-y += light/
> > diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
> > new file mode 100644
> > index 0000000..db5618e
> > --- /dev/null
> > +++ b/drivers/iio/light/Kconfig
> > @@ -0,0 +1,22 @@
> > +#
> > +# Light sensors
> > +#
> > +menu "Light sensors"
> > +
> > +config SENSORS_LM3533
> > + tristate "LM3533 ambient light sensor"
> > + depends on MFD_LM3533
> > + help
> > + If you say yes here you get support for the ambient light sensor
> > + interface on National Semiconductor / TI LM3533 Lighting Power
> > + chips.
> > +
> > + The sensor interface can be used to control the LEDs and backlights
> > + of the chip through defining five light zones and three sets of
> > + corresponding output-current values.
> > +
> > + The driver provides raw and mean adc readings along with the current
> > + light zone through sysfs. A threshold event can be generated on zone
> > + changes. The ALS-control output values can be set per zone for the
> > + three current output channels.
> > +endmenu
> > diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
> > new file mode 100644
> > index 0000000..c1c23a0
> > --- /dev/null
> > +++ b/drivers/iio/light/Makefile
> > @@ -0,0 +1,5 @@
> > +#
> > +# Makefile for IIO Light sensors
> > +#
> > +
> > +obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o
> > diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c
> > new file mode 100644
> > index 0000000..c3e7bac
> > --- /dev/null
> > +++ b/drivers/iio/light/lm3533-als.c
> > @@ -0,0 +1,932 @@
> > +/*
> > + * lm3533-als.c -- LM3533 Ambient Light Sensor driver
> > + *
> > + * Copyright (C) 2011-2012 Texas Instruments
> > + *
> > + * Author: Johan Hovold<[email protected]>
> > + *
> > + * This program is free software; you can redistribute it and/or modify it
> > + * under the terms of the GNU General Public License as published by the
> > + * Free Software Foundation; either version 2 of the License, or (at your
> > + * option) any later version.
> > + */
> > +
> > +#include<linux/atomic.h>
> > +#include<linux/fs.h>
> > +#include<linux/interrupt.h>
> > +#include<linux/io.h>
> > +#include<linux/iio/events.h>
> > +#include<linux/iio/iio.h>
> > +#include<linux/module.h>
> > +#include<linux/mutex.h>
> > +#include<linux/mfd/core.h>
> > +#include<linux/platform_device.h>
> > +#include<linux/slab.h>
> > +#include<linux/uaccess.h>
> > +
> > +#include<linux/mfd/lm3533.h>
> > +
> > +
> > +#define LM3533_ALS_RESISTOR_MIN 1
> > +#define LM3533_ALS_RESISTOR_MAX 127
> > +#define LM3533_ALS_CHANNEL_CURRENT_MAX 2
> > +#define LM3533_ALS_THRESH_MAX 3
> > +#define LM3533_ALS_ZONE_MAX 4
> > +
> > +#define LM3533_REG_ALS_RESISTOR_SELECT 0x30
> > +#define LM3533_REG_ALS_CONF 0x31
> > +#define LM3533_REG_ALS_ZONE_INFO 0x34
> > +#define LM3533_REG_ALS_READ_ADC_RAW 0x37
> > +#define LM3533_REG_ALS_READ_ADC_AVERAGE 0x38
> > +#define LM3533_REG_ALS_BOUNDARY_BASE 0x50
> > +#define LM3533_REG_ALS_TARGET_BASE 0x60
> > +
> > +#define LM3533_ALS_ENABLE_MASK 0x01
> > +#define LM3533_ALS_INPUT_MODE_MASK 0x02
> > +#define LM3533_ALS_INT_ENABLE_MASK 0x01
> > +
> > +#define LM3533_ALS_ZONE_SHIFT 2
> > +#define LM3533_ALS_ZONE_MASK 0x1c
> > +
> > +#define LM3533_ALS_FLAG_INT_ENABLED 1
> > +
> > +
> > +struct lm3533_als {
> > + struct lm3533 *lm3533;
> > + struct platform_device *pdev;
> > +
> > + unsigned long flags;
> > + int irq;
> > +
> > + atomic_t zone;
> > + struct mutex thresh_mutex;
> > +};
> > +
> > +
> > +static int lm3533_als_get_adc(struct iio_dev *indio_dev, bool average,
> > + int *adc)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 reg;
> > + u8 val;
> > + int ret;
> > +
> > + if (average)
> > + reg = LM3533_REG_ALS_READ_ADC_AVERAGE;
> > + else
> > + reg = LM3533_REG_ALS_READ_ADC_RAW;
> > +
> > + ret = lm3533_read(als->lm3533, reg,&val);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to read adc\n");
> > + return ret;
> > + }
> > +
> > + *adc = val;
> > +
> > + return 0;
> > +}
> > +
> > +static int _lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 val;
> > + int ret;
> > +
> > + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO,&val);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to read zone\n");
> > + return ret;
> > + }
> > +
> > + val = (val& LM3533_ALS_ZONE_MASK)>> LM3533_ALS_ZONE_SHIFT;
> > + *zone = min_t(u8, val, LM3533_ALS_ZONE_MAX);
> > +
> > + return 0;
> > +}
> > +
> > +static int lm3533_als_get_zone(struct iio_dev *indio_dev, u8 *zone)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + int ret;
> > +
> > + if (test_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags)) {
> > + *zone = atomic_read(&als->zone);
> > + } else {
> > + ret = _lm3533_als_get_zone(indio_dev, zone);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/*
> > + * channel output channel 0..2
> > + * zone zone 0..4
> > + */
> > +static inline u8 lm3533_als_get_target_reg(unsigned channel, unsigned zone)
> > +{
> > + return LM3533_REG_ALS_TARGET_BASE + 5 * channel + zone;
> > +}
> > +
> > +static int lm3533_als_get_target(struct iio_dev *indio_dev, unsigned channel,
> > + unsigned zone, u8 *val)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 reg;
> > + int ret;
> > +
> > + if (channel> LM3533_ALS_CHANNEL_CURRENT_MAX)
> > + return -EINVAL;
> > +
> > + if (zone> LM3533_ALS_ZONE_MAX)
> > + return -EINVAL;
> > +
> > + reg = lm3533_als_get_target_reg(channel, zone);
> > + ret = lm3533_read(als->lm3533, reg, val);
> > + if (ret)
> > + dev_err(&indio_dev->dev, "failed to get target current\n");
> > +
> > + return ret;
> > +}
> > +
> > +static int lm3533_als_set_target(struct iio_dev *indio_dev, unsigned channel,
> > + unsigned zone, u8 val)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 reg;
> > + int ret;
> > +
> > + if (channel> LM3533_ALS_CHANNEL_CURRENT_MAX)
> > + return -EINVAL;
> > +
> > + if (zone> LM3533_ALS_ZONE_MAX)
> > + return -EINVAL;
> > +
> > + reg = lm3533_als_get_target_reg(channel, zone);
> > + ret = lm3533_write(als->lm3533, reg, val);
> > + if (ret)
> > + dev_err(&indio_dev->dev, "failed to set target current\n");
> > +
> > + return ret;
> > +}
> > +
> > +static int lm3533_als_get_current(struct iio_dev *indio_dev, unsigned channel,
> > + int *val)
> > +{
> > + u8 zone;
> > + u8 target;
> > + int ret;
> > +
> > + ret = lm3533_als_get_zone(indio_dev,&zone);
> > + if (ret)
> > + return ret;
> > +
> > + ret = lm3533_als_get_target(indio_dev, channel, zone,&target);
> > + if (ret)
> > + return ret;
> > +
> > + *val = target;
> > +
> > + return 0;
> > +}
> > +
> > +static int lm3533_als_read_raw(struct iio_dev *indio_dev,
> > + struct iio_chan_spec const *chan,
> > + int *val, int *val2, long mask)
> > +{
> > + int ret;
> > +
> > + switch (mask) {
> > + case 0:
> > + switch (chan->type) {
> > + case IIO_LIGHT:
> > + ret = lm3533_als_get_adc(indio_dev, false, val);
> > + break;
> > + case IIO_CURRENT:
> > + ret = lm3533_als_get_current(indio_dev, chan->channel,
> > + val);
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > + break;
> > + case IIO_CHAN_INFO_AVERAGE_RAW:
> > + ret = lm3533_als_get_adc(indio_dev, true, val);
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + if (ret)
> > + return ret;
> > +
> > + return IIO_VAL_INT;
> > +}
> > +
> > +#define CHANNEL_CURRENT(_channel) \
> > + { \
> > + .type = IIO_CURRENT, \
> > + .channel = _channel, \
> > + .indexed = true, \
> > + .output = true, \
> > + .info_mask = IIO_CHAN_INFO_RAW_SEPARATE_BIT, \
> > + }
> > +
> > +static const struct iio_chan_spec lm3533_als_channels[] = {
> > + {
> > + .type = IIO_LIGHT,
> > + .channel = 0,
> > + .indexed = true,
> > + .info_mask = (IIO_CHAN_INFO_AVERAGE_RAW_SEPARATE_BIT |
> > + IIO_CHAN_INFO_RAW_SEPARATE_BIT),
> > + },
> > + CHANNEL_CURRENT(0),
> > + CHANNEL_CURRENT(1),
> > + CHANNEL_CURRENT(2),
> > +};
> > +
> > +static irqreturn_t lm3533_als_isr(int irq, void *dev_id)
> > +{
> > +
> > + struct iio_dev *indio_dev = dev_id;
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 zone;
> > + int ret;
> > +
> > + /* Clear interrupt by reading the ALS zone register. */
> > + ret = _lm3533_als_get_zone(indio_dev,&zone);
> > + if (ret)
> > + goto out;
> > +
> > + atomic_set(&als->zone, zone);
> > +
> > + iio_push_event(indio_dev,
> > + IIO_UNMOD_EVENT_CODE(IIO_LIGHT,
> > + 0,
> > + IIO_EV_TYPE_THRESH,
> > + IIO_EV_DIR_EITHER),
> > + iio_get_time_ns());
> > +out:
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static int lm3533_als_set_int_mode(struct iio_dev *indio_dev, int enable)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> > + u8 val;
> > + int ret;
> > +
> > + if (enable)
> > + val = mask;
> > + else
> > + val = 0;
> > +
> > + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, val, mask);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to set int mode %d\n",
> > + enable);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int lm3533_als_get_int_mode(struct iio_dev *indio_dev, int *enable)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> > + u8 val;
> > + int ret;
> > +
> > + ret = lm3533_read(als->lm3533, LM3533_REG_ALS_ZONE_INFO,&val);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to get int mode\n");
> > + return ret;
> > + }
> > +
> > + *enable = !!(val& mask);
> > +
> > + return 0;
> > +}
> > +
> > +static inline u8 lm3533_als_get_threshold_reg(unsigned nr, bool raising)
> > +{
> > + u8 offset = !raising;
> > +
> > + return LM3533_REG_ALS_BOUNDARY_BASE + 2 * nr + offset;
> > +}
> > +
> > +static int lm3533_als_get_threshold(struct iio_dev *indio_dev, unsigned nr,
> > + bool raising, u8 *val)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 reg;
> > + int ret;
> > +
> > + if (nr> LM3533_ALS_THRESH_MAX)
> > + return -EINVAL;
> > +
> > + reg = lm3533_als_get_threshold_reg(nr, raising);
> > + ret = lm3533_read(als->lm3533, reg, val);
> > + if (ret)
> > + dev_err(&indio_dev->dev, "failed to get threshold\n");
> > +
> > + return ret;
> > +}
> > +
> > +static int lm3533_als_set_threshold(struct iio_dev *indio_dev, unsigned nr,
> > + bool raising, u8 val)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 val2;
> > + u8 reg, reg2;
> > + int ret;
> > +
> > + if (nr> LM3533_ALS_THRESH_MAX)
> > + return -EINVAL;
> > +
> > + reg = lm3533_als_get_threshold_reg(nr, raising);
> > + reg2 = lm3533_als_get_threshold_reg(nr, !raising);
> > +
> > + mutex_lock(&als->thresh_mutex);
> > + ret = lm3533_read(als->lm3533, reg2,&val2);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to get threshold\n");
> > + goto out;
> > + }
> > + /*
> > + * This device does not allow negative hysteresis (in fact, it uses
> > + * whichever value is smaller as the lower bound) so we need to make
> > + * sure that thresh_falling<= thresh_raising.
> > + */
> > + if ((raising&& (val< val2)) || (!raising&& (val> val2))) {
> > + ret = -EINVAL;
> > + goto out;
> > + }
> > +
> > + ret = lm3533_write(als->lm3533, reg, val);
> > + if (ret) {
> > + dev_err(&indio_dev->dev, "failed to set threshold\n");
> > + goto out;
> > + }
> > +out:
> > + mutex_unlock(&als->thresh_mutex);
> > +
> > + return ret;
> > +}
> > +
> > +static int lm3533_als_get_hysteresis(struct iio_dev *indio_dev, unsigned nr,
> > + u8 *val)
> > +{
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + u8 falling;
> > + u8 raising;
> > + int ret;
> > +
> > + if (nr> LM3533_ALS_THRESH_MAX)
> > + return -EINVAL;
> > +
> > + mutex_lock(&als->thresh_mutex);
> > + ret = lm3533_als_get_threshold(indio_dev, nr, false,&falling);
> > + if (ret)
> > + goto out;
> > + ret = lm3533_als_get_threshold(indio_dev, nr, true,&raising);
> > + if (ret)
> > + goto out;
> > +
> > + *val = raising - falling;
> > +out:
> > + mutex_unlock(&als->thresh_mutex);
> > +
> > + return ret;
> > +}
> > +
> > +static int show_thresh_either_en(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + int enable;
> > + int ret;
> > +
> > + if (als->irq) {
> > + ret = lm3533_als_get_int_mode(indio_dev,&enable);
> > + if (ret)
> > + return ret;
> > + } else {
> > + enable = 0;
> > + }
> > +
> > + return scnprintf(buf, PAGE_SIZE, "%u\n", enable);
> > +}
> > +
> > +static int store_thresh_either_en(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t len)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > + unsigned long enable;
> > + bool int_enabled;
> > + u8 zone;
> > + int ret;
> > +
> > + if (!als->irq)
> > + return -EBUSY;
> > +
> > + if (kstrtoul(buf, 0,&enable))
> > + return -EINVAL;
> > +
> > + int_enabled = test_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> > +
> > + if (enable&& !int_enabled) {
> > + ret = lm3533_als_get_zone(indio_dev,&zone);
> > + if (ret)
> > + return ret;
> > +
> > + atomic_set(&als->zone, zone);
> > +
> > + set_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> > + }
> > +
> > + ret = lm3533_als_set_int_mode(indio_dev, enable);
> > + if (ret) {
> > + if (!int_enabled)
> > + clear_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> > +
> > + return ret;
> > + }
> > +
> > + if (!enable)
> > + clear_bit(LM3533_ALS_FLAG_INT_ENABLED,&als->flags);
> > +
> > + return len;
> > +}
> > +
> > +static ssize_t show_zone(struct device *dev,
> > + struct device_attribute *attr, char *buf)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + u8 zone;
> > + int ret;
> > +
> > + ret = lm3533_als_get_zone(indio_dev,&zone);
> > + if (ret)
> > + return ret;
> > +
> > + return scnprintf(buf, PAGE_SIZE, "%u\n", zone);
> > +}
> > +
> > +enum lm3533_als_attribute_type {
> > + LM3533_ATTR_TYPE_HYSTERESIS,
> > + LM3533_ATTR_TYPE_TARGET,
> > + LM3533_ATTR_TYPE_THRESH_FALLING,
> > + LM3533_ATTR_TYPE_THRESH_RAISING,
> > +};
> > +
> > +struct lm3533_als_attribute {
> > + struct device_attribute dev_attr;
> > + enum lm3533_als_attribute_type type;
> > + u8 val1;
> > + u8 val2;
> > +};
> > +
> > +static inline struct lm3533_als_attribute *
> > +to_lm3533_als_attr(struct device_attribute *attr)
> > +{
> > + return container_of(attr, struct lm3533_als_attribute, dev_attr);
> > +}
> > +
> > +static ssize_t show_als_attr(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> > + u8 val;
> > + int ret;
> > +
> > + switch (als_attr->type) {
> > + case LM3533_ATTR_TYPE_HYSTERESIS:
> > + ret = lm3533_als_get_hysteresis(indio_dev, als_attr->val1,
> > + &val);
> > + break;
> > + case LM3533_ATTR_TYPE_TARGET:
> > + ret = lm3533_als_get_target(indio_dev, als_attr->val1,
> > + als_attr->val2,&val);
> > + break;
> > + case LM3533_ATTR_TYPE_THRESH_FALLING:
> > + ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
> > + false,&val);
> > + break;
> > + case LM3533_ATTR_TYPE_THRESH_RAISING:
> > + ret = lm3533_als_get_threshold(indio_dev, als_attr->val1,
> > + true,&val);
> > + break;
> > + default:
> > + ret = -ENXIO;
> > + }
> > +
> > + if (ret)
> > + return ret;
> > +
> > + return scnprintf(buf, PAGE_SIZE, "%u\n", val);
> > +}
> > +
> > +static ssize_t store_als_attr(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t len)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct lm3533_als_attribute *als_attr = to_lm3533_als_attr(attr);
> > + u8 val;
> > + int ret;
> > +
> > + if (kstrtou8(buf, 0,&val))
> > + return -EINVAL;
> > +
> > + switch (als_attr->type) {
> > + case LM3533_ATTR_TYPE_TARGET:
> > + ret = lm3533_als_set_target(indio_dev, als_attr->val1,
> > + als_attr->val2, val);
> > + break;
> > + case LM3533_ATTR_TYPE_THRESH_FALLING:
> > + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> > + false, val);
> > + break;
> > + case LM3533_ATTR_TYPE_THRESH_RAISING:
> > + ret = lm3533_als_set_threshold(indio_dev, als_attr->val1,
> > + true, val);
> > + break;
> > + default:
> > + ret = -ENXIO;
> > + }
> > +
> > + if (ret)
> > + return ret;
> > +
> > + return len;
> > +}
> > +
> > +#define ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
> > + { .dev_attr = __ATTR(_name, _mode, _show, _store), \
> > + .type = _type, \
> > + .val1 = _val1, \
> > + .val2 = _val2 }
> > +
> > +#define LM3533_ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2) \
> > + struct lm3533_als_attribute lm3533_als_attr_##_name = \
> > + ALS_ATTR(_name, _mode, _show, _store, _type, _val1, _val2)
> > +
> > +#define ALS_TARGET_ATTR_RW(_channel, _zone) \
> > + LM3533_ALS_ATTR(out_current##_channel##_current##_zone##_raw, \
> > + S_IRUGO | S_IWUSR, \
> > + show_als_attr, store_als_attr, \
> > + LM3533_ATTR_TYPE_TARGET, _channel, _zone)
> > +/*
> > + * ALS output current values (ALS mapper targets)
> > + *
> > + * out_current[0-2]_current[0-4]_raw 0-255
> > + */
> > +static ALS_TARGET_ATTR_RW(0, 0);
> > +static ALS_TARGET_ATTR_RW(0, 1);
> > +static ALS_TARGET_ATTR_RW(0, 2);
> > +static ALS_TARGET_ATTR_RW(0, 3);
> > +static ALS_TARGET_ATTR_RW(0, 4);
> > +
> > +static ALS_TARGET_ATTR_RW(1, 0);
> > +static ALS_TARGET_ATTR_RW(1, 1);
> > +static ALS_TARGET_ATTR_RW(1, 2);
> > +static ALS_TARGET_ATTR_RW(1, 3);
> > +static ALS_TARGET_ATTR_RW(1, 4);
> > +
> > +static ALS_TARGET_ATTR_RW(2, 0);
> > +static ALS_TARGET_ATTR_RW(2, 1);
> > +static ALS_TARGET_ATTR_RW(2, 2);
> > +static ALS_TARGET_ATTR_RW(2, 3);
> > +static ALS_TARGET_ATTR_RW(2, 4);
> > +
> > +#define ALS_THRESH_FALLING_ATTR_RW(_nr) \
> > + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_falling_value, \
> > + S_IRUGO | S_IWUSR, \
> > + show_als_attr, store_als_attr, \
> > + LM3533_ATTR_TYPE_THRESH_FALLING, _nr, 0)
> > +
> > +#define ALS_THRESH_RAISING_ATTR_RW(_nr) \
> > + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_raising_value, \
> > + S_IRUGO | S_IWUSR, \
> > + show_als_attr, store_als_attr, \
> > + LM3533_ATTR_TYPE_THRESH_RAISING, _nr, 0)
> > +/*
> > + * ALS Zone thresholds (boundaries)
> > + *
> > + * in_illuminance0_thresh[0-3]_falling_value 0-255
> > + * in_illuminance0_thresh[0-3]_raising_value 0-255
> > + */
> > +static ALS_THRESH_FALLING_ATTR_RW(0);
> > +static ALS_THRESH_FALLING_ATTR_RW(1);
> > +static ALS_THRESH_FALLING_ATTR_RW(2);
> > +static ALS_THRESH_FALLING_ATTR_RW(3);
> > +
> > +static ALS_THRESH_RAISING_ATTR_RW(0);
> > +static ALS_THRESH_RAISING_ATTR_RW(1);
> > +static ALS_THRESH_RAISING_ATTR_RW(2);
> > +static ALS_THRESH_RAISING_ATTR_RW(3);
> > +
> > +#define ALS_HYSTERESIS_ATTR_RO(_nr) \
> > + LM3533_ALS_ATTR(in_illuminance0_thresh##_nr##_hysteresis, \
> > + S_IRUGO, show_als_attr, NULL, \
> > + LM3533_ATTR_TYPE_HYSTERESIS, _nr, 0)
> > +/*
> > + * ALS Zone threshold hysteresis
> > + *
> > + * threshY_hysteresis = threshY_raising - threshY_falling
> > + *
> > + * in_illuminance0_thresh[0-3]_hysteresis 0-255
> > + * in_illuminance0_thresh[0-3]_hysteresis 0-255
> > + */
> > +static ALS_HYSTERESIS_ATTR_RO(0);
> > +static ALS_HYSTERESIS_ATTR_RO(1);
> > +static ALS_HYSTERESIS_ATTR_RO(2);
> > +static ALS_HYSTERESIS_ATTR_RO(3);
> > +
> > +#define ILLUMINANCE_ATTR_RO(_name) \
> > + DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO, show_##_name, NULL)
> > +#define ILLUMINANCE_ATTR_RW(_name) \
> > + DEVICE_ATTR(in_illuminance0_##_name, S_IRUGO | S_IWUSR , \
> > + show_##_name, store_##_name)
> > +/*
> > + * ALS Zone threshold-event enable
> > + *
> > + * in_illuminance0_thresh_either_en 0,1
> > + */
> > +static ILLUMINANCE_ATTR_RW(thresh_either_en);
> > +
> > +/*
> > + * ALS Current Zone
> > + *
> > + * in_illuminance0_zone 0-4
> > + */
> > +static ILLUMINANCE_ATTR_RO(zone);
> > +
> > +static struct attribute *lm3533_als_event_attributes[] = {
> > + &dev_attr_in_illuminance0_thresh_either_en.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh0_falling_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh0_hysteresis.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh0_raising_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh1_falling_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh1_hysteresis.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh1_raising_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh2_falling_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh2_hysteresis.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh2_raising_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh3_falling_value.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh3_hysteresis.dev_attr.attr,
> > + &lm3533_als_attr_in_illuminance0_thresh3_raising_value.dev_attr.attr,
> > + NULL
> > +};
> > +
> > +static struct attribute_group lm3533_als_event_attribute_group = {
> > + .attrs = lm3533_als_event_attributes
> > +};
> > +
> > +static struct attribute *lm3533_als_attributes[] = {
> > + &dev_attr_in_illuminance0_zone.attr,
> > + &lm3533_als_attr_out_current0_current0_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current0_current1_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current0_current2_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current0_current3_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current0_current4_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current1_current0_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current1_current1_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current1_current2_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current1_current3_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current1_current4_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current2_current0_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current2_current1_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current2_current2_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current2_current3_raw.dev_attr.attr,
> > + &lm3533_als_attr_out_current2_current4_raw.dev_attr.attr,
> > + NULL
> > +};
> > +
> > +static struct attribute_group lm3533_als_attribute_group = {
> > + .attrs = lm3533_als_attributes
> > +};
> > +
> > +static int __devinit lm3533_als_set_input_mode(struct lm3533_als *als,
> > + bool pwm_mode)
> > +{
> > + u8 mask = LM3533_ALS_INPUT_MODE_MASK;
> > + u8 val;
> > + int ret;
> > +
> > + if (pwm_mode)
> > + val = mask; /* pwm input */
> > + else
> > + val = 0; /* analog input */
> > +
> > + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, val, mask);
> > + if (ret) {
> > + dev_err(&als->pdev->dev, "failed to set input mode %d\n",
> > + pwm_mode);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int __devinit lm3533_als_set_resistor(struct lm3533_als *als, u8 val)
> > +{
> > + int ret;
> > +
> > + if (val< LM3533_ALS_RESISTOR_MIN || val> LM3533_ALS_RESISTOR_MAX)
> > + return -EINVAL;
> > +
> > + ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val);
> > + if (ret) {
> > + dev_err(&als->pdev->dev, "failed to set resistor\n");
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int __devinit lm3533_als_setup(struct lm3533_als *als,
> > + struct lm3533_als_platform_data *pdata)
> > +{
> > + int ret;
> > +
> > + ret = lm3533_als_set_input_mode(als, pdata->pwm_mode);
> > + if (ret)
> > + return ret;
> > +
> > + /* ALS input is always high impedance in PWM-mode. */
> > + if (!pdata->pwm_mode) {
> > + ret = lm3533_als_set_resistor(als, pdata->r_select);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int __devinit lm3533_als_setup_irq(struct lm3533_als *als, void *dev)
> > +{
> > + u8 mask = LM3533_ALS_INT_ENABLE_MASK;
> > + int ret;
> > +
> > + /* Make sure interrupts are disabled. */
> > + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_ZONE_INFO, 0, mask);
> > + if (ret) {
> > + dev_err(&als->pdev->dev, "failed to disable interrupts\n");
> > + return ret;
> > + }
> > +
> > + ret = request_threaded_irq(als->irq, NULL, lm3533_als_isr,
> > + IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> > + dev_name(&als->pdev->dev), dev);
> > + if (ret) {
> > + dev_err(&als->pdev->dev, "failed to request irq %d\n",
> > + als->irq);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int __devinit lm3533_als_enable(struct lm3533_als *als)
> > +{
> > + u8 mask = LM3533_ALS_ENABLE_MASK;
> > + int ret;
> > +
> > + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, mask, mask);
> > + if (ret)
> > + dev_err(&als->pdev->dev, "failed to enable ALS\n");
> > +
> > + return ret;
> > +}
> > +
> > +static int lm3533_als_disable(struct lm3533_als *als)
> > +{
> > + u8 mask = LM3533_ALS_ENABLE_MASK;
> > + int ret;
> > +
> > + ret = lm3533_update(als->lm3533, LM3533_REG_ALS_CONF, 0, mask);
> > + if (ret)
> > + dev_err(&als->pdev->dev, "failed to disable ALS\n");
> > +
> > + return ret;
> > +}
> > +
> > +static const struct iio_info lm3533_als_info = {
> > + .attrs =&lm3533_als_attribute_group,
> > + .event_attrs =&lm3533_als_event_attribute_group,
> > + .driver_module = THIS_MODULE,
> > + .read_raw =&lm3533_als_read_raw,
> > +};
> > +
> > +static int __devinit lm3533_als_probe(struct platform_device *pdev)
> > +{
> > + struct lm3533 *lm3533;
> > + struct lm3533_als_platform_data *pdata;
> > + struct lm3533_als *als;
> > + struct iio_dev *indio_dev;
> > + int ret;
> > +
> > + lm3533 = dev_get_drvdata(pdev->dev.parent);
> > + if (!lm3533)
> > + return -EINVAL;
> > +
> > + pdata = pdev->dev.platform_data;
> > + if (!pdata) {
> > + dev_err(&pdev->dev, "no platform data\n");
> > + return -EINVAL;
> > + }
> > +
> > + indio_dev = iio_device_alloc(sizeof(*als));
> > + if (!indio_dev)
> > + return -ENOMEM;
> > +
> > + indio_dev->info =&lm3533_als_info;
> > + indio_dev->channels = lm3533_als_channels;
> > + indio_dev->num_channels = ARRAY_SIZE(lm3533_als_channels);
> > + indio_dev->name = dev_name(&pdev->dev);
> > + indio_dev->dev.parent = pdev->dev.parent;
> > + indio_dev->modes = INDIO_DIRECT_MODE;
> > +
> > + als = iio_priv(indio_dev);
> > + als->lm3533 = lm3533;
> > + als->pdev = pdev;
> > + als->irq = lm3533->irq;
> > + atomic_set(&als->zone, 0);
> > + mutex_init(&als->thresh_mutex);
> > +
> > + platform_set_drvdata(pdev, indio_dev);
> > +
> > + if (als->irq) {
> > + ret = lm3533_als_setup_irq(als, indio_dev);
> > + if (ret)
> > + goto err_free_dev;
> > + }
> > +
> > + ret = lm3533_als_setup(als, pdata);
> > + if (ret)
> > + goto err_free_irq;
> > +
> > + ret = lm3533_als_enable(als);
> > + if (ret)
> > + goto err_free_irq;
> > +
> > + ret = iio_device_register(indio_dev);
> > + if (ret) {
> > + dev_err(&pdev->dev, "failed to register ALS\n");
> > + goto err_disable;
> > + }
> > +
> > + return 0;
> > +
> > +err_disable:
> > + lm3533_als_disable(als);
> > +err_free_irq:
> > + if (als->irq)
> > + free_irq(als->irq, indio_dev);
> > +err_free_dev:
> > + iio_device_free(indio_dev);
> > +
> > + return ret;
> > +}
> > +
> > +static int __devexit lm3533_als_remove(struct platform_device *pdev)
> > +{
> > + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> > + struct lm3533_als *als = iio_priv(indio_dev);
> > +
> > + lm3533_als_set_int_mode(indio_dev, false);
> > + iio_device_unregister(indio_dev);
> > + lm3533_als_disable(als);
> > + if (als->irq)
> > + free_irq(als->irq, indio_dev);
> > + iio_device_free(indio_dev);
> > +
> > + return 0;
> > +}
> > +
> > +static struct platform_driver lm3533_als_driver = {
> > + .driver = {
> > + .name = "lm3533-als",
> > + .owner = THIS_MODULE,
> > + },
> > + .probe = lm3533_als_probe,
> > + .remove = __devexit_p(lm3533_als_remove),
> > +};
> > +module_platform_driver(lm3533_als_driver);
> > +
> > +MODULE_AUTHOR("Johan Hovold<[email protected]>");
> > +MODULE_DESCRIPTION("LM3533 Ambient Light Sensor driver");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:lm3533-als");
>
On Tue, May 22, 2012 at 11:40:20AM +0200, Johan Hovold wrote:
> On Tue, May 22, 2012 at 10:19:15AM +0100, Jonathan Cameron wrote:
> > On 5/21/2012 1:18 PM, Johan Hovold wrote:
> > > Add sub-driver for the ambient-light-sensor interface on National
> > > Semiconductor / TI LM3533 lighting power chips.
> > >
> > > The sensor interface can be used to control the LEDs and backlights of
> > > the chip through defining five light zones and three sets of
> > > corresponding output-current values.
> > >
> > > The driver provides raw and mean adc readings along with the current
> > > light zone through sysfs. A threshold event can be generated on zone
> > > changes. The ALS-control output values can be set per zone for the three
> > > current output channels.
> > There are a few bits of documentation I'd rather were in
> > sysfs-bus-iio but we can move them any time.
> >
> > I haven't compile tested as no machine availability till this evening.
> > Please do one last sanity check against staging-next.
>
> Tested against staging-next of today (c3c6cc91b0ae7b).
>
> Greg, any chance you can pick this one up for 3.5?
No, the merge window for 3.5 closed last week, sorry. Now is the time
for me (and the other subsystem maintainers) to merge my trees with
Linus. I'll pick this driver up when 3.5-rc1 is out.
thanks,
greg k-h
On Tue, May 22, 2012 at 11:40:20AM +0200, Johan Hovold wrote:
> On Tue, May 22, 2012 at 10:19:15AM +0100, Jonathan Cameron wrote:
> > On 5/21/2012 1:18 PM, Johan Hovold wrote:
> > > Add sub-driver for the ambient-light-sensor interface on National
> > > Semiconductor / TI LM3533 lighting power chips.
> > >
> > > The sensor interface can be used to control the LEDs and backlights of
> > > the chip through defining five light zones and three sets of
> > > corresponding output-current values.
> > >
> > > The driver provides raw and mean adc readings along with the current
> > > light zone through sysfs. A threshold event can be generated on zone
> > > changes. The ALS-control output values can be set per zone for the three
> > > current output channels.
> > There are a few bits of documentation I'd rather were in
> > sysfs-bus-iio but we can move them any time.
> >
> > I haven't compile tested as no machine availability till this evening.
> > Please do one last sanity check against staging-next.
>
> Tested against staging-next of today (c3c6cc91b0ae7b).
>
> Greg, any chance you can pick this one up for 3.5?
To late for 3.5, the merge window for new stuff closed a week or so
before 3.4 came out, as stuff needs to be tested in linux-next.
I've queued it up now to be merge in 3.6.
thanks,
greg k-h