Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > linux.kernel > #1277999 > unrolled thread

[PATCH v2 8/9] leds: add LM3633 driver

Started byMilo Kim <milo.kim@ti.com>
First post2015-11-26 08:00 +0100
Last post2015-11-30 13:30 +0100
Articles 5 — 4 participants

Back to article view | Back to linux.kernel

This discussion starts older than the indexed window; earlier articles aren't shown. The article labeled Started by below is the oldest one visible, not the original post.


Contents

  [PATCH v2 8/9] leds: add LM3633 driver Milo Kim <milo.kim@ti.com> - 2015-11-26 08:00 +0100
    Re: [PATCH v2 8/9] leds: add LM3633 driver Jacek Anaszewski <j.anaszewski@samsung.com> - 2015-11-27 12:20 +0100
      Re: [PATCH v2 8/9] leds: add LM3633 driver Jacek Anaszewski <jacek.anaszewski@gmail.com> - 2015-11-28 09:30 +0100
      Re: [PATCH v2 8/9] leds: add LM3633 driver "Kim, Milo" <milo.kim@ti.com> - 2015-11-30 09:50 +0100
        Re: [PATCH v2 8/9] leds: add LM3633 driver Jacek Anaszewski <j.anaszewski@samsung.com> - 2015-11-30 13:30 +0100

#1277999 — [PATCH v2 8/9] leds: add LM3633 driver

FromMilo Kim <milo.kim@ti.com>
Date2015-11-26 08:00 +0100
Subject[PATCH v2 8/9] leds: add LM3633 driver
Message-ID<qyYH8-1f6-15@gated-at.bofh.it>
LM3633 LED driver supports generic LED functions and pattern generation.
Pattern is generated through the sysfs. ABI documentation is also added.

Device creation from device tree
--------------------------------
  LED channel name, LED string usage and max current settings are
  configured inside the DT.

LED dimming pattern generation
------------------------------
  LM3633 supports programmable dimming pattern generator.
  To enable it, eight attributes are used. Sysfs ABI describes it.
  - Time domain
    : 'pattern_time_delay', 'pattern_time_rise', 'pattern_time_high',
      'pattern_time_fall' and 'pattern_time_low'.
  - Level domain
    : 'pattern_brightness_low' and 'pattern_brightness_high'.
  - Start or stop
    : 'run_pattern'

LMU fault monitor event handling
--------------------------------
  As soon as LMU fault monitoring is done, LMU device is reset. So LED
  device should be reinitialized. lm3633_led_fault_monitor_notifier() is
  used for this purpose.

Data structure
--------------
  ti_lmu_led:         LED output channel data.
  ti_lmu_led_chip:    LED device data. One LED device can have multiple
                      LED channel data.

Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
Cc: linux-leds@vger.kernel.org 
Cc: Lee Jones <lee.jones@linaro.org> 
Cc: Mark Brown <broonie@kernel.org>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 Documentation/ABI/testing/sysfs-class-led-lm3633 |  97 +++
 drivers/leds/Kconfig                             |  10 +
 drivers/leds/Makefile                            |   1 +
 drivers/leds/leds-lm3633.c                       | 840 +++++++++++++++++++++++
 4 files changed, 948 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-led-lm3633
 create mode 100644 drivers/leds/leds-lm3633.c

diff --git a/Documentation/ABI/testing/sysfs-class-led-lm3633 b/Documentation/ABI/testing/sysfs-class-led-lm3633
new file mode 100644
index 0000000..46217d4
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-led-lm3633
@@ -0,0 +1,97 @@
+LM3633 LED driver generates programmable pattern via the sysfs.
+
+LED Pattern Generator Structure
+
+                            (3)
+  (a) --------------->  ___________
+                       /           \
+                  (2) /             \ (4)
+  (b) ----> _________/               \_________  ...
+               (1)                       (5)
+
+                     |<-----   period   -----> |
+
+What:		/sys/class/leds/<led>/pattern_time_delay
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern startup delay. Please refer to (1).
+                Range is from 0 to 9700. Unit is millisecond.
+
+What:		/sys/class/leds/<led>/pattern_time_rise
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern rising dimming time. Please refer to (2).
+                Range is from 0 to 16000. Unit is millisecond.
+
+What:		/sys/class/leds/<led>/pattern_time_high
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern high level time. Please refer to (3).
+                It means how much time stays at high level.
+                Range is from 0 to 9700. Unit is millisecond.
+
+What:		/sys/class/leds/<led>/pattern_time_fall
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern falling dimming time. Please refer to (4).
+                Range is from 0 to 16000. Unit is millisecond.
+
+What:		/sys/class/leds/<led>/pattern_time_low
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern low level time. Please refer to (5).
+                It means how much time stays at low level.
+                Range is from 0 to 9700. Unit is millisecond.
+
+What:		/sys/class/leds/<led>/pattern_brightness_high
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern brightness value at high level.
+                Please refer to (a). Range is from 0 to max brightness value.
+
+What:		/sys/class/leds/<led>/pattern_brightness_low
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                Set pattern brightness value at low level.
+                Please refer to (b). Range is from 0 to max brightness value.
+
+What:		/sys/class/leds/<led>/run_pattern
+Date:		Dec 2015
+KernelVersion:	4.5
+Contact:	Milo Kim <milo.kim@ti.com>
+Description:	write only
+                It is used for activating or deactivating programmed LED
+                dimming pattern. Please make sure pattern parameters
+                should be written prior to accessing this attribute.
+
+                1 : activate programmed pattern
+                0 : deactivate the pattern
+
+                Example:
+                To run the pattern,
+
+                echo 0 > /sys/class/leds/<led>/pattern_time_delay
+                echo 200 > /sys/class/leds/<led>/pattern_time_rise
+                echo 300 > /sys/class/leds/<led>/pattern_time_high
+                echo 100 > /sys/class/leds/<led>/pattern_time_fall
+                echo 400 > /sys/class/leds/<led>/pattern_time_low
+                echo 0 > /sys/class/leds/<led>/pattern_brightness_low
+                echo 255 > /sys/class/leds/<led>/pattern_brightness_high
+                echo 1 > /sys/class/leds/<led>/run_pattern
+
+                To stop running pattern,
+                echo 0 > /sys/class/leds/<led>/run_pattern
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index b1ab8bd..ed071ac 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -88,6 +88,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 e9d53092..e183b7d 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -14,6 +14,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..9c391ca
--- /dev/null
+++ b/drivers/leds/leds-lm3633.c
@@ -0,0 +1,840 @@
+/*
+ * TI LM3633 LED driver
+ *
+ * Copyright 2015 Texas Instruments
+ *
+ * Author: Milo Kim <milo.kim@ti.com>
+ *
+ * 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.
+ */
+
+#include <linux/bitops.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define LM3633_MAX_PWM				255
+#define LM3633_MIN_CURRENT			5000
+#define LM3633_MAX_CURRENT			30000
+#define LM3633_MAX_PERIOD			9700
+#define LM3633_SHORT_TIMESTEP			16
+#define LM3633_LONG_TIMESTEP			131
+#define LM3633_TIME_OFFSET			61
+#define LM3633_PATTERN_REG_OFFSET		16
+#define LM3633_IMAX_OFFSET			6
+
+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 ti_lmu_led_chip
+ *
+ * @dev:		Parent device pointer
+ * @lmu:		LMU structure. Used for register R/W access.
+ * @lock:		Secure handling for multiple user interface access
+ * @lmu_led:		Multiple LED strings
+ * @num_leds:		Number of LED strings
+ * @nb:			Notifier block for handling LMU fault monitor event
+ *
+ * One LED chip can have multiple LED strings.
+ */
+struct ti_lmu_led_chip {
+	struct device *dev;
+	struct ti_lmu *lmu;
+	struct mutex lock;
+	struct ti_lmu_led *lmu_led;
+	int num_leds;
+	struct notifier_block nb;
+};
+
+/**
+ * struct ti_lmu_led
+ *
+ * @chip:		Pointer to parent LED device
+ * @bank_id:		LED bank ID
+ * @cdev:		LED subsystem device structure
+ * @name:		LED channel name
+ * @led_sources:	LED output channel configuration.
+ *			Bit mask is set on parsing DT.
+ * @max_brightness:	Max brightness value.
+ *			Is is determined by led-max-microamp.
+ *
+ * Each LED device has its own channel configuration.
+ * For chip control, parent chip data structure is used.
+ */
+struct ti_lmu_led {
+	struct ti_lmu_led_chip *chip;
+	enum lm3633_led_bank_id bank_id;
+	struct led_classdev cdev;
+	const char *name;
+	unsigned long led_sources;
+	u8 max_brightness;
+};
+
+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 u8 lm3633_led_get_enable_mask(struct ti_lmu_led *lmu_led)
+{
+	return 1 << (lmu_led->bank_id + LM3633_LED_BANK_OFFSET);
+}
+
+static int lm3633_led_enable_bank(struct ti_lmu_led *lmu_led)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	u8 mask = lm3633_led_get_enable_mask(lmu_led);
+
+	return regmap_update_bits(regmap, LM3633_REG_ENABLE, mask, mask);
+}
+
+static int lm3633_led_disable_bank(struct ti_lmu_led *lmu_led)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	u8 mask = lm3633_led_get_enable_mask(lmu_led);
+
+	return regmap_update_bits(regmap, LM3633_REG_ENABLE, mask, 0);
+}
+
+static int lm3633_led_config_bank(struct ti_lmu_led *lmu_led)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	const u8 group_led[] = { 0, BIT(0), BIT(0), 0, BIT(3), BIT(3), };
+	const 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,
+	};
+	const 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,
+	};
+	int i, ret;
+	u8 val;
+
+	/*
+	 * 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 0 and LED 3 are fixed, so no assignment is required */
+		if (i == 0 || i == 3)
+			continue;
+
+		if (test_bit(i, &lmu_led->led_sources)) {
+			if (lmu_led->led_sources & group_led[i]) {
+				lmu_led->bank_id = default_id[i];
+				val = 0;
+			} else {
+				lmu_led->bank_id = separate_id[i];
+				val = BIT(i);
+			}
+
+			ret = regmap_update_bits(regmap, LM3633_REG_BANK_SEL,
+						 BIT(i), val);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static u8 lm3633_convert_time_to_index(unsigned int msec)
+{
+	u8 idx, offset;
+
+	/*
+	 * Find an approximate index
+	 *
+	 *      0 <= time <= 1000 : 16ms step
+	 *   1000 <  time <= 9700 : 131ms step, base index is 61
+	 */
+
+	msec = min_t(int, msec, LM3633_MAX_PERIOD);
+
+	if (msec <= 1000) {
+		idx = msec / LM3633_SHORT_TIMESTEP;
+		if (idx > 1)
+			idx--;
+		offset = 0;
+	} else {
+		idx = (msec - 1000) / LM3633_LONG_TIMESTEP;
+		offset = LM3633_TIME_OFFSET;
+	}
+
+	return idx + offset;
+}
+
+static u8 lm3633_convert_ramp_to_index(unsigned int msec)
+{
+	const int ramp_table[] = { 2, 250, 500, 1000, 2000, 4000, 8000, 16000 };
+	int size = ARRAY_SIZE(ramp_table);
+	int i;
+
+	if (msec <= ramp_table[0])
+		return 0;
+
+	if (msec > ramp_table[size - 1])
+		return size - 1;
+
+	for (i = 1; i < size; i++) {
+		if (msec == ramp_table[i])
+			return i;
+
+		/* Find an approximate index by looking up the table */
+		if (msec > ramp_table[i - 1] && msec < ramp_table[i]) {
+			if (msec - ramp_table[i - 1] < ramp_table[i] - msec)
+				return i - 1;
+			else
+				return i;
+		}
+	}
+
+	return 0;
+}
+
+static int lm3633_led_update_linear_time(const char *buf,
+					 struct ti_lmu_led *lmu_led, u8 reg)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	unsigned long time;
+	u8 offset = lmu_led->bank_id * LM3633_PATTERN_REG_OFFSET;
+
+	/*
+	 * Time register addresses need offset number based on the LED bank.
+	 * Register values are index domain, so input time value should be
+	 * converted to index.
+	 */
+
+	if (kstrtoul(buf, 0, &time))
+		return -EINVAL;
+
+	return regmap_write(regmap, reg + offset,
+			    lm3633_convert_time_to_index(time));
+}
+
+static int lm3633_led_update_ramp_time(const char *buf,
+				       struct ti_lmu_led *lmu_led,
+				       u8 mask, u8 shift)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	unsigned long ramp_time;
+	u8 reg, val;
+
+	/*
+	 * Register values are index domain, so input time value should be
+	 * converted to index.
+	 */
+
+	if (kstrtoul(buf, 0, &ramp_time))
+		return -EINVAL;
+
+	switch (lmu_led->bank_id) {
+	case LM3633_LED_BANK_C:
+	case LM3633_LED_BANK_D:
+	case LM3633_LED_BANK_E:
+		reg = LM3633_REG_PTN0_RAMP;
+		break;
+	case LM3633_LED_BANK_F:
+	case LM3633_LED_BANK_G:
+	case LM3633_LED_BANK_H:
+		reg = LM3633_REG_PTN1_RAMP;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	val = lm3633_convert_ramp_to_index(ramp_time) << shift;
+	return regmap_update_bits(regmap, reg, mask, val);
+}
+
+static ssize_t lm3633_led_store_pattern_time_delay(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;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_update_linear_time(buf, lmu_led, LM3633_REG_PTN_DELAY);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t lm3633_led_store_pattern_time_high(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;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_update_linear_time(buf, lmu_led,
+					    LM3633_REG_PTN_HIGHTIME);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t lm3633_led_store_pattern_time_low(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;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_update_linear_time(buf, lmu_led,
+					    LM3633_REG_PTN_LOWTIME);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t lm3633_led_store_pattern_time_rise(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;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_update_ramp_time(buf, lmu_led, LM3633_PTN_RAMPUP_MASK,
+					  LM3633_PTN_RAMPUP_SHIFT);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t lm3633_led_store_pattern_time_fall(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;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_update_ramp_time(buf, lmu_led, LM3633_PTN_RAMPDN_MASK,
+				       LM3633_PTN_RAMPDN_SHIFT);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static int lm3633_led_set_pattern_brightness(const char *buf,
+					     struct ti_lmu_led *lmu_led, u8 reg)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	unsigned long brt;
+	int ret;
+
+	if (kstrtoul(buf, 0, &brt))
+		return -EINVAL;
+
+	ret = lm3633_led_disable_bank(lmu_led);
+	if (ret)
+		return ret;
+
+	brt = min_t(unsigned long, brt, lmu_led->max_brightness);
+
+	return regmap_write(regmap, reg, (u8)brt);
+}
+
+static ssize_t lm3633_led_store_pattern_brightness_low(struct device *dev,
+						  struct device_attribute *attr,
+						  const char *buf, size_t len)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+	u8 offset = lmu_led->bank_id * LM3633_PATTERN_REG_OFFSET;
+	u8 reg = LM3633_REG_PTN_LOWBRT + offset;
+	int ret;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_set_pattern_brightness(buf, lmu_led, reg);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static ssize_t lm3633_led_store_pattern_brightness_high(struct device *dev,
+						  struct device_attribute *attr,
+						  const char *buf, size_t len)
+{
+	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
+	u8 reg = LM3633_REG_PTN_HIGHBRT + lmu_led->bank_id;
+	int ret;
+
+	mutex_lock(&lmu_led->chip->lock);
+	ret = lm3633_led_set_pattern_brightness(buf, lmu_led, reg);
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static int lm3633_led_activate_pattern(struct ti_lmu_led *lmu_led)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	u8 mask = lm3633_led_get_enable_mask(lmu_led);
+	int ret;
+
+	ret = regmap_update_bits(regmap, LM3633_REG_PATTERN, mask, mask);
+	if (ret)
+		return ret;
+
+	return lm3633_led_enable_bank(lmu_led);
+}
+
+static int lm3633_led_deactivate_pattern(struct ti_lmu_led *lmu_led)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	u8 mask = lm3633_led_get_enable_mask(lmu_led);
+
+	return regmap_update_bits(regmap, LM3633_REG_PATTERN, mask, 0);
+}
+
+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);
+	unsigned long run;
+	int ret;
+
+	if (kstrtoul(buf, 0, &run))
+		return -EINVAL;
+
+	mutex_lock(&lmu_led->chip->lock);
+
+	if (!run)
+		ret = lm3633_led_deactivate_pattern(lmu_led);
+	else
+		ret = lm3633_led_activate_pattern(lmu_led);
+
+	mutex_unlock(&lmu_led->chip->lock);
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+#define LM3633_PATTERN_ATTR(name)	\
+DEVICE_ATTR(pattern_##name, S_IWUSR, NULL, lm3633_led_store_pattern_##name)
+
+static LM3633_PATTERN_ATTR(time_delay);
+static LM3633_PATTERN_ATTR(time_rise);
+static LM3633_PATTERN_ATTR(time_high);
+static LM3633_PATTERN_ATTR(time_fall);
+static LM3633_PATTERN_ATTR(time_low);
+static LM3633_PATTERN_ATTR(brightness_low);
+static LM3633_PATTERN_ATTR(brightness_high);
+static DEVICE_ATTR(run_pattern, S_IWUSR, NULL, lm3633_led_run_pattern);
+
+static struct attribute *lm3633_led_attrs[] = {
+	&dev_attr_pattern_time_delay.attr,
+	&dev_attr_pattern_time_rise.attr,
+	&dev_attr_pattern_time_high.attr,
+	&dev_attr_pattern_time_fall.attr,
+	&dev_attr_pattern_time_low.attr,
+	&dev_attr_pattern_brightness_low.attr,
+	&dev_attr_pattern_brightness_high.attr,
+	&dev_attr_run_pattern.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(lm3633_led);	/* lm3633_led_groups */
+
+static int lm3633_led_set_max_current(struct ti_lmu_led *lmu_led)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	u8 reg = LM3633_REG_IMAX_LVLED_BASE + lmu_led->bank_id;
+
+	return regmap_write(regmap, reg, LMU_IMAX_30mA);
+}
+
+static int lm3633_led_brightness_set(struct led_classdev *cdev,
+				     enum led_brightness brightness)
+{
+	struct ti_lmu_led *lmu_led = container_of(cdev, struct ti_lmu_led,
+						  cdev);
+	struct ti_lmu_led_chip *chip = lmu_led->chip;
+	struct regmap *regmap = chip->lmu->regmap;
+	u8 reg = LM3633_REG_BRT_LVLED_BASE + lmu_led->bank_id;
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = regmap_write(regmap, reg, brightness);
+	if (ret) {
+		mutex_unlock(&chip->lock);
+		return ret;
+	}
+
+	if (brightness == 0)
+		lm3633_led_disable_bank(lmu_led);
+	else
+		lm3633_led_enable_bank(lmu_led);
+
+	mutex_unlock(&chip->lock);
+
+	return 0;
+}
+
+static int lm3633_led_init(struct ti_lmu_led *lmu_led, int bank_id)
+{
+	struct device *dev = lmu_led->chip->dev;
+	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
+	 */
+
+	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 = lmu_led->max_brightness;
+	lmu_led->cdev.brightness_set_blocking = lm3633_led_brightness_set;
+	lmu_led->cdev.groups = lm3633_led_groups;
+	lmu_led->cdev.name = lmu_led->name;
+
+	ret = devm_led_classdev_register(dev, &lmu_led->cdev);
+	if (ret) {
+		dev_err(dev, "LED register err: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void lm3633_led_of_set_label(struct device_node *np,
+				    struct ti_lmu_led *lmu_led)
+{
+	const char *name;
+
+	if (!of_property_read_string(np, "label", &name))
+		lmu_led->name = name;
+	else
+		lmu_led->name = np->name;
+}
+
+static int lm3633_led_of_create_channel(struct device_node *np,
+					struct ti_lmu_led *lmu_led)
+{
+	int ret, num_sources;
+	u32 sources[LM3633_MAX_LEDS];
+
+	ret = of_property_count_u32_elems(np, "led-sources");
+	if (ret < 0 || ret > LM3633_MAX_LEDS)
+		return -EINVAL;
+
+	num_sources = ret;
+	ret = of_property_read_u32_array(np, "led-sources", sources,
+					 num_sources);
+	if (ret)
+		return ret;
+
+	lmu_led->led_sources = 0;
+	while (num_sources--)
+		set_bit(sources[num_sources], &lmu_led->led_sources);
+
+	return 0;
+}
+
+static u8 lm3633_led_convert_current_to_index(u32 imax_microamp)
+{
+	u8 imax_milliamp = imax_microamp / 1000;
+	const u8 imax_table[] = {
+		LMU_IMAX_6mA,  LMU_IMAX_7mA,  LMU_IMAX_8mA,  LMU_IMAX_9mA,
+		LMU_IMAX_10mA, LMU_IMAX_11mA, LMU_IMAX_12mA, LMU_IMAX_13mA,
+		LMU_IMAX_14mA, LMU_IMAX_15mA, LMU_IMAX_16mA, LMU_IMAX_17mA,
+		LMU_IMAX_18mA, LMU_IMAX_19mA, LMU_IMAX_20mA, LMU_IMAX_21mA,
+		LMU_IMAX_22mA, LMU_IMAX_23mA, LMU_IMAX_24mA, LMU_IMAX_25mA,
+		LMU_IMAX_26mA, LMU_IMAX_27mA, LMU_IMAX_28mA, LMU_IMAX_29mA,
+	};
+
+	/* Valid range is from 5mA to 30mA */
+	if (imax_milliamp <= 5)
+		return LMU_IMAX_5mA;
+
+	if (imax_milliamp >= 30)
+		return LMU_IMAX_30mA;
+
+	return imax_table[imax_milliamp - LM3633_IMAX_OFFSET];
+}
+
+static u8 lm3633_led_scale_max_brightness(struct ti_lmu_led *lmu_led, u32 imax)
+{
+	u8 max_current = lm3633_led_convert_current_to_index(imax);
+	const u8 max_brightness_table[] = {
+		[LMU_IMAX_5mA]  = 191,
+		[LMU_IMAX_6mA]  = 197,
+		[LMU_IMAX_7mA]  = 203,
+		[LMU_IMAX_8mA]  = 208,
+		[LMU_IMAX_9mA]  = 212,
+		[LMU_IMAX_10mA] = 216,
+		[LMU_IMAX_11mA] = 219,
+		[LMU_IMAX_12mA] = 222,
+		[LMU_IMAX_13mA] = 225,
+		[LMU_IMAX_14mA] = 228,
+		[LMU_IMAX_15mA] = 230,
+		[LMU_IMAX_16mA] = 233,
+		[LMU_IMAX_17mA] = 235,
+		[LMU_IMAX_18mA] = 237,
+		[LMU_IMAX_19mA] = 239,
+		[LMU_IMAX_20mA] = 241,
+		[LMU_IMAX_21mA] = 242,
+		[LMU_IMAX_22mA] = 244,
+		[LMU_IMAX_23mA] = 246,
+		[LMU_IMAX_24mA] = 247,
+		[LMU_IMAX_25mA] = 249,
+		[LMU_IMAX_26mA] = 250,
+		[LMU_IMAX_27mA] = 251,
+		[LMU_IMAX_28mA] = 253,
+		[LMU_IMAX_29mA] = 254,
+		[LMU_IMAX_30mA] = 255,
+	};
+
+	return max_brightness_table[max_current];
+}
+
+static void lm3633_led_of_get_max_brightness(struct device_node *np,
+					     struct ti_lmu_led *lmu_led)
+{
+	struct regmap *regmap = lmu_led->chip->lmu->regmap;
+	u32 imax = 0;
+	u8 mode = 0;
+
+	of_property_read_u32(np, "led-max-microamp", &imax);
+
+	if (imax <= LM3633_MIN_CURRENT)
+		imax = LM3633_MIN_CURRENT;
+	else
+		imax = min_t(unsigned int, imax, LM3633_MAX_CURRENT);
+
+	/*
+	 * Max brightness is determined by mapping mode - exponential or
+	 * linear.
+	 */
+	regmap_read(regmap, LM3633_REG_LED_MAPPING_MODE, (unsigned int *)&mode);
+
+	if (mode & LM3633_LED_EXPONENTIAL)
+		lmu_led->max_brightness =
+				lm3633_led_scale_max_brightness(lmu_led, imax);
+	else
+		lmu_led->max_brightness =
+				(imax * LM3633_MAX_PWM) / LM3633_MAX_CURRENT;
+}
+
+static int lm3633_led_of_create(struct ti_lmu_led_chip *chip,
+				struct device_node *np)
+{
+	struct device_node *child;
+	struct device *dev = chip->dev;
+	struct ti_lmu_led *lmu_led, *each;
+	int ret, num_leds;
+	int i = 0;
+
+	if (!np)
+		return -ENODEV;
+
+	num_leds = of_get_child_count(np);
+	if (num_leds == 0 || num_leds > LM3633_MAX_LEDS) {
+		dev_err(dev, "Invalid number of LEDs: %d\n", num_leds);
+		return -EINVAL;
+	}
+
+	lmu_led = devm_kzalloc(dev, sizeof(*lmu_led) * num_leds, GFP_KERNEL);
+	if (!lmu_led)
+		return -ENOMEM;
+
+	for_each_child_of_node(np, child) {
+		each = lmu_led + i;
+		each->bank_id = 0;
+		each->chip = chip;
+
+		lm3633_led_of_set_label(child, each);
+
+		ret = lm3633_led_of_create_channel(child, each);
+		if (ret) {
+			of_node_put(np);
+			return ret;
+		}
+
+		lm3633_led_of_get_max_brightness(child, each);
+		i++;
+	}
+
+	chip->lmu_led = lmu_led;
+	chip->num_leds = num_leds;
+
+	return 0;
+}
+
+static int lm3633_led_fault_monitor_notifier(struct notifier_block *nb,
+					     unsigned long action, void *unused)
+{
+	struct ti_lmu_led_chip *chip = container_of(nb, struct ti_lmu_led_chip,
+						    nb);
+	struct ti_lmu_led *each;
+	int i, ret;
+
+	/*
+	 * LMU fault monitor driver resets the device, so LED should be
+	 * re-configured after fault detection procedure is done.
+	 */
+	if (action == LMU_EVENT_MONITOR_DONE) {
+		for (i = 0; i < chip->num_leds; i++) {
+			each = chip->lmu_led + i;
+			ret = lm3633_led_config_bank(each);
+			if (ret) {
+				dev_err(chip->dev,
+					"Output bank register err: %d\n", ret);
+				return NOTIFY_STOP;
+			}
+
+			ret = lm3633_led_set_max_current(each);
+			if (ret) {
+				dev_err(chip->dev, "Set max current err: %d\n",
+					ret);
+				return NOTIFY_STOP;
+			}
+
+			led_set_brightness(&each->cdev, each->cdev.brightness);
+		}
+	}
+
+	return NOTIFY_OK;
+}
+
+static int lm3633_led_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ti_lmu *lmu = dev_get_drvdata(dev->parent);
+	struct ti_lmu_led_chip *chip;
+	struct ti_lmu_led *each;
+	int i, ret;
+
+	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->dev = dev;
+	chip->lmu = lmu;
+
+	ret = lm3633_led_of_create(chip, dev->of_node);
+	if (ret)
+		return ret;
+
+	mutex_init(&chip->lock);
+
+	for (i = 0; i < chip->num_leds; i++) {
+		each = chip->lmu_led + i;
+
+		ret = lm3633_led_init(each, i);
+		if (ret) {
+			dev_err(dev, "LED initialization err: %d\n", ret);
+			return ret;
+		}
+	}
+
+	/*
+	 * Notifier callback is required because LED device needs
+	 * reconfiguration after open/short circuit fault monitoring
+	 * by ti-lmu-fault-monitor driver.
+	 */
+	chip->nb.notifier_call = lm3633_led_fault_monitor_notifier;
+	ret = blocking_notifier_chain_register(&chip->lmu->notifier, &chip->nb);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, chip);
+
+	return 0;
+}
+
+static int lm3633_led_remove(struct platform_device *pdev)
+{
+	struct ti_lmu_led_chip *chip = platform_get_drvdata(pdev);
+	int i;
+
+	for (i = 0; i < chip->num_leds; i++)
+		lm3633_led_deactivate_pattern(chip->lmu_led + i);
+
+	blocking_notifier_chain_unregister(&chip->lmu->notifier, &chip->nb);
+
+	return 0;
+}
+
+static struct platform_driver lm3633_led_driver = {
+	.probe = lm3633_led_probe,
+	.remove = lm3633_led_remove,
+	.driver = {
+		.name = "lm3633-leds",
+	},
+};
+
+module_platform_driver(lm3633_led_driver);
+
+MODULE_DESCRIPTION("TI LM3633 LED Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:lm3633-leds");
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[toc] | [next] | [standalone]


#1278758

FromJacek Anaszewski <j.anaszewski@samsung.com>
Date2015-11-27 12:20 +0100
Message-ID<qzpei-27s-21@gated-at.bofh.it>
In reply to#1277999
Hi Milo,

Thanks for the update. I have few comments below.

On 11/26/2015 07:57 AM, Milo Kim wrote:
> LM3633 LED driver supports generic LED functions and pattern generation.
> Pattern is generated through the sysfs. ABI documentation is also added.
>
> Device creation from device tree
> --------------------------------
>    LED channel name, LED string usage and max current settings are
>    configured inside the DT.
>
> LED dimming pattern generation
> ------------------------------
>    LM3633 supports programmable dimming pattern generator.
>    To enable it, eight attributes are used. Sysfs ABI describes it.
>    - Time domain
>      : 'pattern_time_delay', 'pattern_time_rise', 'pattern_time_high',
>        'pattern_time_fall' and 'pattern_time_low'.
>    - Level domain
>      : 'pattern_brightness_low' and 'pattern_brightness_high'.
>    - Start or stop
>      : 'run_pattern'
>
> LMU fault monitor event handling
> --------------------------------
>    As soon as LMU fault monitoring is done, LMU device is reset. So LED
>    device should be reinitialized. lm3633_led_fault_monitor_notifier() is
>    used for this purpose.
>
> Data structure
> --------------
>    ti_lmu_led:         LED output channel data.
>    ti_lmu_led_chip:    LED device data. One LED device can have multiple
>                        LED channel data.
>
> Cc: Jacek Anaszewski <j.anaszewski@samsung.com>
> Cc: linux-leds@vger.kernel.org
> Cc: Lee Jones <lee.jones@linaro.org>
> Cc: Mark Brown <broonie@kernel.org>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> ---
>   Documentation/ABI/testing/sysfs-class-led-lm3633 |  97 +++
>   drivers/leds/Kconfig                             |  10 +
>   drivers/leds/Makefile                            |   1 +
>   drivers/leds/leds-lm3633.c                       | 840 +++++++++++++++++++++++
>   4 files changed, 948 insertions(+)
>   create mode 100644 Documentation/ABI/testing/sysfs-class-led-lm3633
>   create mode 100644 drivers/leds/leds-lm3633.c
>
> diff --git a/Documentation/ABI/testing/sysfs-class-led-lm3633 b/Documentation/ABI/testing/sysfs-class-led-lm3633
> new file mode 100644
> index 0000000..46217d4
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-class-led-lm3633
> @@ -0,0 +1,97 @@
> +LM3633 LED driver generates programmable pattern via the sysfs.
> +
> +LED Pattern Generator Structure
> +
> +                            (3)
> +  (a) --------------->  ___________
> +                       /           \
> +                  (2) /             \ (4)
> +  (b) ----> _________/               \_________  ...
> +               (1)                       (5)
> +
> +                     |<-----   period   -----> |
> +
> +What:		/sys/class/leds/<led>/pattern_time_delay
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern startup delay. Please refer to (1).
> +                Range is from 0 to 9700. Unit is millisecond.
> +
> +What:		/sys/class/leds/<led>/pattern_time_rise
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern rising dimming time. Please refer to (2).
> +                Range is from 0 to 16000. Unit is millisecond.
> +
> +What:		/sys/class/leds/<led>/pattern_time_high
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern high level time. Please refer to (3).
> +                It means how much time stays at high level.
> +                Range is from 0 to 9700. Unit is millisecond.
> +
> +What:		/sys/class/leds/<led>/pattern_time_fall
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern falling dimming time. Please refer to (4).
> +                Range is from 0 to 16000. Unit is millisecond.
> +
> +What:		/sys/class/leds/<led>/pattern_time_low
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern low level time. Please refer to (5).
> +                It means how much time stays at low level.
> +                Range is from 0 to 9700. Unit is millisecond.
> +
> +What:		/sys/class/leds/<led>/pattern_brightness_high
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern brightness value at high level.
> +                Please refer to (a). Range is from 0 to max brightness value.
> +
> +What:		/sys/class/leds/<led>/pattern_brightness_low
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                Set pattern brightness value at low level.
> +                Please refer to (b). Range is from 0 to max brightness value.
> +
> +What:		/sys/class/leds/<led>/run_pattern
> +Date:		Dec 2015
> +KernelVersion:	4.5
> +Contact:	Milo Kim <milo.kim@ti.com>
> +Description:	write only
> +                It is used for activating or deactivating programmed LED
> +                dimming pattern. Please make sure pattern parameters
> +                should be written prior to accessing this attribute.
> +
> +                1 : activate programmed pattern
> +                0 : deactivate the pattern
> +
> +                Example:
> +                To run the pattern,
> +
> +                echo 0 > /sys/class/leds/<led>/pattern_time_delay
> +                echo 200 > /sys/class/leds/<led>/pattern_time_rise
> +                echo 300 > /sys/class/leds/<led>/pattern_time_high
> +                echo 100 > /sys/class/leds/<led>/pattern_time_fall
> +                echo 400 > /sys/class/leds/<led>/pattern_time_low
> +                echo 0 > /sys/class/leds/<led>/pattern_brightness_low
> +                echo 255 > /sys/class/leds/<led>/pattern_brightness_high
> +                echo 1 > /sys/class/leds/<led>/run_pattern
> +
> +                To stop running pattern,
> +                echo 0 > /sys/class/leds/<led>/run_pattern
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index b1ab8bd..ed071ac 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -88,6 +88,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 e9d53092..e183b7d 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -14,6 +14,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..9c391ca
> --- /dev/null
> +++ b/drivers/leds/leds-lm3633.c
> @@ -0,0 +1,840 @@
> +/*
> + * TI LM3633 LED driver
> + *
> + * Copyright 2015 Texas Instruments
> + *
> + * Author: Milo Kim <milo.kim@ti.com>
> + *
> + * 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.
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/kernel.h>
> +#include <linux/leds.h>
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/notifier.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#define LM3633_MAX_PWM				255
> +#define LM3633_MIN_CURRENT			5000
> +#define LM3633_MAX_CURRENT			30000
> +#define LM3633_MAX_PERIOD			9700
> +#define LM3633_SHORT_TIMESTEP			16
> +#define LM3633_LONG_TIMESTEP			131
> +#define LM3633_TIME_OFFSET			61
> +#define LM3633_PATTERN_REG_OFFSET		16
> +#define LM3633_IMAX_OFFSET			6
> +
> +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 ti_lmu_led_chip
> + *
> + * @dev:		Parent device pointer
> + * @lmu:		LMU structure. Used for register R/W access.
> + * @lock:		Secure handling for multiple user interface access
> + * @lmu_led:		Multiple LED strings

Could you clarify what "string" means here?

> + * @num_leds:		Number of LED strings

and here?

> + * @nb:			Notifier block for handling LMU fault monitor event
> + *
> + * One LED chip can have multiple LED strings.
> + */
> +struct ti_lmu_led_chip {
> +	struct device *dev;
> +	struct ti_lmu *lmu;
> +	struct mutex lock;
> +	struct ti_lmu_led *lmu_led;
> +	int num_leds;
> +	struct notifier_block nb;
> +};
> +
> +/**
> + * struct ti_lmu_led
> + *
> + * @chip:		Pointer to parent LED device
> + * @bank_id:		LED bank ID
> + * @cdev:		LED subsystem device structure
> + * @name:		LED channel name
> + * @led_sources:	LED output channel configuration.
> + *			Bit mask is set on parsing DT.
> + * @max_brightness:	Max brightness value.
> + *			Is is determined by led-max-microamp.
> + *
> + * Each LED device has its own channel configuration.
> + * For chip control, parent chip data structure is used.
> + */
> +struct ti_lmu_led {
> +	struct ti_lmu_led_chip *chip;
> +	enum lm3633_led_bank_id bank_id;
> +	struct led_classdev cdev;
> +	const char *name;

You have it in the struct led_classdev.

> +	unsigned long led_sources;
> +	u8 max_brightness;

This is also present in the struct led_classdev.

> +};
> +
> +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 u8 lm3633_led_get_enable_mask(struct ti_lmu_led *lmu_led)
> +{
> +	return 1 << (lmu_led->bank_id + LM3633_LED_BANK_OFFSET);
> +}
> +
> +static int lm3633_led_enable_bank(struct ti_lmu_led *lmu_led)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	u8 mask = lm3633_led_get_enable_mask(lmu_led);
> +
> +	return regmap_update_bits(regmap, LM3633_REG_ENABLE, mask, mask);
> +}
> +
> +static int lm3633_led_disable_bank(struct ti_lmu_led *lmu_led)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	u8 mask = lm3633_led_get_enable_mask(lmu_led);
> +
> +	return regmap_update_bits(regmap, LM3633_REG_ENABLE, mask, 0);
> +}
> +
> +static int lm3633_led_config_bank(struct ti_lmu_led *lmu_led)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	const u8 group_led[] = { 0, BIT(0), BIT(0), 0, BIT(3), BIT(3), };
> +	const 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,
> +	};
> +	const 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,
> +	};
> +	int i, ret;
> +	u8 val;
> +
> +	/*
> +	 * 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 0 and LED 3 are fixed, so no assignment is required */
> +		if (i == 0 || i == 3)
> +			continue;
> +
> +		if (test_bit(i, &lmu_led->led_sources)) {
> +			if (lmu_led->led_sources & group_led[i]) {
> +				lmu_led->bank_id = default_id[i];
> +				val = 0;
> +			} else {
> +				lmu_led->bank_id = separate_id[i];
> +				val = BIT(i);
> +			}
> +
> +			ret = regmap_update_bits(regmap, LM3633_REG_BANK_SEL,
> +						 BIT(i), val);
> +			if (ret)
> +				return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static u8 lm3633_convert_time_to_index(unsigned int msec)
> +{
> +	u8 idx, offset;
> +
> +	/*
> +	 * Find an approximate index

I think that we shouldn't approximate but clamp the msec
to the nearest possible device setting.

> +	 *
> +	 *      0 <= time <= 1000 : 16ms step
> +	 *   1000 <  time <= 9700 : 131ms step, base index is 61
> +	 */
> +
> +	msec = min_t(int, msec, LM3633_MAX_PERIOD);
> +
> +	if (msec <= 1000) {
> +		idx = msec / LM3633_SHORT_TIMESTEP;
> +		if (idx > 1)
> +			idx--;
> +		offset = 0;
> +	} else {
> +		idx = (msec - 1000) / LM3633_LONG_TIMESTEP;
> +		offset = LM3633_TIME_OFFSET;
> +	}
> +
> +	return idx + offset;
> +}
> +
> +static u8 lm3633_convert_ramp_to_index(unsigned int msec)
> +{
> +	const int ramp_table[] = { 2, 250, 500, 1000, 2000, 4000, 8000, 16000 };
> +	int size = ARRAY_SIZE(ramp_table);
> +	int i;
> +
> +	if (msec <= ramp_table[0])
> +		return 0;
> +
> +	if (msec > ramp_table[size - 1])
> +		return size - 1;
> +
> +	for (i = 1; i < size; i++) {
> +		if (msec == ramp_table[i])
> +			return i;
> +
> +		/* Find an approximate index by looking up the table */

Similarly here. So you should have an array of the possible msec
values and iterate through it to look for the nearest one.

> +		if (msec > ramp_table[i - 1] && msec < ramp_table[i]) {
> +			if (msec - ramp_table[i - 1] < ramp_table[i] - msec)
> +				return i - 1;
> +			else
> +				return i;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int lm3633_led_update_linear_time(const char *buf,
> +					 struct ti_lmu_led *lmu_led, u8 reg)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	unsigned long time;
> +	u8 offset = lmu_led->bank_id * LM3633_PATTERN_REG_OFFSET;
> +
> +	/*
> +	 * Time register addresses need offset number based on the LED bank.
> +	 * Register values are index domain, so input time value should be
> +	 * converted to index.
> +	 */
> +
> +	if (kstrtoul(buf, 0, &time))
> +		return -EINVAL;
> +
> +	return regmap_write(regmap, reg + offset,
> +			    lm3633_convert_time_to_index(time));
> +}
> +
> +static int lm3633_led_update_ramp_time(const char *buf,
> +				       struct ti_lmu_led *lmu_led,
> +				       u8 mask, u8 shift)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	unsigned long ramp_time;
> +	u8 reg, val;
> +
> +	/*
> +	 * Register values are index domain, so input time value should be
> +	 * converted to index.
> +	 */
> +
> +	if (kstrtoul(buf, 0, &ramp_time))
> +		return -EINVAL;
> +
> +	switch (lmu_led->bank_id) {
> +	case LM3633_LED_BANK_C:
> +	case LM3633_LED_BANK_D:
> +	case LM3633_LED_BANK_E:
> +		reg = LM3633_REG_PTN0_RAMP;
> +		break;
> +	case LM3633_LED_BANK_F:
> +	case LM3633_LED_BANK_G:
> +	case LM3633_LED_BANK_H:
> +		reg = LM3633_REG_PTN1_RAMP;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	val = lm3633_convert_ramp_to_index(ramp_time) << shift;

Please add empty line here.

> +	return regmap_update_bits(regmap, reg, mask, val);
> +}
> +
> +static ssize_t lm3633_led_store_pattern_time_delay(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;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_update_linear_time(buf, lmu_led, LM3633_REG_PTN_DELAY);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static ssize_t lm3633_led_store_pattern_time_high(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;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_update_linear_time(buf, lmu_led,
> +					    LM3633_REG_PTN_HIGHTIME);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static ssize_t lm3633_led_store_pattern_time_low(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;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_update_linear_time(buf, lmu_led,
> +					    LM3633_REG_PTN_LOWTIME);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static ssize_t lm3633_led_store_pattern_time_rise(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;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_update_ramp_time(buf, lmu_led, LM3633_PTN_RAMPUP_MASK,
> +					  LM3633_PTN_RAMPUP_SHIFT);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static ssize_t lm3633_led_store_pattern_time_fall(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;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_update_ramp_time(buf, lmu_led, LM3633_PTN_RAMPDN_MASK,
> +				       LM3633_PTN_RAMPDN_SHIFT);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static int lm3633_led_set_pattern_brightness(const char *buf,
> +					     struct ti_lmu_led *lmu_led, u8 reg)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	unsigned long brt;
> +	int ret;
> +
> +	if (kstrtoul(buf, 0, &brt))
> +		return -EINVAL;
> +
> +	ret = lm3633_led_disable_bank(lmu_led);
> +	if (ret)
> +		return ret;
> +
> +	brt = min_t(unsigned long, brt, lmu_led->max_brightness);
> +
> +	return regmap_write(regmap, reg, (u8)brt);
> +}
> +
> +static ssize_t lm3633_led_store_pattern_brightness_low(struct device *dev,
> +						  struct device_attribute *attr,
> +						  const char *buf, size_t len)
> +{
> +	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
> +	u8 offset = lmu_led->bank_id * LM3633_PATTERN_REG_OFFSET;
> +	u8 reg = LM3633_REG_PTN_LOWBRT + offset;
> +	int ret;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_set_pattern_brightness(buf, lmu_led, reg);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static ssize_t lm3633_led_store_pattern_brightness_high(struct device *dev,
> +						  struct device_attribute *attr,
> +						  const char *buf, size_t len)
> +{
> +	struct ti_lmu_led *lmu_led = to_ti_lmu_led(dev);
> +	u8 reg = LM3633_REG_PTN_HIGHBRT + lmu_led->bank_id;
> +	int ret;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +	ret = lm3633_led_set_pattern_brightness(buf, lmu_led, reg);
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static int lm3633_led_activate_pattern(struct ti_lmu_led *lmu_led)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	u8 mask = lm3633_led_get_enable_mask(lmu_led);
> +	int ret;
> +
> +	ret = regmap_update_bits(regmap, LM3633_REG_PATTERN, mask, mask);
> +	if (ret)
> +		return ret;
> +
> +	return lm3633_led_enable_bank(lmu_led);
> +}
> +
> +static int lm3633_led_deactivate_pattern(struct ti_lmu_led *lmu_led)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	u8 mask = lm3633_led_get_enable_mask(lmu_led);
> +
> +	return regmap_update_bits(regmap, LM3633_REG_PATTERN, mask, 0);
> +}
> +
> +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);
> +	unsigned long run;
> +	int ret;
> +
> +	if (kstrtoul(buf, 0, &run))
> +		return -EINVAL;
> +
> +	mutex_lock(&lmu_led->chip->lock);
> +
> +	if (!run)
> +		ret = lm3633_led_deactivate_pattern(lmu_led);
> +	else
> +		ret = lm3633_led_activate_pattern(lmu_led);
> +
> +	mutex_unlock(&lmu_led->chip->lock);
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +#define LM3633_PATTERN_ATTR(name)	\
> +DEVICE_ATTR(pattern_##name, S_IWUSR, NULL, lm3633_led_store_pattern_##name)
> +
> +static LM3633_PATTERN_ATTR(time_delay);
> +static LM3633_PATTERN_ATTR(time_rise);
> +static LM3633_PATTERN_ATTR(time_high);
> +static LM3633_PATTERN_ATTR(time_fall);
> +static LM3633_PATTERN_ATTR(time_low);
> +static LM3633_PATTERN_ATTR(brightness_low);
> +static LM3633_PATTERN_ATTR(brightness_high);
> +static DEVICE_ATTR(run_pattern, S_IWUSR, NULL, lm3633_led_run_pattern);
> +
> +static struct attribute *lm3633_led_attrs[] = {
> +	&dev_attr_pattern_time_delay.attr,
> +	&dev_attr_pattern_time_rise.attr,
> +	&dev_attr_pattern_time_high.attr,
> +	&dev_attr_pattern_time_fall.attr,
> +	&dev_attr_pattern_time_low.attr,
> +	&dev_attr_pattern_brightness_low.attr,
> +	&dev_attr_pattern_brightness_high.attr,
> +	&dev_attr_run_pattern.attr,
> +	NULL,
> +};
> +ATTRIBUTE_GROUPS(lm3633_led);	/* lm3633_led_groups */
> +
> +static int lm3633_led_set_max_current(struct ti_lmu_led *lmu_led)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	u8 reg = LM3633_REG_IMAX_LVLED_BASE + lmu_led->bank_id;
> +
> +	return regmap_write(regmap, reg, LMU_IMAX_30mA);
> +}
> +
> +static int lm3633_led_brightness_set(struct led_classdev *cdev,
> +				     enum led_brightness brightness)
> +{
> +	struct ti_lmu_led *lmu_led = container_of(cdev, struct ti_lmu_led,
> +						  cdev);
> +	struct ti_lmu_led_chip *chip = lmu_led->chip;
> +	struct regmap *regmap = chip->lmu->regmap;
> +	u8 reg = LM3633_REG_BRT_LVLED_BASE + lmu_led->bank_id;
> +	int ret;
> +
> +	mutex_lock(&chip->lock);
> +
> +	ret = regmap_write(regmap, reg, brightness);
> +	if (ret) {
> +		mutex_unlock(&chip->lock);
> +		return ret;
> +	}
> +
> +	if (brightness == 0)
> +		lm3633_led_disable_bank(lmu_led);

This should be checked at first, as I suppose that disabling
a bank suffices to turn the LED off.

> +	else
> +		lm3633_led_enable_bank(lmu_led);
> +
> +	mutex_unlock(&chip->lock);
> +
> +	return 0;
> +}
> +
> +static int lm3633_led_init(struct ti_lmu_led *lmu_led, int bank_id)
> +{
> +	struct device *dev = lmu_led->chip->dev;
> +	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
> +	 */
> +
> +	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 = lmu_led->max_brightness;
> +	lmu_led->cdev.brightness_set_blocking = lm3633_led_brightness_set;
> +	lmu_led->cdev.groups = lm3633_led_groups;
> +	lmu_led->cdev.name = lmu_led->name;
> +
> +	ret = devm_led_classdev_register(dev, &lmu_led->cdev);
> +	if (ret) {
> +		dev_err(dev, "LED register err: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void lm3633_led_of_set_label(struct device_node *np,
> +				    struct ti_lmu_led *lmu_led)
> +{
> +	const char *name;
> +
> +	if (!of_property_read_string(np, "label", &name))
> +		lmu_led->name = name;
> +	else
> +		lmu_led->name = np->name;
> +}
> +
> +static int lm3633_led_of_create_channel(struct device_node *np,
> +					struct ti_lmu_led *lmu_led)
> +{
> +	int ret, num_sources;
> +	u32 sources[LM3633_MAX_LEDS];
> +
> +	ret = of_property_count_u32_elems(np, "led-sources");
> +	if (ret < 0 || ret > LM3633_MAX_LEDS)
> +		return -EINVAL;
> +
> +	num_sources = ret;
> +	ret = of_property_read_u32_array(np, "led-sources", sources,
> +					 num_sources);
> +	if (ret)
> +		return ret;
> +
> +	lmu_led->led_sources = 0;
> +	while (num_sources--)
> +		set_bit(sources[num_sources], &lmu_led->led_sources);
> +
> +	return 0;
> +}
> +
> +static u8 lm3633_led_convert_current_to_index(u32 imax_microamp)
> +{
> +	u8 imax_milliamp = imax_microamp / 1000;
> +	const u8 imax_table[] = {
> +		LMU_IMAX_6mA,  LMU_IMAX_7mA,  LMU_IMAX_8mA,  LMU_IMAX_9mA,
> +		LMU_IMAX_10mA, LMU_IMAX_11mA, LMU_IMAX_12mA, LMU_IMAX_13mA,
> +		LMU_IMAX_14mA, LMU_IMAX_15mA, LMU_IMAX_16mA, LMU_IMAX_17mA,
> +		LMU_IMAX_18mA, LMU_IMAX_19mA, LMU_IMAX_20mA, LMU_IMAX_21mA,
> +		LMU_IMAX_22mA, LMU_IMAX_23mA, LMU_IMAX_24mA, LMU_IMAX_25mA,
> +		LMU_IMAX_26mA, LMU_IMAX_27mA, LMU_IMAX_28mA, LMU_IMAX_29mA,
> +	};
> +
> +	/* Valid range is from 5mA to 30mA */
> +	if (imax_milliamp <= 5)
> +		return LMU_IMAX_5mA;
> +
> +	if (imax_milliamp >= 30)
> +		return LMU_IMAX_30mA;
> +
> +	return imax_table[imax_milliamp - LM3633_IMAX_OFFSET];
> +}
> +
> +static u8 lm3633_led_scale_max_brightness(struct ti_lmu_led *lmu_led, u32 imax)
> +{
> +	u8 max_current = lm3633_led_convert_current_to_index(imax);
> +	const u8 max_brightness_table[] = {
> +		[LMU_IMAX_5mA]  = 191,
> +		[LMU_IMAX_6mA]  = 197,
> +		[LMU_IMAX_7mA]  = 203,
> +		[LMU_IMAX_8mA]  = 208,
> +		[LMU_IMAX_9mA]  = 212,
> +		[LMU_IMAX_10mA] = 216,
> +		[LMU_IMAX_11mA] = 219,
> +		[LMU_IMAX_12mA] = 222,
> +		[LMU_IMAX_13mA] = 225,
> +		[LMU_IMAX_14mA] = 228,
> +		[LMU_IMAX_15mA] = 230,
> +		[LMU_IMAX_16mA] = 233,
> +		[LMU_IMAX_17mA] = 235,
> +		[LMU_IMAX_18mA] = 237,
> +		[LMU_IMAX_19mA] = 239,
> +		[LMU_IMAX_20mA] = 241,
> +		[LMU_IMAX_21mA] = 242,
> +		[LMU_IMAX_22mA] = 244,
> +		[LMU_IMAX_23mA] = 246,
> +		[LMU_IMAX_24mA] = 247,
> +		[LMU_IMAX_25mA] = 249,
> +		[LMU_IMAX_26mA] = 250,
> +		[LMU_IMAX_27mA] = 251,
> +		[LMU_IMAX_28mA] = 253,
> +		[LMU_IMAX_29mA] = 254,
> +		[LMU_IMAX_30mA] = 255,
> +	};

After analyzing the subject one more time I think that we need to
change the approach regarding max brightness issue.

At first - we shouldn't fix max current to max possible register value.
Instead we should take led-max-microamp property and write its value
to the [0x22 + bank offset] registers.

With this approach whole 0-255 range of brightness levels will be
valid for the driver.

In effect all LMU_IMAX* enums seem to be not needed.


> +	return max_brightness_table[max_current];
> +}
> +
> +static void lm3633_led_of_get_max_brightness(struct device_node *np,
> +					     struct ti_lmu_led *lmu_led)
> +{
> +	struct regmap *regmap = lmu_led->chip->lmu->regmap;
> +	u32 imax = 0;
> +	u8 mode = 0;
> +
> +	of_property_read_u32(np, "led-max-microamp", &imax);
> +
> +	if (imax <= LM3633_MIN_CURRENT)
> +		imax = LM3633_MIN_CURRENT;
> +	else
> +		imax = min_t(unsigned int, imax, LM3633_MAX_CURRENT);
> +
> +	/*
> +	 * Max brightness is determined by mapping mode - exponential or
> +	 * linear.
> +	 */
> +	regmap_read(regmap, LM3633_REG_LED_MAPPING_MODE, (unsigned int *)&mode);
> +
> +	if (mode & LM3633_LED_EXPONENTIAL)
> +		lmu_led->max_brightness =
> +				lm3633_led_scale_max_brightness(lmu_led, imax);
> +	else
> +		lmu_led->max_brightness =
> +				(imax * LM3633_MAX_PWM) / LM3633_MAX_CURRENT;
> +}
> +
> +static int lm3633_led_of_create(struct ti_lmu_led_chip *chip,
> +				struct device_node *np)
> +{
> +	struct device_node *child;
> +	struct device *dev = chip->dev;
> +	struct ti_lmu_led *lmu_led, *each;
> +	int ret, num_leds;
> +	int i = 0;
> +
> +	if (!np)
> +		return -ENODEV;
> +
> +	num_leds = of_get_child_count(np);
> +	if (num_leds == 0 || num_leds > LM3633_MAX_LEDS) {
> +		dev_err(dev, "Invalid number of LEDs: %d\n", num_leds);
> +		return -EINVAL;
> +	}
> +
> +	lmu_led = devm_kzalloc(dev, sizeof(*lmu_led) * num_leds, GFP_KERNEL);
> +	if (!lmu_led)
> +		return -ENOMEM;
> +
> +	for_each_child_of_node(np, child) {
> +		each = lmu_led + i;
> +		each->bank_id = 0;
> +		each->chip = chip;
> +
> +		lm3633_led_of_set_label(child, each);
> +
> +		ret = lm3633_led_of_create_channel(child, each);
> +		if (ret) {
> +			of_node_put(np);
> +			return ret;
> +		}
> +
> +		lm3633_led_of_get_max_brightness(child, each);
> +		i++;
> +	}
> +
> +	chip->lmu_led = lmu_led;
> +	chip->num_leds = num_leds;
> +
> +	return 0;
> +}
> +
> +static int lm3633_led_fault_monitor_notifier(struct notifier_block *nb,
> +					     unsigned long action, void *unused)
> +{
> +	struct ti_lmu_led_chip *chip = container_of(nb, struct ti_lmu_led_chip,
> +						    nb);
> +	struct ti_lmu_led *each;
> +	int i, ret;
> +
> +	/*
> +	 * LMU fault monitor driver resets the device, so LED should be
> +	 * re-configured after fault detection procedure is done.
> +	 */
> +	if (action == LMU_EVENT_MONITOR_DONE) {
> +		for (i = 0; i < chip->num_leds; i++) {
> +			each = chip->lmu_led + i;
> +			ret = lm3633_led_config_bank(each);
> +			if (ret) {
> +				dev_err(chip->dev,
> +					"Output bank register err: %d\n", ret);
> +				return NOTIFY_STOP;
> +			}
> +
> +			ret = lm3633_led_set_max_current(each);
> +			if (ret) {
> +				dev_err(chip->dev, "Set max current err: %d\n",
> +					ret);
> +				return NOTIFY_STOP;
> +			}
> +
> +			led_set_brightness(&each->cdev, each->cdev.brightness);
> +		}
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +static int lm3633_led_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct ti_lmu *lmu = dev_get_drvdata(dev->parent);
> +	struct ti_lmu_led_chip *chip;
> +	struct ti_lmu_led *each;
> +	int i, ret;
> +
> +	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
> +	if (!chip)
> +		return -ENOMEM;
> +
> +	chip->dev = dev;
> +	chip->lmu = lmu;
> +
> +	ret = lm3633_led_of_create(chip, dev->of_node);
> +	if (ret)
> +		return ret;
> +
> +	mutex_init(&chip->lock);
> +
> +	for (i = 0; i < chip->num_leds; i++) {
> +		each = chip->lmu_led + i;
> +
> +		ret = lm3633_led_init(each, i);
> +		if (ret) {
> +			dev_err(dev, "LED initialization err: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	/*
> +	 * Notifier callback is required because LED device needs
> +	 * reconfiguration after open/short circuit fault monitoring
> +	 * by ti-lmu-fault-monitor driver.
> +	 */
> +	chip->nb.notifier_call = lm3633_led_fault_monitor_notifier;
> +	ret = blocking_notifier_chain_register(&chip->lmu->notifier, &chip->nb);
> +	if (ret)
> +		return ret;
> +
> +	platform_set_drvdata(pdev, chip);
> +
> +	return 0;
> +}
> +
> +static int lm3633_led_remove(struct platform_device *pdev)
> +{
> +	struct ti_lmu_led_chip *chip = platform_get_drvdata(pdev);
> +	int i;
> +
> +	for (i = 0; i < chip->num_leds; i++)
> +		lm3633_led_deactivate_pattern(chip->lmu_led + i);
> +
> +	blocking_notifier_chain_unregister(&chip->lmu->notifier, &chip->nb);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver lm3633_led_driver = {
> +	.probe = lm3633_led_probe,
> +	.remove = lm3633_led_remove,
> +	.driver = {
> +		.name = "lm3633-leds",
> +	},
> +};
> +
> +module_platform_driver(lm3633_led_driver);
> +
> +MODULE_DESCRIPTION("TI LM3633 LED Driver");
> +MODULE_AUTHOR("Milo Kim");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:lm3633-leds");
>


-- 
Best Regards,
Jacek Anaszewski
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[toc] | [prev] | [next] | [standalone]


#1279112

FromJacek Anaszewski <jacek.anaszewski@gmail.com>
Date2015-11-28 09:30 +0100
Message-ID<qzJ3k-6pq-1@gated-at.bofh.it>
In reply to#1278758
On 11/27/2015 12:19 PM, Jacek Anaszewski wrote:
> Hi Milo,
>
> Thanks for the update. I have few comments below.
>
[...]
>> +static u8 lm3633_led_scale_max_brightness(struct ti_lmu_led *lmu_led,
>> u32 imax)
>> +{
>> +    u8 max_current = lm3633_led_convert_current_to_index(imax);
>> +    const u8 max_brightness_table[] = {
>> +        [LMU_IMAX_5mA]  = 191,
>> +        [LMU_IMAX_6mA]  = 197,
>> +        [LMU_IMAX_7mA]  = 203,
>> +        [LMU_IMAX_8mA]  = 208,
>> +        [LMU_IMAX_9mA]  = 212,
>> +        [LMU_IMAX_10mA] = 216,
>> +        [LMU_IMAX_11mA] = 219,
>> +        [LMU_IMAX_12mA] = 222,
>> +        [LMU_IMAX_13mA] = 225,
>> +        [LMU_IMAX_14mA] = 228,
>> +        [LMU_IMAX_15mA] = 230,
>> +        [LMU_IMAX_16mA] = 233,
>> +        [LMU_IMAX_17mA] = 235,
>> +        [LMU_IMAX_18mA] = 237,
>> +        [LMU_IMAX_19mA] = 239,
>> +        [LMU_IMAX_20mA] = 241,
>> +        [LMU_IMAX_21mA] = 242,
>> +        [LMU_IMAX_22mA] = 244,
>> +        [LMU_IMAX_23mA] = 246,
>> +        [LMU_IMAX_24mA] = 247,
>> +        [LMU_IMAX_25mA] = 249,
>> +        [LMU_IMAX_26mA] = 250,
>> +        [LMU_IMAX_27mA] = 251,
>> +        [LMU_IMAX_28mA] = 253,
>> +        [LMU_IMAX_29mA] = 254,
>> +        [LMU_IMAX_30mA] = 255,
>> +    };
>
> After analyzing the subject one more time I think that we need to
> change the approach regarding max brightness issue.
>
> At first - we shouldn't fix max current to max possible register value.
> Instead we should take led-max-microamp property and write its value
> to the [0x22 + bank offset] registers.

It was of course a mental shortcut. The value to write should be
calculated by transforming the formula given next to the register
documentation:

11111 = 29.8 mA
0.8mA steps, FS = 5mA + code * 0.8 mA

code is here the [0x22 + bank_offset] register value.

Effectively, the formula to calculate the register value basing
on led-max-microamp value should be:

FS = led-max-microamp - 5000 / 800

E.g. for 20.2 mA:

FS = (20200 - 5000) / 800 = 19

for 29.8 mA:

FS = (29800 - 5000) / 800 = 31

for 5 mA:

FS = (5000 - 5000) / 800 = 0

 From the above min, max and step values for led-max-microamp
are 5000, 29800 and 800 respectively. Please correct DT documentation
accordingly.

> With this approach whole 0-255 range of brightness levels will be
> valid for the driver.
>
> In effect all LMU_IMAX* enums seem to be not needed.


-- 
Best Regards,
Jacek Anaszewski
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[toc] | [prev] | [next] | [standalone]


#1279690

From"Kim, Milo" <milo.kim@ti.com>
Date2015-11-30 09:50 +0100
Message-ID<qAsjM-1hf-21@gated-at.bofh.it>
In reply to#1278758
Hi Jacek,

Thanks for your detailed feedback. Please refer to my comments below.

On 11/27/2015 8:19 PM, Jacek Anaszewski wrote:
> Hi Milo,
>
> Thanks for the update. I have few comments below.
>
>> diff --git a/drivers/leds/leds-lm3633.c b/drivers/leds/leds-lm3633.c
>> new file mode 100644
>> index 0000000..9c391ca
>> --- /dev/null
>> +++ b/drivers/leds/leds-lm3633.c
>> @@ -0,0 +1,840 @@
>> +/*
>> + * TI LM3633 LED driver
>> + *
>> + * Copyright 2015 Texas Instruments
>> + *
>> + * Author: Milo Kim <milo.kim@ti.com>
>> + *
>> + * 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.
>> + */
>> +
>> +#include <linux/bitops.h>
>> +#include <linux/kernel.h>
>> +#include <linux/leds.h>
>> +#include <linux/mfd/ti-lmu.h>
>> +#include <linux/mfd/ti-lmu-register.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/notifier.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/slab.h>
>> +
>> +#define LM3633_MAX_PWM				255
>> +#define LM3633_MIN_CURRENT			5000
>> +#define LM3633_MAX_CURRENT			30000
>> +#define LM3633_MAX_PERIOD			9700
>> +#define LM3633_SHORT_TIMESTEP			16
>> +#define LM3633_LONG_TIMESTEP			131
>> +#define LM3633_TIME_OFFSET			61
>> +#define LM3633_PATTERN_REG_OFFSET		16
>> +#define LM3633_IMAX_OFFSET			6
>> +
>> +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 ti_lmu_led_chip
>> + *
>> + * @dev:		Parent device pointer
>> + * @lmu:		LMU structure. Used for register R/W access.
>> + * @lock:		Secure handling for multiple user interface access
>> + * @lmu_led:		Multiple LED strings
>
> Could you clarify what "string" means here?
>
>> + * @num_leds:		Number of LED strings
>
> and here?

Oops! I forgot to replace this term with 'output channel'. Thanks for 
catching this.

>> +static u8 lm3633_convert_time_to_index(unsigned int msec)
>> +{
>> +	u8 idx, offset;
>> +
>> +	/*
>> +	 * Find an approximate index
>
> I think that we shouldn't approximate but clamp the msec
> to the nearest possible device setting.
>
>> +	 *
>> +	 *      0 <= time <= 1000 : 16ms step
>> +	 *   1000 <  time <= 9700 : 131ms step, base index is 61
>> +	 */
>> +
>> +	msec = min_t(int, msec, LM3633_MAX_PERIOD);
>> +
>> +	if (msec <= 1000) {
>> +		idx = msec / LM3633_SHORT_TIMESTEP;
>> +		if (idx > 1)
>> +			idx--;
>> +		offset = 0;
>> +	} else {
>> +		idx = (msec - 1000) / LM3633_LONG_TIMESTEP;
>> +		offset = LM3633_TIME_OFFSET;
>> +	}
>> +
>> +	return idx + offset;
>> +}
>> +
>> +static u8 lm3633_convert_ramp_to_index(unsigned int msec)
>> +{
>> +	const int ramp_table[] = { 2, 250, 500, 1000, 2000, 4000, 8000, 16000 };
>> +	int size = ARRAY_SIZE(ramp_table);
>> +	int i;
>> +
>> +	if (msec <= ramp_table[0])
>> +		return 0;
>> +
>> +	if (msec > ramp_table[size - 1])
>> +		return size - 1;
>> +
>> +	for (i = 1; i < size; i++) {
>> +		if (msec == ramp_table[i])
>> +			return i;
>> +
>> +		/* Find an approximate index by looking up the table */
>
> Similarly here. So you should have an array of the possible msec
> values and iterate through it to look for the nearest one.

Driver uses 'ramp_table[]' for this purpose. Could you describe it in 
more details?

>> +
>> +static int lm3633_led_brightness_set(struct led_classdev *cdev,
>> +				     enum led_brightness brightness)
>> +{
>> +	struct ti_lmu_led *lmu_led = container_of(cdev, struct ti_lmu_led,
>> +						  cdev);
>> +	struct ti_lmu_led_chip *chip = lmu_led->chip;
>> +	struct regmap *regmap = chip->lmu->regmap;
>> +	u8 reg = LM3633_REG_BRT_LVLED_BASE + lmu_led->bank_id;
>> +	int ret;
>> +
>> +	mutex_lock(&chip->lock);
>> +
>> +	ret = regmap_write(regmap, reg, brightness);
>> +	if (ret) {
>> +		mutex_unlock(&chip->lock);
>> +		return ret;
>> +	}
>> +
>> +	if (brightness == 0)
>> +		lm3633_led_disable_bank(lmu_led);
>
> This should be checked at first, as I suppose that disabling
> a bank suffices to turn the LED off.

Well, it's not a big deal when turn off the LED. However, our silicon 
designer recommended brightness update should be done prior to enabling 
LED bank. Let me share some block diagram.

[I2C logic] - [brightness control] - [control bank] - [output channel]

If control bank is enabled first, then previous brightness value in 
register can be used instead of new updated brightness. So brightness 
update should be done first.

>> +static u8 lm3633_led_scale_max_brightness(struct ti_lmu_led *lmu_led, u32 imax)
>> +{
>> +	u8 max_current = lm3633_led_convert_current_to_index(imax);
>> +	const u8 max_brightness_table[] = {
>> +		[LMU_IMAX_5mA]  = 191,
>> +		[LMU_IMAX_6mA]  = 197,
>> +		[LMU_IMAX_7mA]  = 203,
>> +		[LMU_IMAX_8mA]  = 208,
>> +		[LMU_IMAX_9mA]  = 212,
>> +		[LMU_IMAX_10mA] = 216,
>> +		[LMU_IMAX_11mA] = 219,
>> +		[LMU_IMAX_12mA] = 222,
>> +		[LMU_IMAX_13mA] = 225,
>> +		[LMU_IMAX_14mA] = 228,
>> +		[LMU_IMAX_15mA] = 230,
>> +		[LMU_IMAX_16mA] = 233,
>> +		[LMU_IMAX_17mA] = 235,
>> +		[LMU_IMAX_18mA] = 237,
>> +		[LMU_IMAX_19mA] = 239,
>> +		[LMU_IMAX_20mA] = 241,
>> +		[LMU_IMAX_21mA] = 242,
>> +		[LMU_IMAX_22mA] = 244,
>> +		[LMU_IMAX_23mA] = 246,
>> +		[LMU_IMAX_24mA] = 247,
>> +		[LMU_IMAX_25mA] = 249,
>> +		[LMU_IMAX_26mA] = 250,
>> +		[LMU_IMAX_27mA] = 251,
>> +		[LMU_IMAX_28mA] = 253,
>> +		[LMU_IMAX_29mA] = 254,
>> +		[LMU_IMAX_30mA] = 255,
>> +	};
>
> After analyzing the subject one more time I think that we need to
> change the approach regarding max brightness issue.
>
> At first - we shouldn't fix max current to max possible register value.
> Instead we should take led-max-microamp property and write its value
> to the [0x22 + bank offset] registers.
>
> With this approach whole 0-255 range of brightness levels will be
> valid for the driver.
>
> In effect all LMU_IMAX* enums seem to be not needed.

Good to hear that, it's the most simplest work for the device ;)
Let me update the driver. Thanks!

Best regards,
Milo
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[toc] | [prev] | [next] | [standalone]


#1279827

FromJacek Anaszewski <j.anaszewski@samsung.com>
Date2015-11-30 13:30 +0100
Message-ID<qAvKH-3zm-29@gated-at.bofh.it>
In reply to#1279690
On 11/30/2015 09:48 AM, Kim, Milo wrote:
> Hi Jacek,
>
> Thanks for your detailed feedback. Please refer to my comments below.
>
> On 11/27/2015 8:19 PM, Jacek Anaszewski wrote:
>> Hi Milo,
>>
>> Thanks for the update. I have few comments below.
>>
>>> diff --git a/drivers/leds/leds-lm3633.c b/drivers/leds/leds-lm3633.c
>>> new file mode 100644
>>> index 0000000..9c391ca
>>> --- /dev/null
>>> +++ b/drivers/leds/leds-lm3633.c
>>> @@ -0,0 +1,840 @@
>>> +/*
>>> + * TI LM3633 LED driver
>>> + *
>>> + * Copyright 2015 Texas Instruments
>>> + *
>>> + * Author: Milo Kim <milo.kim@ti.com>
>>> + *
>>> + * 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.
>>> + */
>>> +
>>> +#include <linux/bitops.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/leds.h>
>>> +#include <linux/mfd/ti-lmu.h>
>>> +#include <linux/mfd/ti-lmu-register.h>
>>> +#include <linux/module.h>
>>> +#include <linux/mutex.h>
>>> +#include <linux/notifier.h>
>>> +#include <linux/of.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/slab.h>
>>> +
>>> +#define LM3633_MAX_PWM                255
>>> +#define LM3633_MIN_CURRENT            5000
>>> +#define LM3633_MAX_CURRENT            30000
>>> +#define LM3633_MAX_PERIOD            9700
>>> +#define LM3633_SHORT_TIMESTEP            16
>>> +#define LM3633_LONG_TIMESTEP            131
>>> +#define LM3633_TIME_OFFSET            61
>>> +#define LM3633_PATTERN_REG_OFFSET        16
>>> +#define LM3633_IMAX_OFFSET            6
>>> +
>>> +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 ti_lmu_led_chip
>>> + *
>>> + * @dev:        Parent device pointer
>>> + * @lmu:        LMU structure. Used for register R/W access.
>>> + * @lock:        Secure handling for multiple user interface access
>>> + * @lmu_led:        Multiple LED strings
>>
>> Could you clarify what "string" means here?
>>
>>> + * @num_leds:        Number of LED strings
>>
>> and here?
>
> Oops! I forgot to replace this term with 'output channel'. Thanks for
> catching this.
>
>>> +static u8 lm3633_convert_time_to_index(unsigned int msec)
>>> +{
>>> +    u8 idx, offset;
>>> +
>>> +    /*
>>> +     * Find an approximate index
>>
>> I think that we shouldn't approximate but clamp the msec
>> to the nearest possible device setting.
>>
>>> +     *
>>> +     *      0 <= time <= 1000 : 16ms step
>>> +     *   1000 <  time <= 9700 : 131ms step, base index is 61
>>> +     */
>>> +
>>> +    msec = min_t(int, msec, LM3633_MAX_PERIOD);
>>> +
>>> +    if (msec <= 1000) {
>>> +        idx = msec / LM3633_SHORT_TIMESTEP;
>>> +        if (idx > 1)
>>> +            idx--;
>>> +        offset = 0;
>>> +    } else {
>>> +        idx = (msec - 1000) / LM3633_LONG_TIMESTEP;
>>> +        offset = LM3633_TIME_OFFSET;
>>> +    }
>>> +
>>> +    return idx + offset;
>>> +}
>>> +
>>> +static u8 lm3633_convert_ramp_to_index(unsigned int msec)
>>> +{
>>> +    const int ramp_table[] = { 2, 250, 500, 1000, 2000, 4000, 8000,
>>> 16000 };
>>> +    int size = ARRAY_SIZE(ramp_table);
>>> +    int i;
>>> +
>>> +    if (msec <= ramp_table[0])
>>> +        return 0;
>>> +
>>> +    if (msec > ramp_table[size - 1])
>>> +        return size - 1;
>>> +
>>> +    for (i = 1; i < size; i++) {
>>> +        if (msec == ramp_table[i])
>>> +            return i;
>>> +
>>> +        /* Find an approximate index by looking up the table */
>>
>> Similarly here. So you should have an array of the possible msec
>> values and iterate through it to look for the nearest one.
>
> Driver uses 'ramp_table[]' for this purpose. Could you describe it in
> more details?

Right, but why the values don't match the ones from the documentation?
e.g.: 2, 262, 524, 1049, 2097 etc.

And regarding sysfs attributes - they should be readable and return
the aligned value.

>>> +
>>> +static int lm3633_led_brightness_set(struct led_classdev *cdev,
>>> +                     enum led_brightness brightness)
>>> +{
>>> +    struct ti_lmu_led *lmu_led = container_of(cdev, struct ti_lmu_led,
>>> +                          cdev);
>>> +    struct ti_lmu_led_chip *chip = lmu_led->chip;
>>> +    struct regmap *regmap = chip->lmu->regmap;
>>> +    u8 reg = LM3633_REG_BRT_LVLED_BASE + lmu_led->bank_id;
>>> +    int ret;
>>> +
>>> +    mutex_lock(&chip->lock);
>>> +
>>> +    ret = regmap_write(regmap, reg, brightness);
>>> +    if (ret) {
>>> +        mutex_unlock(&chip->lock);
>>> +        return ret;
>>> +    }
>>> +
>>> +    if (brightness == 0)
>>> +        lm3633_led_disable_bank(lmu_led);
>>
>> This should be checked at first, as I suppose that disabling
>> a bank suffices to turn the LED off.
>
> Well, it's not a big deal when turn off the LED. However, our silicon
> designer recommended brightness update should be done prior to enabling
> LED bank. Let me share some block diagram.
>
> [I2C logic] - [brightness control] - [control bank] - [output channel]
>
> If control bank is enabled first, then previous brightness value in
> register can be used instead of new updated brightness. So brightness
> update should be done first.

So, call "regmap_write(regmap, reg, brightness)" only if brightness > 0.

How about below (error handling skipped):

if (brightness > 0) {
         regmap_write(regmap, reg, brightness);
	lm3633_led_enable_bank(lmu_led);
} else {
	lm3633_led_disable_bank(lmu_led);
}


>
>>> +static u8 lm3633_led_scale_max_brightness(struct ti_lmu_led
>>> *lmu_led, u32 imax)
>>> +{
>>> +    u8 max_current = lm3633_led_convert_current_to_index(imax);
>>> +    const u8 max_brightness_table[] = {
>>> +        [LMU_IMAX_5mA]  = 191,
>>> +        [LMU_IMAX_6mA]  = 197,
>>> +        [LMU_IMAX_7mA]  = 203,
>>> +        [LMU_IMAX_8mA]  = 208,
>>> +        [LMU_IMAX_9mA]  = 212,
>>> +        [LMU_IMAX_10mA] = 216,
>>> +        [LMU_IMAX_11mA] = 219,
>>> +        [LMU_IMAX_12mA] = 222,
>>> +        [LMU_IMAX_13mA] = 225,
>>> +        [LMU_IMAX_14mA] = 228,
>>> +        [LMU_IMAX_15mA] = 230,
>>> +        [LMU_IMAX_16mA] = 233,
>>> +        [LMU_IMAX_17mA] = 235,
>>> +        [LMU_IMAX_18mA] = 237,
>>> +        [LMU_IMAX_19mA] = 239,
>>> +        [LMU_IMAX_20mA] = 241,
>>> +        [LMU_IMAX_21mA] = 242,
>>> +        [LMU_IMAX_22mA] = 244,
>>> +        [LMU_IMAX_23mA] = 246,
>>> +        [LMU_IMAX_24mA] = 247,
>>> +        [LMU_IMAX_25mA] = 249,
>>> +        [LMU_IMAX_26mA] = 250,
>>> +        [LMU_IMAX_27mA] = 251,
>>> +        [LMU_IMAX_28mA] = 253,
>>> +        [LMU_IMAX_29mA] = 254,
>>> +        [LMU_IMAX_30mA] = 255,
>>> +    };
>>
>> After analyzing the subject one more time I think that we need to
>> change the approach regarding max brightness issue.
>>
>> At first - we shouldn't fix max current to max possible register value.
>> Instead we should take led-max-microamp property and write its value
>> to the [0x22 + bank offset] registers.
>>
>> With this approach whole 0-255 range of brightness levels will be
>> valid for the driver.
>>
>> In effect all LMU_IMAX* enums seem to be not needed.
>
> Good to hear that, it's the most simplest work for the device ;)
> Let me update the driver. Thanks!
>
> Best regards,
> Milo
>
>


-- 
Best Regards,
Jacek Anaszewski
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[toc] | [prev] | [standalone]


Back to top | Article view | linux.kernel


csiph-web