diff mbox series

leds: add TI LMU backlight driver

Message ID 20180830082219.GA10133@amd (mailing list archive)
State New, archived
Headers show
Series leds: add TI LMU backlight driver | expand

Commit Message

Pavel Machek Aug. 30, 2018, 8:22 a.m. UTC
This adds backlight support for the following TI LMU
chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.

It controls LEDs on Droid 4
smartphone, including keyboard and screen backlights.

Signed-off-by: Milo Kim <milo.kim@ti.com>
[add LED subsystem support for keyboard backlight and rework DT
binding according to Rob Herrings feedback]
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
[remove backlight subsystem support for now]
Signed-off-by: Pavel Machek <pavel@ucw.cz>

Comments

Tony Lindgren Aug. 30, 2018, 4:40 p.m. UTC | #1
* Pavel Machek <pavel@ucw.cz> [180830 08:26]:
> This adds backlight support for the following TI LMU
> chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
...
> +struct ti_lmu_bank {
> +	struct device *dev;
> +	int bank_id;
> +	const struct ti_lmu_bl_cfg *cfg;
> +	struct ti_lmu *lmu;
> +	const char *label;
> +	int leds;
> +	int current_brightness;
> +	u32 default_brightness;
> +	u32 ramp_up_msec;
> +	u32 ramp_down_msec;
> +
> +	struct notifier_block nb;
> +
> +	struct led_classdev *led;
> +};
> +
> +static int ti_lmu_bl_enable(struct ti_lmu_bank *lmu_bank, bool enable)
> +{
> +	struct regmap *regmap = lmu_bank->lmu->regmap;
> +	unsigned long enable_time = lmu_bank->cfg->reginfo->enable_usec;
> +	u8 *reg = lmu_bank->cfg->reginfo->enable;
> +	u8 mask = BIT(lmu_bank->bank_id);
> +	u8 val = (enable == true) ? mask : 0;
> +	int ret;
> +
> +	if (!reg)
> +		return -EINVAL;
> +
> +	ret = regmap_update_bits(regmap, *reg, mask, val);
> +	if (ret)
> +		return ret;
> +
> +	if (enable_time > 0)
> +		usleep_range(enable_time, enable_time + 100);
> +
> +	return 0;
> +}

If it's a led driver, should you just s/ti_lmu_bl/ti_lmu_led/g?

Regards,

Tony
Jacek Anaszewski Aug. 30, 2018, 7:20 p.m. UTC | #2
Hi Pavel,

Thank you for the patch.

Looking for DT bindings I can find the following

Documentation/devicetree/bindings/mfd/ti-lmu.txt,

which for LED bindings redirects to:

"[2] ../leds/leds-lm3633.txt",

but it is not present in the mainline.

Best regards,
Jacek Anaszewski

On 08/30/2018 10:22 AM, Pavel Machek wrote:
> 
> This adds backlight support for the following TI LMU
> chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
> 
> It controls LEDs on Droid 4
> smartphone, including keyboard and screen backlights.
> 
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> [add LED subsystem support for keyboard backlight and rework DT
> binding according to Rob Herrings feedback]
> Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> [remove backlight subsystem support for now]
> Signed-off-by: Pavel Machek <pavel@ucw.cz>
[...]
diff mbox series

Patch

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 44097a3..9965222 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -756,6 +756,13 @@  config LEDS_NIC78BX
 	  To compile this driver as a module, choose M here: the module
 	  will be called leds-nic78bx.
 
+config LEDS_TI_LMU
+	tristate "LED driver for TI LMU"
+ 	depends on MFD_TI_LMU
+	help
+          Say Y to enable the LED driver for TI LMU devices.
+          This supports LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
+
 comment "LED Triggers"
 source "drivers/leds/trigger/Kconfig"
 
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 420b5d2..95c890d 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -78,6 +78,8 @@  obj-$(CONFIG_LEDS_MT6323)		+= leds-mt6323.o
 obj-$(CONFIG_LEDS_LM3692X)		+= leds-lm3692x.o
 obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o
 obj-$(CONFIG_LEDS_LM3601X)		+= leds-lm3601x.o
+ti-lmu-objs				:= ti-lmu-core.o ti-lmu-data.o
+obj-$(CONFIG_LEDS_TI_LMU)		+= ti-lmu.o
 
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_CR0014114)		+= leds-cr0014114.o
diff --git a/drivers/leds/ti-lmu-core.c b/drivers/leds/ti-lmu-core.c
new file mode 100644
index 0000000..9a6dfb7
--- /dev/null
+++ b/drivers/leds/ti-lmu-core.c
@@ -0,0 +1,536 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2015 Texas Instruments
+ * Copyright 2018 Sebastian Reichel
+ * Copyright 2018 Pavel Machek <pavel@ucw.cz>
+ *
+ * TI LMU Led driver, based on previous work from
+ * Milo Kim <milo.kim@ti.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/leds.h>
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#include "ti-lmu-data.h"
+
+enum ti_lmu_bl_ramp_mode {
+	BL_RAMP_UP,
+	BL_RAMP_DOWN,
+};
+
+#define NUM_DUAL_CHANNEL			2
+#define LMU_DUAL_CHANNEL_USED		(BIT(0) | BIT(1))
+#define LMU_11BIT_LSB_MASK		(BIT(0) | BIT(1) | BIT(2))
+#define LMU_11BIT_MSB_SHIFT		3
+
+struct ti_lmu_bank {
+	struct device *dev;
+	int bank_id;
+	const struct ti_lmu_bl_cfg *cfg;
+	struct ti_lmu *lmu;
+	const char *label;
+	int leds;
+	int current_brightness;
+	u32 default_brightness;
+	u32 ramp_up_msec;
+	u32 ramp_down_msec;
+
+	struct notifier_block nb;
+
+	struct led_classdev *led;
+};
+
+static int ti_lmu_bl_enable(struct ti_lmu_bank *lmu_bank, bool enable)
+{
+	struct regmap *regmap = lmu_bank->lmu->regmap;
+	unsigned long enable_time = lmu_bank->cfg->reginfo->enable_usec;
+	u8 *reg = lmu_bank->cfg->reginfo->enable;
+	u8 mask = BIT(lmu_bank->bank_id);
+	u8 val = (enable == true) ? mask : 0;
+	int ret;
+
+	if (!reg)
+		return -EINVAL;
+
+	ret = regmap_update_bits(regmap, *reg, mask, val);
+	if (ret)
+		return ret;
+
+	if (enable_time > 0)
+		usleep_range(enable_time, enable_time + 100);
+
+	return 0;
+}
+
+static int ti_lmu_bl_update_brightness_register(struct ti_lmu_bank *lmu_bank,
+						       int brightness)
+{
+	const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
+	const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
+	struct regmap *regmap = lmu_bank->lmu->regmap;
+	u8 reg, val;
+	int ret;
+
+	/*
+	 * Brightness register update
+	 *
+	 * 11 bit dimming: update LSB bits and write MSB byte.
+	 *		   MSB brightness should be shifted.
+	 *  8 bit dimming: write MSB byte.
+	 */
+	if (cfg->max_brightness == MAX_BRIGHTNESS_11BIT) {
+		reg = reginfo->brightness_lsb[lmu_bank->bank_id];
+		ret = regmap_update_bits(regmap, reg,
+					 LMU_11BIT_LSB_MASK,
+					 brightness);
+		if (ret)
+			return ret;
+
+		val = brightness >> LMU_11BIT_MSB_SHIFT;
+	} else {
+		val = brightness;
+	}
+
+	reg = reginfo->brightness_msb[lmu_bank->bank_id];
+	return regmap_write(regmap, reg, val);
+}
+
+static int ti_lmu_bl_set_brightness(struct ti_lmu_bank *lmu_bank,
+				    int brightness)
+{
+	bool enable = brightness > 0;
+	int ret;
+
+	ret = ti_lmu_bl_enable(lmu_bank, enable);
+	if (ret)
+		return ret;
+
+	lmu_bank->current_brightness = brightness;
+
+	return ti_lmu_bl_update_brightness_register(lmu_bank, brightness);
+}
+
+static int ti_lmu_bl_set_led_blocking(struct led_classdev *ledc,
+					     enum led_brightness value)
+{
+	struct ti_lmu_bank *lmu_bank = dev_get_drvdata(ledc->dev->parent);
+	int brightness = value;
+
+	return ti_lmu_bl_set_brightness(lmu_bank, brightness);
+}
+
+static int ti_lmu_bl_check_channel(struct ti_lmu_bank *lmu_bank)
+{
+	const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
+	const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
+
+	if (!reginfo->brightness_msb)
+		return -EINVAL;
+
+	if (cfg->max_brightness > MAX_BRIGHTNESS_8BIT) {
+		if (!reginfo->brightness_lsb)
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int ti_lmu_bl_create_channel(struct ti_lmu_bank *lmu_bank)
+{
+	struct regmap *regmap = lmu_bank->lmu->regmap;
+	const struct lmu_bl_reg_data *regdata = lmu_bank->cfg->reginfo->channel;
+	int num_channels = lmu_bank->cfg->num_channels;
+	unsigned long led_sources = lmu_bank->leds;
+	int i, ret;
+	u8 shift;
+
+	/*
+	 * How to create backlight output channels:
+	 *   Check 'led_sources' bit and update registers.
+	 *
+	 *   1) Dual channel configuration
+	 *     The 1st register data is used for single channel.
+	 *     The 2nd register data is used for dual channel.
+	 *
+	 *   2) Multiple channel configuration
+	 *     Each register data is mapped to bank ID.
+	 *     Bit shift operation is defined in channel registers.
+	 *
+	 * Channel register data consists of address, mask, value.
+	 */
+
+	if (num_channels == NUM_DUAL_CHANNEL) {
+		if (led_sources == LMU_DUAL_CHANNEL_USED)
+			regdata++;
+
+		return regmap_update_bits(regmap, regdata->reg, regdata->mask,
+					  regdata->val);
+	}
+
+	for (i = 0; regdata && i < num_channels; i++) {
+		/*
+		 * Note that the result of regdata->val is shift bit.
+		 * The bank_id should be shifted for the channel configuration.
+		 */
+		if (test_bit(i, &led_sources)) {
+			shift = regdata->val;
+			ret = regmap_update_bits(regmap, regdata->reg,
+						 regdata->mask,
+						 lmu_bank->bank_id << shift);
+			if (ret)
+				return ret;
+		}
+
+		regdata++;
+	}
+
+	return 0;
+}
+
+static int ti_lmu_bl_update_ctrl_mode(struct ti_lmu_bank *lmu_bank)
+{
+	struct regmap *regmap = lmu_bank->lmu->regmap;
+	const struct lmu_bl_reg_data *regdata =
+		lmu_bank->cfg->reginfo->mode + lmu_bank->bank_id;
+	u8 val = regdata->val;
+
+	if (!regdata)
+		return 0;
+
+	/*
+	 * Update PWM configuration register.
+	 * If the mode is register based, then clear the bit.
+	 */
+	val = 0;
+
+	return regmap_update_bits(regmap, regdata->reg, regdata->mask, val);
+}
+
+static int ti_lmu_bl_convert_ramp_to_index(struct ti_lmu_bank *lmu_bank,
+						  enum ti_lmu_bl_ramp_mode mode)
+{
+	const int *ramp_table = lmu_bank->cfg->ramp_table;
+	const int size = lmu_bank->cfg->size_ramp;
+	unsigned int msec;
+	int i;
+
+	if (!ramp_table)
+		return -EINVAL;
+
+	switch (mode) {
+	case BL_RAMP_UP:
+		msec = lmu_bank->ramp_up_msec;
+		break;
+	case BL_RAMP_DOWN:
+		msec = lmu_bank->ramp_down_msec;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	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 -EINVAL;
+}
+
+
+static int ti_lmu_bl_set_ramp(struct ti_lmu_bank *lmu_bank)
+{
+	struct regmap *regmap = lmu_bank->lmu->regmap;
+	const struct ti_lmu_bl_reg *reginfo = lmu_bank->cfg->reginfo;
+	int offset = reginfo->ramp_reg_offset;
+	int i, ret, index;
+	struct lmu_bl_reg_data regdata;
+
+	for (i = BL_RAMP_UP; i <= BL_RAMP_DOWN; i++) {
+		index = ti_lmu_bl_convert_ramp_to_index(lmu_bank, i);
+		if (index > 0) {
+			if (!reginfo->ramp)
+				break;
+
+			regdata = reginfo->ramp[i];
+			if (lmu_bank->bank_id != 0)
+				regdata.val += offset;
+
+			/* regdata.val is shift bit */
+			ret = regmap_update_bits(regmap, regdata.reg,
+						 regdata.mask,
+						 index << regdata.val);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int ti_lmu_bl_configure(struct ti_lmu_bank *lmu_bank)
+{
+	int ret;
+
+	ret = ti_lmu_bl_check_channel(lmu_bank);
+	if (ret)
+		return ret;
+
+	ret = ti_lmu_bl_create_channel(lmu_bank);
+	if (ret)
+		return ret;
+
+	ret = ti_lmu_bl_update_ctrl_mode(lmu_bank);
+	if (ret)
+		return ret;
+
+	return ti_lmu_bl_set_ramp(lmu_bank);
+}
+
+static int ti_lmu_bl_register_led(struct ti_lmu_bank *lmu_bank)
+{
+	int err;
+
+	lmu_bank->led = devm_kzalloc(lmu_bank->dev, sizeof(*lmu_bank->led),
+				     GFP_KERNEL);
+	if (!lmu_bank->led)
+		return -ENOMEM;
+
+	lmu_bank->led->name = lmu_bank->label;
+	lmu_bank->led->max_brightness = lmu_bank->cfg->max_brightness;
+	lmu_bank->led->brightness_set_blocking =
+		ti_lmu_bl_set_led_blocking;
+
+	err = devm_led_classdev_register(lmu_bank->dev, lmu_bank->led);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
+{
+	return ti_lmu_bl_register_led(lmu_bank);
+}
+
+static int setup_of_node(struct platform_device *pdev)
+{
+	struct device_node *parent_node = pdev->dev.parent->of_node;
+	char *name;
+
+	if (!parent_node)
+		return 0;
+
+	name = kasprintf(GFP_KERNEL, "bank%d", pdev->id);
+	if (!name)
+		return -ENOMEM;
+
+	pdev->dev.of_node = of_get_child_by_name(parent_node, name);
+	kfree(name);
+
+	if (!pdev->dev.of_node)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int ti_lmu_parse_led_sources(struct device *dev)
+{
+	unsigned long mask = 0;
+	int ret;
+	int size, i;
+	u32 *leds;
+
+	size = device_property_read_u32_array(dev, "ti,led-sources", NULL, 0);
+	if (size <= 0) {
+		dev_err(dev, "Missing or malformed property led-sources: %d\n",
+			size);
+		return size < 0 ? size : -EINVAL;
+	}
+
+	leds = kmalloc_array(size, sizeof(u32), GFP_KERNEL);
+	if (!leds)
+		return -ENOMEM;
+
+	ret = device_property_read_u32_array(dev, "ti,led-sources", leds, size);
+	if (ret) {
+		dev_err(dev, "Failed to read led-sources property: %d\n", ret);
+		goto out;
+	}
+
+	for (i = 0; i < size; i++)
+		set_bit(leds[i], &mask);
+
+	ret = mask;
+
+out:
+	kfree(leds);
+	return ret;
+}
+
+static int ti_lmu_bl_init(struct ti_lmu_bank *lmu_bank)
+{
+	struct regmap *regmap = lmu_bank->lmu->regmap;
+	const struct lmu_bl_reg_data *regdata =
+		lmu_bank->cfg->reginfo->init;
+	int num_init = lmu_bank->cfg->reginfo->num_init;
+	int i, ret;
+
+	if (lmu_bank->lmu->backlight_initialized)
+		return 0;
+	lmu_bank->lmu->backlight_initialized = true;
+
+	for (i = 0; regdata && i < num_init; i++) {
+		ret = regmap_update_bits(regmap, regdata->reg, regdata->mask,
+					 regdata->val);
+		if (ret)
+			return ret;
+
+		regdata++;
+	}
+
+	return 0;
+}
+
+static int ti_lmu_bl_reload(struct ti_lmu_bank *lmu_bank)
+{
+	int err;
+
+	ti_lmu_bl_init(lmu_bank);
+
+	err = ti_lmu_bl_configure(lmu_bank);
+	if (err)
+		return err;
+
+	return ti_lmu_bl_set_brightness(lmu_bank, lmu_bank->current_brightness);
+}
+
+static int ti_lmu_bl_monitor_notifier(struct notifier_block *nb,
+					     unsigned long action, void *unused)
+{
+	struct ti_lmu_bank *lmu_bank = container_of(nb, struct ti_lmu_bank, nb);
+
+	if (action == LMU_EVENT_MONITOR_DONE) {
+		if (ti_lmu_bl_reload(lmu_bank))
+			return NOTIFY_STOP;
+	}
+
+	return NOTIFY_OK;
+}
+
+static int ti_lmu_bl_probe(struct platform_device *pdev)
+{
+	struct ti_lmu_bank *lmu_bank;
+	int err;
+
+	err = setup_of_node(pdev);
+	if (err)
+		return err;
+
+	lmu_bank = devm_kzalloc(&pdev->dev, sizeof(*lmu_bank), GFP_KERNEL);
+	if (!lmu_bank)
+		return -ENOMEM;
+	lmu_bank->dev = &pdev->dev;
+	dev_set_drvdata(&pdev->dev, lmu_bank);
+
+	err = device_property_read_string(&pdev->dev, "label",
+					  &lmu_bank->label);
+	if (err)
+		return err;
+
+	lmu_bank->leds = ti_lmu_parse_led_sources(&pdev->dev);
+	if (lmu_bank->leds < 0)
+		return lmu_bank->leds;
+	else if (lmu_bank->leds == 0)
+		return -EINVAL;
+
+	device_property_read_u32(&pdev->dev, "default-brightness-level",
+				 &lmu_bank->default_brightness);
+	device_property_read_u32(&pdev->dev, "ti,ramp-up-ms",
+				 &lmu_bank->ramp_up_msec);
+	device_property_read_u32(&pdev->dev, "ti,ramp-down-ms",
+				 &lmu_bank->ramp_down_msec);
+
+	lmu_bank->lmu = dev_get_drvdata(pdev->dev.parent);
+	lmu_bank->cfg = &lmu_bl_cfg[lmu_bank->lmu->id];
+	lmu_bank->bank_id = pdev->id;
+
+	ti_lmu_bl_init(lmu_bank);
+
+	err = ti_lmu_bl_configure(lmu_bank);
+	if (err)
+		return err;
+
+	err = ti_lmu_bl_add_device(lmu_bank);
+	if (err)
+		return err;
+
+	err = ti_lmu_bl_set_brightness(lmu_bank,
+				       lmu_bank->default_brightness);
+	if (err)
+		return err;
+
+	/*
+	 * Notifier callback is required because backlight device needs
+	 * reconfiguration after fault detection procedure is done by
+	 * ti-lmu-fault-monitor driver.
+	 */
+	if (lmu_bank->cfg->fault_monitor_used) {
+		lmu_bank->nb.notifier_call = ti_lmu_bl_monitor_notifier;
+		err = blocking_notifier_chain_register(&lmu_bank->lmu->notifier,
+						       &lmu_bank->nb);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static int ti_lmu_bl_remove(struct platform_device *pdev)
+{
+	struct ti_lmu_bank *lmu_bank = platform_get_drvdata(pdev);
+
+	if (lmu_bank->cfg->fault_monitor_used)
+		blocking_notifier_chain_unregister(&lmu_bank->lmu->notifier,
+						   &lmu_bank->nb);
+
+	ti_lmu_bl_set_brightness(lmu_bank, 0);
+
+	return 0;
+}
+
+static struct platform_driver ti_lmu_bl_driver = {
+	.probe  = ti_lmu_bl_probe,
+	.remove  = ti_lmu_bl_remove,
+	.driver = {
+		.name = "ti-lmu-led",
+	},
+};
+module_platform_driver(ti_lmu_bl_driver)
+
+MODULE_DESCRIPTION("TI LMU LED Driver");
+MODULE_AUTHOR("Sebastian Reichel");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:ti-lmu-led");
diff --git a/drivers/leds/ti-lmu-data.c b/drivers/leds/ti-lmu-data.c
new file mode 100644
index 0000000..d7e8a83
--- /dev/null
+++ b/drivers/leds/ti-lmu-data.c
@@ -0,0 +1,304 @@ 
+/*
+ * TI LMU (Lighting Management Unit) Backlight Device Data
+ *
+ * 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 "ti-lmu-data.h"
+
+/* LM3532 */
+static const struct lmu_bl_reg_data lm3532_init_data[] = {
+	{ LM3532_REG_ZONE_CFG_A, LM3532_ZONE_MASK, LM3532_ZONE_0 },
+	{ LM3532_REG_ZONE_CFG_B, LM3532_ZONE_MASK, LM3532_ZONE_1 },
+	{ LM3532_REG_ZONE_CFG_C, LM3532_ZONE_MASK, LM3532_ZONE_2 },
+};
+
+static const struct lmu_bl_reg_data lm3532_channel_data[] = {
+	{ LM3532_REG_OUTPUT_CFG, LM3532_ILED1_CFG_MASK,
+	  LM3532_ILED1_CFG_SHIFT },
+	{ LM3532_REG_OUTPUT_CFG, LM3532_ILED2_CFG_MASK,
+	  LM3532_ILED2_CFG_SHIFT },
+	{ LM3532_REG_OUTPUT_CFG, LM3532_ILED3_CFG_MASK,
+	  LM3532_ILED3_CFG_SHIFT },
+};
+
+static const struct lmu_bl_reg_data lm3532_mode_data[] = {
+	{ LM3532_REG_PWM_A_CFG, LM3532_PWM_A_MASK, LM3532_PWM_ZONE_0 },
+	{ LM3532_REG_PWM_B_CFG, LM3532_PWM_B_MASK, LM3532_PWM_ZONE_1 },
+	{ LM3532_REG_PWM_C_CFG, LM3532_PWM_C_MASK, LM3532_PWM_ZONE_2 },
+};
+
+static const struct lmu_bl_reg_data lm3532_ramp_data[] = {
+	{ LM3532_REG_RAMPUP, LM3532_RAMPUP_MASK, LM3532_RAMPUP_SHIFT },
+	{ LM3532_REG_RAMPDN, LM3532_RAMPDN_MASK, LM3532_RAMPDN_SHIFT },
+};
+
+static u8 lm3532_enable_reg = LM3532_REG_ENABLE;
+
+static u8 lm3532_brightness_regs[] = {
+	LM3532_REG_BRT_A,
+	LM3532_REG_BRT_B,
+	LM3532_REG_BRT_C,
+};
+
+static const struct ti_lmu_bl_reg lm3532_reg_info = {
+	.init		= lm3532_init_data,
+	.num_init	= ARRAY_SIZE(lm3532_init_data),
+	.channel	= lm3532_channel_data,
+	.mode		= lm3532_mode_data,
+	.ramp		= lm3532_ramp_data,
+	.enable		= &lm3532_enable_reg,
+	.brightness_msb	= lm3532_brightness_regs,
+};
+
+/* LM3631 */
+static const struct lmu_bl_reg_data lm3631_init_data[] = {
+	{ LM3631_REG_BRT_MODE, LM3631_MODE_MASK, LM3631_DEFAULT_MODE },
+	{ LM3631_REG_BL_CFG, LM3631_MAP_MASK, LM3631_EXPONENTIAL_MAP },
+};
+
+static const struct lmu_bl_reg_data lm3631_channel_data[] = {
+	{ LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_SINGLE_CHANNEL },
+	{ LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_DUAL_CHANNEL },
+};
+
+static const struct lmu_bl_reg_data lm3631_ramp_data[] = {
+	{ LM3631_REG_SLOPE, LM3631_SLOPE_MASK, LM3631_SLOPE_SHIFT },
+};
+
+static u8 lm3631_enable_reg = LM3631_REG_DEVCTRL;
+static u8 lm3631_brightness_msb_reg = LM3631_REG_BRT_MSB;
+static u8 lm3631_brightness_lsb_reg = LM3631_REG_BRT_LSB;
+
+static const struct ti_lmu_bl_reg lm3631_reg_info = {
+	.init		= lm3631_init_data,
+	.num_init	= ARRAY_SIZE(lm3631_init_data),
+	.channel	= lm3631_channel_data,
+	.ramp		= lm3631_ramp_data,
+	.enable		= &lm3631_enable_reg,
+	.brightness_msb	= &lm3631_brightness_msb_reg,
+	.brightness_lsb	= &lm3631_brightness_lsb_reg,
+};
+
+/* LM3632 */
+static const struct lmu_bl_reg_data lm3632_init_data[] = {
+	{ LM3632_REG_CONFIG1, LM3632_OVP_MASK, LM3632_OVP_25V },
+	{ LM3632_REG_CONFIG2, LM3632_SWFREQ_MASK, LM3632_SWFREQ_1MHZ },
+};
+
+static const struct lmu_bl_reg_data lm3632_channel_data[] = {
+	{ LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_SINGLE_CHANNEL },
+	{ LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_DUAL_CHANNEL },
+};
+
+static const struct lmu_bl_reg_data lm3632_mode_data[] = {
+	{ LM3632_REG_IO_CTRL, LM3632_PWM_MASK, LM3632_PWM_MODE },
+};
+
+static u8 lm3632_enable_reg = LM3632_REG_ENABLE;
+static u8 lm3632_brightness_msb_reg = LM3632_REG_BRT_MSB;
+static u8 lm3632_brightness_lsb_reg = LM3632_REG_BRT_LSB;
+
+static const struct ti_lmu_bl_reg lm3632_reg_info = {
+	.init		= lm3632_init_data,
+	.num_init	= ARRAY_SIZE(lm3632_init_data),
+	.channel	= lm3632_channel_data,
+	.mode		= lm3632_mode_data,
+	.enable		= &lm3632_enable_reg,
+	.brightness_msb	= &lm3632_brightness_msb_reg,
+	.brightness_lsb	= &lm3632_brightness_lsb_reg,
+};
+
+/* LM3633 */
+static const struct lmu_bl_reg_data lm3633_init_data[] = {
+	{ LM3633_REG_BOOST_CFG, LM3633_OVP_MASK, LM3633_OVP_40V },
+	{ LM3633_REG_BL_RAMP_CONF, LM3633_BL_RAMP_MASK, LM3633_BL_RAMP_EACH },
+};
+
+static const struct lmu_bl_reg_data lm3633_channel_data[] = {
+	{ LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED1_CFG_MASK,
+	  LM3633_HVLED1_CFG_SHIFT },
+	{ LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED2_CFG_MASK,
+	  LM3633_HVLED2_CFG_SHIFT },
+	{ LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED3_CFG_MASK,
+	  LM3633_HVLED3_CFG_SHIFT },
+};
+
+static const struct lmu_bl_reg_data lm3633_mode_data[] = {
+	{ LM3633_REG_PWM_CFG, LM3633_PWM_A_MASK, LM3633_PWM_A_MASK },
+	{ LM3633_REG_PWM_CFG, LM3633_PWM_B_MASK, LM3633_PWM_B_MASK },
+};
+
+static const struct lmu_bl_reg_data lm3633_ramp_data[] = {
+	{ LM3633_REG_BL0_RAMP, LM3633_BL_RAMPUP_MASK, LM3633_BL_RAMPUP_SHIFT },
+	{ LM3633_REG_BL0_RAMP, LM3633_BL_RAMPDN_MASK, LM3633_BL_RAMPDN_SHIFT },
+};
+
+static u8 lm3633_enable_reg = LM3633_REG_ENABLE;
+
+static u8 lm3633_brightness_msb_regs[] = {
+	LM3633_REG_BRT_HVLED_A_MSB,
+	LM3633_REG_BRT_HVLED_B_MSB,
+};
+
+static u8 lm3633_brightness_lsb_regs[] = {
+	LM3633_REG_BRT_HVLED_A_LSB,
+	LM3633_REG_BRT_HVLED_B_LSB,
+};
+
+static const struct ti_lmu_bl_reg lm3633_reg_info = {
+	.init		 = lm3633_init_data,
+	.num_init	 = ARRAY_SIZE(lm3633_init_data),
+	.channel	 = lm3633_channel_data,
+	.mode		 = lm3633_mode_data,
+	.ramp		 = lm3633_ramp_data,
+	.ramp_reg_offset = 1, /* For LM3633_REG_BL1_RAMPUP/DN */
+	.enable		 = &lm3633_enable_reg,
+	.brightness_msb	 = lm3633_brightness_msb_regs,
+	.brightness_lsb	 = lm3633_brightness_lsb_regs,
+};
+
+/* LM3695 */
+static const struct lmu_bl_reg_data lm3695_init_data[] = {
+	{ LM3695_REG_GP, LM3695_BRT_RW_MASK, LM3695_BRT_RW_MASK },
+};
+
+static const struct lmu_bl_reg_data lm3695_channel_data[] = {
+	{ LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_SINGLE_CHANNEL },
+	{ LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_DUAL_CHANNEL },
+};
+
+static u8 lm3695_enable_reg = LM3695_REG_GP;
+static u8 lm3695_brightness_msb_reg = LM3695_REG_BRT_MSB;
+static u8 lm3695_brightness_lsb_reg = LM3695_REG_BRT_LSB;
+
+static const struct ti_lmu_bl_reg lm3695_reg_info = {
+	.init		= lm3695_init_data,
+	.num_init	= ARRAY_SIZE(lm3695_init_data),
+	.channel	= lm3695_channel_data,
+	.enable		= &lm3695_enable_reg,
+	.enable_usec	= 600,
+	.brightness_msb	= &lm3695_brightness_msb_reg,
+	.brightness_lsb	= &lm3695_brightness_lsb_reg,
+};
+
+/* LM3697 */
+static const struct lmu_bl_reg_data lm3697_init_data[] = {
+	{ LM3697_REG_RAMP_CONF, LM3697_RAMP_MASK, LM3697_RAMP_EACH },
+};
+
+static const struct lmu_bl_reg_data lm3697_channel_data[] = {
+	{ LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED1_CFG_MASK,
+	  LM3697_HVLED1_CFG_SHIFT },
+	{ LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED2_CFG_MASK,
+	  LM3697_HVLED2_CFG_SHIFT },
+	{ LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED3_CFG_MASK,
+	  LM3697_HVLED3_CFG_SHIFT },
+};
+
+static const struct lmu_bl_reg_data lm3697_mode_data[] = {
+	{ LM3697_REG_PWM_CFG, LM3697_PWM_A_MASK, LM3697_PWM_A_MASK },
+	{ LM3697_REG_PWM_CFG, LM3697_PWM_B_MASK, LM3697_PWM_B_MASK },
+};
+
+static const struct lmu_bl_reg_data lm3697_ramp_data[] = {
+	{ LM3697_REG_BL0_RAMP, LM3697_RAMPUP_MASK, LM3697_RAMPUP_SHIFT },
+	{ LM3697_REG_BL0_RAMP, LM3697_RAMPDN_MASK, LM3697_RAMPDN_SHIFT },
+};
+
+static u8 lm3697_enable_reg = LM3697_REG_ENABLE;
+
+static u8 lm3697_brightness_msb_regs[] = {
+	LM3697_REG_BRT_A_MSB,
+	LM3697_REG_BRT_B_MSB,
+};
+
+static u8 lm3697_brightness_lsb_regs[] = {
+	LM3697_REG_BRT_A_LSB,
+	LM3697_REG_BRT_B_LSB,
+};
+
+static const struct ti_lmu_bl_reg lm3697_reg_info = {
+	.init		 = lm3697_init_data,
+	.num_init	 = ARRAY_SIZE(lm3697_init_data),
+	.channel	 = lm3697_channel_data,
+	.mode		 = lm3697_mode_data,
+	.ramp		 = lm3697_ramp_data,
+	.ramp_reg_offset = 1, /* For LM3697_REG_BL1_RAMPUP/DN */
+	.enable		 = &lm3697_enable_reg,
+	.brightness_msb	 = lm3697_brightness_msb_regs,
+	.brightness_lsb	 = lm3697_brightness_lsb_regs,
+};
+
+static int lm3532_ramp_table[] = { 0, 1, 2, 4, 8, 16, 32, 65 };
+
+static int lm3631_ramp_table[] = {
+	   0,   1,   2,    5,   10,   20,   50,  100,
+	 250, 500, 750, 1000, 1500, 2000, 3000, 4000,
+};
+
+static int common_ramp_table[] = {
+	   2, 250, 500, 1000, 2000, 4000, 8000, 16000,
+};
+
+#define LM3532_MAX_CHANNELS		3
+#define LM3631_MAX_CHANNELS		2
+#define LM3632_MAX_CHANNELS		2
+#define LM3633_MAX_CHANNELS		3
+#define LM3695_MAX_CHANNELS		2
+#define LM3697_MAX_CHANNELS		3
+
+const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID] = {
+	{
+		.reginfo		= &lm3532_reg_info,
+		.num_channels		= LM3532_MAX_CHANNELS,
+		.max_brightness		= MAX_BRIGHTNESS_8BIT,
+		.pwm_action		= UPDATE_PWM_AND_BRT_REGISTER,
+		.ramp_table		= lm3532_ramp_table,
+		.size_ramp		= ARRAY_SIZE(lm3532_ramp_table),
+	},
+	{
+		.reginfo		= &lm3631_reg_info,
+		.num_channels		= LM3631_MAX_CHANNELS,
+		.max_brightness		= MAX_BRIGHTNESS_11BIT,
+		.pwm_action		= UPDATE_PWM_ONLY,
+		.ramp_table		= lm3631_ramp_table,
+		.size_ramp		= ARRAY_SIZE(lm3631_ramp_table),
+	},
+	{
+		.reginfo		= &lm3632_reg_info,
+		.num_channels		= LM3632_MAX_CHANNELS,
+		.max_brightness		= MAX_BRIGHTNESS_11BIT,
+		.pwm_action		= UPDATE_PWM_ONLY,
+	},
+	{
+		.reginfo		= &lm3633_reg_info,
+		.num_channels		= LM3633_MAX_CHANNELS,
+		.max_brightness		= MAX_BRIGHTNESS_11BIT,
+		.pwm_action		= UPDATE_MAX_BRT,
+		.ramp_table		= common_ramp_table,
+		.size_ramp		= ARRAY_SIZE(common_ramp_table),
+		.fault_monitor_used	= true,
+	},
+	{
+		.reginfo		= &lm3695_reg_info,
+		.num_channels		= LM3695_MAX_CHANNELS,
+		.max_brightness		= MAX_BRIGHTNESS_11BIT,
+		.pwm_action		= UPDATE_PWM_AND_BRT_REGISTER,
+	},
+	{
+		.reginfo		= &lm3697_reg_info,
+		.num_channels		= LM3697_MAX_CHANNELS,
+		.max_brightness		= MAX_BRIGHTNESS_11BIT,
+		.pwm_action		= UPDATE_PWM_AND_BRT_REGISTER,
+		.ramp_table		= common_ramp_table,
+		.size_ramp		= ARRAY_SIZE(common_ramp_table),
+		.fault_monitor_used	= true,
+	},
+};
diff --git a/drivers/leds/ti-lmu-data.h b/drivers/leds/ti-lmu-data.h
new file mode 100644
index 0000000..c64e8e6
--- /dev/null
+++ b/drivers/leds/ti-lmu-data.h
@@ -0,0 +1,95 @@ 
+/*
+ * TI LMU (Lighting Management Unit) Backlight Device Data Definitions
+ *
+ * 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.
+ */
+
+#ifndef __TI_LMU_BACKLIGHT_H__
+#define __TI_LMU_BACKLIGHT_H__
+
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+
+#define MAX_BRIGHTNESS_8BIT		255
+#define MAX_BRIGHTNESS_11BIT		2047
+
+enum ti_lmu_bl_pwm_action {
+	/* Update PWM duty, no brightness register update is required */
+	UPDATE_PWM_ONLY,
+	/* Update not only duty but also brightness register */
+	UPDATE_PWM_AND_BRT_REGISTER,
+	/* Update max value in brightness registers */
+	UPDATE_MAX_BRT,
+};
+
+struct lmu_bl_reg_data {
+	u8 reg;
+	u8 mask;
+	u8 val;
+};
+
+/**
+ * struct ti_lmu_bl_reg
+ *
+ * @init:		Device initialization registers
+ * @num_init:		Numbers of initialization registers
+ * @channel:		Backlight channel configuration registers
+ * @mode:		Brightness control mode registers
+ * @ramp:		Ramp registers for lighting effect
+ * @ramp_reg_offset:	Ramp register offset.
+ *			Only used for multiple ramp registers.
+ * @enable:		Enable control register address
+ * @enable_usec:	Delay time for updating enable register.
+ *			Unit is microsecond.
+ * @brightness_msb:	Brightness MSB(Upper 8 bits) registers.
+ *			Concatenated with LSB in 11 bit dimming mode.
+ *			In 8 bit dimming, only MSB is used.
+ * @brightness_lsb:	Brightness LSB(Lower 3 bits) registers.
+ *			Only valid in 11 bit dimming mode.
+ */
+struct ti_lmu_bl_reg {
+	const struct lmu_bl_reg_data *init;
+	int num_init;
+	const struct lmu_bl_reg_data *channel;
+	const struct lmu_bl_reg_data *mode;
+	const struct lmu_bl_reg_data *ramp;
+	int ramp_reg_offset;
+	u8 *enable;
+	unsigned long enable_usec;
+	u8 *brightness_msb;
+	u8 *brightness_lsb;
+};
+
+/**
+ * struct ti_lmu_bl_cfg
+ *
+ * @reginfo:		Device register configuration
+ * @num_channels:	Number of backlight channels
+ * @max_brightness:	Max brightness value of backlight device
+ * @pwm_action:		How to control brightness registers in PWM mode
+ * @ramp_table:		[Optional] Ramp time table for lighting effect.
+ *			It's used for searching approximate register index.
+ * @size_ramp:		[Optional] Size of ramp table
+ * @fault_monitor_used:	[Optional] Set true if the device needs to handle
+ *			LMU fault monitor event.
+ *
+ * This structure is used for device specific data configuration.
+ */
+struct ti_lmu_bl_cfg {
+	const struct ti_lmu_bl_reg *reginfo;
+	int num_channels;
+	int max_brightness;
+	enum ti_lmu_bl_pwm_action pwm_action;
+	int *ramp_table;
+	int size_ramp;
+	bool fault_monitor_used;
+};
+
+extern const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID];
+#endif