[PATCH 08/10] leds: Add LM3633 driver
From: Milo Kim
Date: Fri Feb 14 2014 - 01:32:43 EST
LM3633 LED driver supports generic LED functions and pattern generation.
Pattern is generated by using LMU effect driver APIs.
Sysfs documentation is added.
Cc: Bryan Wu <cooloney@xxxxxxxxx>
Signed-off-by: Milo Kim <milo.kim@xxxxxx>
---
Documentation/leds/leds-lm3633.txt | 38 +++
drivers/leds/Kconfig | 10 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-lm3633.c | 661 ++++++++++++++++++++++++++++++++++++
4 files changed, 710 insertions(+)
create mode 100644 Documentation/leds/leds-lm3633.txt
create mode 100644 drivers/leds/leds-lm3633.c
diff --git a/Documentation/leds/leds-lm3633.txt b/Documentation/leds/leds-lm3633.txt
new file mode 100644
index 0000000..a5a59c3
--- /dev/null
+++ b/Documentation/leds/leds-lm3633.txt
@@ -0,0 +1,38 @@
+LM3633 LED Driver
+=================
+
+LM3633 LED driver supports not only LED functions but also programmable pattern.
+A pattern is generated via the sysfs.
+
+LED Pattern Generator
+
+ (3)
+ (a) ---------------> ___________
+ / \
+ (2) / \ (4)
+ (b) ----> _________/ \_________ ...
+ (1) (5)
+
+ |<----- period -----> |
+
+ Time dimension
+ (1) delay : 0 ~ 10 sec
+ (2) rise : 0 ~ 16 sec
+ (3) high : 0 ~ 10 sec
+ (4) fall : 0 ~ 16 sec
+ (5) low : 0 ~ 16 sec
+
+ Level dimension - channel current
+ (a) low : 0 ~ 255
+ (b) high : 0 ~ 255
+
+Example)
+Time : No delay, rise 500ms, high 1000ms, fall 400ms, low 2000ms
+Level : Brightness 0 and 255
+
+echo 0 500 1000 400 2000 > /sys/class/leds/<LED NAME>/pattern_times
+echo 0 255 > /sys/class/leds/<LED NAME>/pattern_levels
+echo 1 > /sys/class/leds/<LED NAME>/run_pattern
+
+To stop running pattern,
+echo 0 > /sys/class/leds/<LED NAME>/run_pattern
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 72156c1..ed659be 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -63,6 +63,16 @@ config LEDS_LM3533
hardware-accelerated blinking with maximum on and off periods of 9.8
and 77 seconds respectively.
+config LEDS_LM3633
+ tristate "LED support for the TI LM3633 LMU"
+ depends on LEDS_CLASS
+ depends on MFD_TI_LMU
+ help
+ This option enables support for the LEDs on the LM3633.
+ LM3633 has 6 low voltage indicator LEDs.
+ All low voltage current sinks can have a programmable pattern
+ modulated onto LED output strings.
+
config LEDS_LM3642
tristate "LED support for LM3642 Chip"
depends on LEDS_CLASS && I2C
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 3cd76db..96f55fe 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -11,6 +11,7 @@ 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_LM3633) += leds-lm3633.o
obj-$(CONFIG_LEDS_LM3642) += leds-lm3642.o
obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o
obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o
diff --git a/drivers/leds/leds-lm3633.c b/drivers/leds/leds-lm3633.c
new file mode 100644
index 0000000..13a43bf
--- /dev/null
+++ b/drivers/leds/leds-lm3633.c
@@ -0,0 +1,661 @@
+/*
+ * TI LM3633 LED driver
+ *
+ * Copyright 2014 Texas Instruments
+ *
+ * Author: Milo Kim <milo.kim@xxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * LM3633 LED driver has features below.
+ *
+ * - Generic LED subsystem control
+ * - LED string configuration
+ * - Pattern programming via the sysfs
+ * - Platform data configuration from the device tree nodes
+ *
+ * Pattern generated by using LMU effect driver APIs.
+ *
+ */
+
+#include <linux/leds.h>
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-effect.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define LM3633_LED_MAX_BRIGHTNESS 255
+#define LM3633_DEFAULT_LED_NAME "indicator"
+
+enum lm3633_led_bank_id {
+ LM3633_LED_BANK_C,
+ LM3633_LED_BANK_D,
+ LM3633_LED_BANK_E,
+ LM3633_LED_BANK_F,
+ LM3633_LED_BANK_G,
+ LM3633_LED_BANK_H,
+ LM3633_MAX_LEDS,
+};
+
+struct lm3633_pattern_time {
+ unsigned int delay;
+ unsigned int rise;
+ unsigned int high;
+ unsigned int fall;
+ unsigned int low;
+};
+
+struct lm3633_pattern_level {
+ u8 low;
+ u8 high;
+};
+
+/* One LED chip can have multiple LED strings (max: 6) */
+struct ti_lmu_led_chip {
+ struct device *dev;
+ struct ti_lmu *lmu;
+ struct mutex lock;
+ int num_leds;
+};
+
+/* LED string structure */
+struct ti_lmu_led {
+ enum lm3633_led_bank_id bank_id;
+
+ struct led_classdev cdev;
+ struct ti_lmu_led_chip *chip;
+ struct ti_lmu_led_platform_data *led_pdata;
+
+ struct work_struct work;
+ enum led_brightness brightness;
+
+ /* Pattern specific data */
+ struct lm3633_pattern_time time;
+ struct lm3633_pattern_level level;
+};
+
+static struct ti_lmu_led *to_ti_lmu_led(struct device *dev)
+{
+ struct led_classdev *_cdev = dev_get_drvdata(dev);
+ return container_of(_cdev, struct ti_lmu_led, cdev);
+}
+
+static int lm3633_led_config_bank(struct ti_lmu_led *lmu_led)
+{
+ u8 val;
+ int i, ret;
+ u8 group_led[] = { 0, BIT(0), BIT(0), 0, BIT(3), BIT(3), };
+ enum lm3633_led_bank_id default_id[] = {
+ LM3633_LED_BANK_C, LM3633_LED_BANK_C, LM3633_LED_BANK_C,
+ LM3633_LED_BANK_F, LM3633_LED_BANK_F, LM3633_LED_BANK_F,
+ };
+ enum lm3633_led_bank_id separate_id[] = {
+ LM3633_LED_BANK_C, LM3633_LED_BANK_D, LM3633_LED_BANK_E,
+ LM3633_LED_BANK_F, LM3633_LED_BANK_G, LM3633_LED_BANK_H,
+ };
+
+ /*
+ * Check configured LED string and assign control bank
+ *
+ * Each LED is tied with other LEDS (group):
+ * the default control bank is assigned
+ *
+ * Otherwise:
+ * separate bank is assigned
+ */
+
+ for (i = 0; i < LM3633_MAX_LEDS; i++) {
+ /* LED 1 and LED 4 are fixed, so no assignment is required */
+ if (i == 0 || i == 3)
+ continue;
+
+ if (test_bit(i, &lmu_led->led_pdata->led_string)) {
+ if (lmu_led->led_pdata->led_string & group_led[i]) {
+ lmu_led->bank_id = default_id[i];
+ val = 0;
+ } else {
+ lmu_led->bank_id = separate_id[i];
+ val = BIT(i);
+ }
+
+ ret = ti_lmu_update_bits(lmu_led->chip->lmu,
+ LM3633_REG_BANK_SEL,
+ BIT(i), val);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int lm3633_led_enable_bank(struct ti_lmu_led *lmu_led, bool on)
+{
+ u8 mask = 1 << (lmu_led->bank_id + LM3633_LED_BANK_OFFSET);
+
+ if (on)
+ return ti_lmu_update_bits(lmu_led->chip->lmu,
+ LM3633_REG_ENABLE, mask, mask);
+ else
+ return ti_lmu_update_bits(lmu_led->chip->lmu,
+ LM3633_REG_ENABLE, mask, 0);
+}
+
+/*
+ * This callback function is invoked in case the LMU effect driver is
+ * requested successfully.
+ */
+static void lm3633_led_effect_cb(struct ti_lmu_effect *lmu_effect,
+ int req_id, void *data)
+{
+ struct ti_lmu_led *lmu_led = data;
+ u8 reg_offset = lmu_led->bank_id * LM3633_PATTERN_REG_OFFSET;
+
+ switch (req_id) {
+ case LED_EFFECT_DELAY:
+ ti_lmu_effect_set_time(lmu_effect, lmu_led->time.delay,
+ reg_offset);
+ break;
+ case LED_EFFECT_HIGHTIME:
+ ti_lmu_effect_set_time(lmu_effect, lmu_led->time.high,
+ reg_offset);
+ break;
+ case LED_EFFECT_LOWTIME:
+ ti_lmu_effect_set_time(lmu_effect, lmu_led->time.low,
+ reg_offset);
+ break;
+ case LED_EFFECT_PTN0_RAMPUP:
+ case LED_EFFECT_PTN1_RAMPUP:
+ ti_lmu_effect_set_ramp(lmu_effect, lmu_led->time.rise);
+ break;
+ case LED_EFFECT_PTN0_RAMPDN:
+ case LED_EFFECT_PTN1_RAMPDN:
+ ti_lmu_effect_set_ramp(lmu_effect, lmu_led->time.fall);
+ break;
+ case LED_EFFECT_LOWBRT:
+ ti_lmu_effect_set_level(lmu_effect, lmu_led->level.low,
+ reg_offset);
+ break;
+ case LED_EFFECT_HIGHBRT:
+ ti_lmu_effect_set_level(lmu_effect, lmu_led->level.high,
+ lmu_led->bank_id);
+ break;
+ default:
+ break;
+ }
+}
+
+static int lm3633_led_effect_request(enum lmu_effect_request_id id,
+ struct ti_lmu_led *lmu_led)
+{
+ const char *name[] = {
+ [LED_EFFECT_DELAY] = LM3633_EFFECT_PTN_DELAY,
+ [LED_EFFECT_HIGHTIME] = LM3633_EFFECT_PTN_HIGHTIME,
+ [LED_EFFECT_LOWTIME] = LM3633_EFFECT_PTN_LOWTIME,
+ [LED_EFFECT_PTN0_RAMPUP] = LM3633_EFFECT_PTN0_RAMPUP,
+ [LED_EFFECT_PTN0_RAMPDN] = LM3633_EFFECT_PTN0_RAMPDOWN,
+ [LED_EFFECT_PTN1_RAMPUP] = LM3633_EFFECT_PTN1_RAMPUP,
+ [LED_EFFECT_PTN1_RAMPDN] = LM3633_EFFECT_PTN1_RAMPDOWN,
+ [LED_EFFECT_LOWBRT] = LM3633_EFFECT_PTN_LOWBRT,
+ [LED_EFFECT_HIGHBRT] = LM3633_EFFECT_PTN_HIGHBRT,
+ };
+
+ return ti_lmu_effect_request(name[id], lm3633_led_effect_cb, id,
+ lmu_led);
+}
+
+static ssize_t lm3633_led_show_pattern_times(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+
+ return sprintf(buf, "delay: %u, rise: %u, high:%u, fall:%u, low: %u\n",
+ lmu_led->time.delay, lmu_led->time.rise,
+ lmu_led->time.high, lmu_led->time.fall,
+ lmu_led->time.low);
+}
+
+static ssize_t lm3633_led_store_pattern_times(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+ int ret;
+
+ /*
+ * Sequence
+ *
+ * 1) Read pattern time data (unit: msec)
+ * 2) Update DELAY register
+ * 3) Update HIGH TIME register
+ * 4) Update LOW TIME register
+ * 5) Update RAMP TIME register
+ */
+
+ ret = sscanf(buf, "%u %u %u %u %u", &lmu_led->time.delay,
+ &lmu_led->time.rise, &lmu_led->time.high,
+ &lmu_led->time.fall, &lmu_led->time.low);
+ if (ret != 5)
+ return -EINVAL;
+
+ mutex_lock(&lmu_led->chip->lock);
+
+ ret = lm3633_led_effect_request(LED_EFFECT_DELAY, lmu_led);
+ if (ret)
+ goto skip;
+
+ ret = lm3633_led_effect_request(LED_EFFECT_HIGHTIME, lmu_led);
+ if (ret)
+ goto skip;
+
+ ret = lm3633_led_effect_request(LED_EFFECT_LOWTIME, lmu_led);
+ if (ret)
+ goto skip;
+
+ switch (lmu_led->bank_id) {
+ case LM3633_LED_BANK_C:
+ case LM3633_LED_BANK_D:
+ case LM3633_LED_BANK_E:
+ ret = lm3633_led_effect_request(LED_EFFECT_PTN0_RAMPUP,
+ lmu_led);
+ if (ret)
+ goto skip;
+
+ ret = lm3633_led_effect_request(LED_EFFECT_PTN0_RAMPDN,
+ lmu_led);
+ if (ret)
+ goto skip;
+ break;
+ case LM3633_LED_BANK_F:
+ case LM3633_LED_BANK_G:
+ case LM3633_LED_BANK_H:
+ ret = lm3633_led_effect_request(LED_EFFECT_PTN1_RAMPUP,
+ lmu_led);
+ if (ret)
+ goto skip;
+
+ ret = lm3633_led_effect_request(LED_EFFECT_PTN1_RAMPDN,
+ lmu_led);
+ if (ret)
+ goto skip;
+ break;
+ default:
+ break;
+ }
+
+ mutex_unlock(&lmu_led->chip->lock);
+ return len;
+skip:
+ mutex_unlock(&lmu_led->chip->lock);
+ return ret;
+}
+
+static ssize_t lm3633_led_show_pattern_levels(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+
+ return sprintf(buf, "low brightness: %u, high brightness: %u\n",
+ lmu_led->level.low, lmu_led->level.high);
+}
+
+static ssize_t lm3633_led_store_pattern_levels(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+ unsigned int low, high;
+ int ret;
+
+ /*
+ * Sequence
+ *
+ * 1) Read pattern level data
+ * 2) Disable a bank before programming a pattern
+ * 3) Update LOW BRIGHTNESS register
+ * 4) Update HIGH BRIGHTNESS register
+ * 5) Update PATTERN ENABLE register
+ * 6) Enable the bank if required
+ */
+
+ ret = sscanf(buf, "%u %u", &low, &high);
+ if (ret != 2)
+ return -EINVAL;
+
+ low = min_t(unsigned int, low, LM3633_LED_MAX_BRIGHTNESS);
+ high = min_t(unsigned int, high, LM3633_LED_MAX_BRIGHTNESS);
+ lmu_led->level.low = (u8)low;
+ lmu_led->level.high = (u8)high;
+
+ mutex_lock(&lmu_led->chip->lock);
+ ret = lm3633_led_enable_bank(lmu_led, false);
+ if (ret)
+ goto skip;
+
+ ret = lm3633_led_effect_request(LED_EFFECT_LOWBRT, lmu_led);
+ if (ret)
+ goto skip;
+
+ ret = lm3633_led_effect_request(LED_EFFECT_HIGHBRT, lmu_led);
+ if (ret)
+ goto skip;
+
+ mutex_unlock(&lmu_led->chip->lock);
+ return len;
+skip:
+ mutex_unlock(&lmu_led->chip->lock);
+ return ret;
+}
+
+static ssize_t lm3633_led_run_pattern(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+ struct ti_lmu_led_chip *chip = lmu_led->chip;
+ u8 offset = lmu_led->bank_id + LM3633_LED_BANK_OFFSET;
+ u8 mask = LM3633_PATTERN_EN << offset;
+ unsigned long enable;
+ int ret;
+
+ if (kstrtoul(buf, 0, &enable))
+ return -EINVAL;
+
+ mutex_lock(&chip->lock);
+ if (enable)
+ ret = ti_lmu_update_bits(chip->lmu, LM3633_REG_PATTERN, mask,
+ mask);
+ else
+ ret = ti_lmu_update_bits(chip->lmu, LM3633_REG_PATTERN, mask,
+ 0);
+ if (ret) {
+ mutex_unlock(&chip->lock);
+ return ret;
+ }
+
+ if (enable)
+ lm3633_led_enable_bank(lmu_led, true);
+
+ mutex_unlock(&chip->lock);
+
+ return len;
+}
+
+static DEVICE_ATTR(pattern_times, S_IRUGO | S_IWUSR,
+ lm3633_led_show_pattern_times,
+ lm3633_led_store_pattern_times);
+static DEVICE_ATTR(pattern_levels, S_IRUGO | S_IWUSR,
+ lm3633_led_show_pattern_levels,
+ lm3633_led_store_pattern_levels);
+static DEVICE_ATTR(run_pattern, S_IWUSR, NULL,
+ lm3633_led_run_pattern);
+
+static struct attribute *lm3633_led_attributes[] = {
+ &dev_attr_pattern_times.attr,
+ &dev_attr_pattern_levels.attr,
+ &dev_attr_run_pattern.attr,
+ NULL,
+};
+
+static struct attribute_group lm3633_led_attr_group = {
+ .attrs = lm3633_led_attributes
+};
+
+static int lm3633_led_set_max_current(struct ti_lmu_led *lmu_led)
+{
+ u8 reg = LM3633_REG_IMAX_LVLED_BASE + lmu_led->bank_id;
+ enum ti_lmu_max_current imax = lmu_led->led_pdata->imax;
+
+ return ti_lmu_write_byte(lmu_led->chip->lmu, reg, imax);
+}
+
+static void lm3633_led_work(struct work_struct *_work)
+{
+ struct ti_lmu_led *lmu_led;
+ struct ti_lmu_led_chip *chip;
+
+ lmu_led = container_of(_work, struct ti_lmu_led, work);
+ chip = lmu_led->chip;
+
+ mutex_lock(&chip->lock);
+
+ ti_lmu_write_byte(chip->lmu,
+ LM3633_REG_BRT_LVLED_BASE + lmu_led->bank_id,
+ lmu_led->brightness);
+
+ if (lmu_led->brightness == 0)
+ lm3633_led_enable_bank(lmu_led, false);
+ else
+ lm3633_led_enable_bank(lmu_led, true);
+
+ mutex_unlock(&chip->lock);
+}
+
+static void lm3633_led_brightness_set(struct led_classdev *_cdev,
+ enum led_brightness brt_val)
+{
+ struct ti_lmu_led *lmu_led;
+
+ lmu_led = container_of(_cdev, struct ti_lmu_led, cdev);
+ lmu_led->brightness = brt_val;
+
+ schedule_work(&lmu_led->work);
+}
+
+static int lm3633_led_init(struct ti_lmu_led *lmu_led, int bank_id)
+{
+ struct device *dev = lmu_led->chip->dev;
+ char name[12];
+ int ret;
+
+ /*
+ * Sequence
+ *
+ * 1) Configure LED bank which is used for brightness control
+ * 2) Set max current for each output channel
+ * 3) Add LED device
+ * 4) Add sysfs attributes for LED pattern
+ */
+
+ ret = lm3633_led_config_bank(lmu_led);
+ if (ret) {
+ dev_err(dev, "Output bank register err: %d\n", ret);
+ return ret;
+ }
+
+ ret = lm3633_led_set_max_current(lmu_led);
+ if (ret) {
+ dev_err(dev, "Set max current err: %d\n", ret);
+ return ret;
+ }
+
+ lmu_led->cdev.max_brightness = LM3633_LED_MAX_BRIGHTNESS;
+ lmu_led->cdev.brightness_set = lm3633_led_brightness_set;
+ if (lmu_led->led_pdata->name) {
+ lmu_led->cdev.name = lmu_led->led_pdata->name;
+ } else {
+ snprintf(name, sizeof(name), "%s:%d", LM3633_DEFAULT_LED_NAME,
+ bank_id);
+ lmu_led->cdev.name = name;
+ }
+
+ ret = led_classdev_register(dev, &lmu_led->cdev);
+ if (ret) {
+ dev_err(dev, "LED register err: %d\n", ret);
+ return ret;
+ }
+
+ ret = sysfs_create_group(&lmu_led->cdev.dev->kobj,
+ &lm3633_led_attr_group);
+ if (ret) {
+ dev_err(dev, "LED sysfs err: %d\n", ret);
+ return ret;
+ }
+
+ INIT_WORK(&lmu_led->work, lm3633_led_work);
+
+ return 0;
+}
+
+static int lm3633_led_parse_dt(struct device *dev, struct ti_lmu *lmu)
+{
+ struct ti_lmu_led_platform_data *pdata;
+ struct device_node *node = dev->of_node;
+ struct device_node *child;
+ int num_leds;
+ int i = 0;
+ u8 imax_mA;
+
+ if (!node) {
+ dev_err(dev, "No device node exists\n");
+ return -ENODEV;
+ }
+
+ num_leds = of_get_child_count(node);
+ if (num_leds == 0) {
+ dev_err(dev, "No LED channels\n");
+ return -EINVAL;
+ }
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata) * num_leds, GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ for_each_child_of_node(node, child) {
+ of_property_read_string(child, "chan-name", &pdata[i].name);
+
+ /* Make LED strings */
+ pdata[i].led_string = 0;
+ if (of_find_property(child, "lvled1-used", NULL))
+ pdata[i].led_string |= LMU_LVLED1;
+ if (of_find_property(child, "lvled2-used", NULL))
+ pdata[i].led_string |= LMU_LVLED2;
+ if (of_find_property(child, "lvled3-used", NULL))
+ pdata[i].led_string |= LMU_LVLED3;
+ if (of_find_property(child, "lvled4-used", NULL))
+ pdata[i].led_string |= LMU_LVLED4;
+ if (of_find_property(child, "lvled5-used", NULL))
+ pdata[i].led_string |= LMU_LVLED5;
+ if (of_find_property(child, "lvled6-used", NULL))
+ pdata[i].led_string |= LMU_LVLED6;
+
+ of_property_read_u8(child, "max-current-milliamp", &imax_mA);
+ pdata[i].imax = ti_lmu_get_current_code(imax_mA);
+
+ i++;
+ }
+
+ lmu->pdata->led_pdata = pdata;
+ lmu->pdata->num_leds = num_leds;
+
+ return 0;
+}
+
+static int lm3633_led_probe(struct platform_device *pdev)
+{
+ struct ti_lmu *lmu = dev_get_drvdata(pdev->dev.parent);
+ struct ti_lmu_led_chip *chip;
+ struct ti_lmu_led *lmu_led, *each;
+ struct device *dev = &pdev->dev;
+ int i, ret, num_leds;
+
+ if (!lmu->pdata->led_pdata) {
+ if (IS_ENABLED(CONFIG_OF))
+ ret = lm3633_led_parse_dt(dev, lmu);
+ else
+ return -ENODEV;
+
+ if (ret)
+ return ret;
+ }
+
+ num_leds = lmu->pdata->num_leds;
+ if (num_leds > LM3633_MAX_LEDS || num_leds <= 0) {
+ dev_err(dev, "Invalid num_leds: %d\n", num_leds);
+ return -EINVAL;
+ }
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = dev;
+ chip->lmu = lmu;
+
+ lmu_led = devm_kzalloc(dev, sizeof(*lmu_led) * num_leds, GFP_KERNEL);
+ if (!lmu_led)
+ return -ENOMEM;
+
+ for (i = 0; i < num_leds; i++) {
+ each = lmu_led + i;
+ each->bank_id = 0;
+ each->chip = chip;
+ each->led_pdata = lmu->pdata->led_pdata + i;
+
+ ret = lm3633_led_init(each, i);
+ if (ret) {
+ dev_err(dev, "Initialize a LED err: %d\n", ret);
+ goto cleanup_leds;
+ }
+ }
+
+ chip->num_leds = num_leds;
+ mutex_init(&chip->lock);
+ platform_set_drvdata(pdev, lmu_led);
+
+ return 0;
+
+cleanup_leds:
+ while (--i >= 0) {
+ each = lmu_led + i;
+ led_classdev_unregister(&each->cdev);
+ }
+ return ret;
+}
+
+static int lm3633_led_remove(struct platform_device *pdev)
+{
+ struct ti_lmu_led *lmu_led = platform_get_drvdata(pdev);
+ struct ti_lmu_led *each;
+ int i;
+
+ for (i = 0; i < lmu_led->chip->num_leds; i++) {
+ each = lmu_led + i;
+ led_classdev_unregister(&each->cdev);
+ flush_work(&each->work);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id lm3633_led_of_match[] = {
+ { .compatible = "ti,lm3633-leds", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lm3633_led_of_match);
+#endif
+
+static struct platform_driver lm3633_led_driver = {
+ .probe = lm3633_led_probe,
+ .remove = lm3633_led_remove,
+ .driver = {
+ .name = "lm3633-leds",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(lm3633_led_of_match),
+ },
+};
+module_platform_driver(lm3633_led_driver);
+
+MODULE_DESCRIPTION("TI LM3633 LED Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3633-leds");
--
1.7.9.5
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/