diff mbox series

[rfc] leds: add TI LMU backlight driver

Message ID 20180829212032.GB15786@amd (mailing list archive)
State New, archived
Headers show
Series [rfc] leds: add TI LMU backlight driver | expand

Commit Message

Pavel Machek Aug. 29, 2018, 9:20 p.m. UTC
Here's preview of driver for TI LMU. It controls LEDs on Droid 4
smartphone, including keyboard and screen backlights.

This adds backlight support for the following TI LMU
chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.

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>

---

Does it looks mostly reasonable? I guess it will need some
s/BACKLIGHT/LEDS/ , and I'll need to remove my debugging hacks.

I'd prefer this to be LED driver, first; I'll need to figure out what
to do with backlight. I guess something like existing "backlight"
trigger should do the trick.

Best regards,
								Pavel

Comments

Dan Murphy Aug. 30, 2018, 7:41 p.m. UTC | #1
Pavel

On 08/29/2018 04:20 PM, Pavel Machek wrote:
> 
> Here's preview of driver for TI LMU. It controls LEDs on Droid 4
> smartphone, including keyboard and screen backlights.
> 
> This adds backlight support for the following TI LMU
> chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
> 
> 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>
> 
> ---
> 
> Does it looks mostly reasonable? I guess it will need some
> s/BACKLIGHT/LEDS/ , and I'll need to remove my debugging hacks.
> 
> I'd prefer this to be LED driver, first; I'll need to figure out what
> to do with backlight. I guess something like existing "backlight"
> trigger should do the trick.
> 

I looked at this driver from Milo before submitting a specific LM3697 driver.

I do not like this driver.
I don't like that it smashes numerous devices into some structure with varying register maps.

Not only that but it appears that you just pulled this driver from a repo and posted it without clean up.

If the devices share register maps and can be added to families I would prefer to do it that way.

So if the LM3695 and LM3697 share the same features and register map they should be one driver
The LM363x series may be able to be a different driver.

I would prefer separated drivers rather then trying to consolidate them.  Otherwise we may have other devices
trying to be shoe horned into this framework.

Dan

> Best regards,
> 								Pavel
> 
> 
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index 44097a3..7b0929e 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 BACKLIGHT_TI_LMU
> +       tristate "Backlight driver for TI LMU"
> +       depends on BACKLIGHT_CLASS_DEVICE && MFD_TI_LMU
> +       help
> +         Say Y to enable the backlight 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..8edb9bf 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -78,6 +78,9 @@ 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-backlight-objs                   := ti-lmu-backlight-core.o \
> +                                           ti-lmu-backlight-data.o
> +obj-$(CONFIG_BACKLIGHT_TI_LMU)          += ti-lmu-backlight.o
>  
>  # LED SPI Drivers
>  obj-$(CONFIG_LEDS_CR0014114)		+= leds-cr0014114.o
> diff --git a/drivers/leds/ti-lmu-backlight-core.c b/drivers/leds/ti-lmu-backlight-core.c
> new file mode 100644
> index 0000000..c9af061
> --- /dev/null
> +++ b/drivers/leds/ti-lmu-backlight-core.c
> @@ -0,0 +1,556 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2015 Texas Instruments
> + * Copyright 2018 Sebastian Reichel
> + *
> + * TI LMU Backlight 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-backlight-data.h"
> +
> +enum ti_lmu_bl_ramp_mode {
> +	BL_RAMP_UP,
> +	BL_RAMP_DOWN,
> +};
> +
> +#define NUM_DUAL_CHANNEL			2
> +#define LMU_BACKLIGHT_DUAL_CHANNEL_USED		(BIT(0) | BIT(1))
> +#define LMU_BACKLIGHT_11BIT_LSB_MASK		(BIT(0) | BIT(1) | BIT(2))
> +#define LMU_BACKLIGHT_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 backlight_device *backlight;
> +	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_BACKLIGHT_11BIT_LSB_MASK,
> +					 brightness);
> +		if (ret)
> +			return ret;
> +
> +		val = brightness >> LMU_BACKLIGHT_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_BACKLIGHT_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;
> +
> +	printk("lmu: register_led\n");
> +
> +	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;
> +
> +	printk("lmu: register_led\n");
> +	
> +	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);
> +//		name = kasprintf(GFP_KERNEL, "lcd_backlight", pdev->id);
> +	if (!name) {
> +		printk("No memory?!\n");
> +		return -ENOMEM;
> +	}
> +
> +	printk("Searching for device in parent: %pOFn", parent_node);
> +
> +	pdev->dev.of_node = of_get_child_by_name(parent_node, name);
> +	kfree(name);
> +
> +	if (!pdev->dev.of_node) {
> +		printk("No such child: %s\n", name);		
> +		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;
> +
> +	printk("lmu: set_brightness %d\n", lmu_bank->default_brightness);		
> +	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;
> +
> +	printk("lmu: bl probe\n");
> +	err = setup_of_node(pdev);
> +	if (err)
> +		return err;
> +
> +	printk("lmu: bank\n");	
> +	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;
> +
> +	if (!strcmp(lmu_bank->label, "keyboard")) {
> +		lmu_bank->label = "kbd_backlight";
> +	} else
> +		lmu_bank->default_brightness = 255;
> +
> +	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;
> +
> +	printk("lmu: brightness\n");		
> +	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-backlight",
> +	},
> +};
> +module_platform_driver(ti_lmu_bl_driver)
> +
> +MODULE_DESCRIPTION("TI LMU Backlight LED Driver");
> +MODULE_AUTHOR("Sebastian Reichel");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:ti-lmu-led-backlight");
> diff --git a/drivers/leds/ti-lmu-backlight-data.c b/drivers/leds/ti-lmu-backlight-data.c
> new file mode 100644
> index 0000000..583136c
> --- /dev/null
> +++ b/drivers/leds/ti-lmu-backlight-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-backlight-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-backlight-data.h b/drivers/leds/ti-lmu-backlight-data.h
> new file mode 100644
> index 0000000..c64e8e6
> --- /dev/null
> +++ b/drivers/leds/ti-lmu-backlight-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
>  
>
Pavel Machek Aug. 30, 2018, 8:18 p.m. UTC | #2
Hi!

> > Here's preview of driver for TI LMU. It controls LEDs on Droid 4
> > smartphone, including keyboard and screen backlights.
> > 
> > This adds backlight support for the following TI LMU
> > chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
> > 
> > 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>
> > 
> > ---
> > 
> > Does it looks mostly reasonable? I guess it will need some
> > s/BACKLIGHT/LEDS/ , and I'll need to remove my debugging hacks.
> > 
> > I'd prefer this to be LED driver, first; I'll need to figure out what
> > to do with backlight. I guess something like existing "backlight"
> > trigger should do the trick.
> > 
> 
> I looked at this driver from Milo before submitting a specific LM3697 driver.

Aha. I did not realize that was for same hardware... I should have
cc-ed you, I guess.

> I do not like this driver.
> I don't like that it smashes numerous devices into some structure with varying register maps.
>

Can you elaborate? The chips are similar enough that single driver
makes sense, and we certainly want to maintain one driver, not 6
drivers differing only in .. what exactly?

> Not only that but it appears that you just pulled this driver from a repo and posted it without clean up.
> 

a) No I did not, feel free to generate a diff.

b) Even if I did, why would that be a problem?

> If the devices share register maps and can be added to families I would prefer to do it that way.
> 
> So if the LM3695 and LM3697 share the same features and register map they should be one driver
> The LM363x series may be able to be a different driver.

Well all 6 chips this driver supports seem to be similar enough, so
that single driver makes sense.

Best regards,
								Pavel
kernel test robot Aug. 30, 2018, 8:37 p.m. UTC | #3
Hi Pavel,

I love your patch! Yet something to improve:

[auto build test ERROR on j.anaszewski-leds/for-next]
[also build test ERROR on v4.19-rc1 next-20180830]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Pavel-Machek/leds-add-TI-LMU-backlight-driver/20180830-220414
base:   https://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds.git for-next
config: i386-allmodconfig (attached as .config)
compiler: gcc-7 (Debian 7.3.0-16) 7.3.0
reproduce:
        # save the attached .config to linux build tree
        make ARCH=i386 

All errors (new ones prefixed by >>):

   drivers/leds/ti-lmu-backlight-core.c: In function 'ti_lmu_bl_init':
>> drivers/leds/ti-lmu-backlight-core.c:412:19: error: 'struct ti_lmu' has no member named 'backlight_initialized'
     if (lmu_bank->lmu->backlight_initialized)
                      ^~
   drivers/leds/ti-lmu-backlight-core.c:414:15: error: 'struct ti_lmu' has no member named 'backlight_initialized'
     lmu_bank->lmu->backlight_initialized = true;
                  ^~
   drivers/leds/ti-lmu-backlight-core.c: In function 'ti_lmu_bl_probe':
>> drivers/leds/ti-lmu-backlight-core.c:496:43: error: 'struct ti_lmu' has no member named 'id'
     lmu_bank->cfg = &lmu_bl_cfg[lmu_bank->lmu->id];
                                              ^~

vim +412 drivers/leds/ti-lmu-backlight-core.c

   403	
   404	static int ti_lmu_bl_init(struct ti_lmu_bank *lmu_bank)
   405	{
   406		struct regmap *regmap = lmu_bank->lmu->regmap;
   407		const struct lmu_bl_reg_data *regdata =
   408			lmu_bank->cfg->reginfo->init;
   409		int num_init = lmu_bank->cfg->reginfo->num_init;
   410		int i, ret;
   411	
 > 412		if (lmu_bank->lmu->backlight_initialized)
   413			return 0;
   414		lmu_bank->lmu->backlight_initialized = true;
   415	
   416		for (i = 0; regdata && i < num_init; i++) {
   417			ret = regmap_update_bits(regmap, regdata->reg, regdata->mask,
   418						 regdata->val);
   419			if (ret)
   420				return ret;
   421	
   422			regdata++;
   423		}
   424	
   425		return 0;
   426	}
   427	
   428	static int ti_lmu_bl_reload(struct ti_lmu_bank *lmu_bank)
   429	{
   430		int err;
   431	
   432		ti_lmu_bl_init(lmu_bank);
   433	
   434		err = ti_lmu_bl_configure(lmu_bank);
   435		if (err)
   436			return err;
   437	
   438		printk("lmu: set_brightness %d\n", lmu_bank->default_brightness);		
   439		return ti_lmu_bl_set_brightness(lmu_bank, lmu_bank->current_brightness);
   440	}
   441	
   442	static int ti_lmu_bl_monitor_notifier(struct notifier_block *nb,
   443						     unsigned long action, void *unused)
   444	{
   445		struct ti_lmu_bank *lmu_bank = container_of(nb, struct ti_lmu_bank, nb);
   446	
   447		if (action == LMU_EVENT_MONITOR_DONE) {
   448			if (ti_lmu_bl_reload(lmu_bank))
   449				return NOTIFY_STOP;
   450		}
   451	
   452		return NOTIFY_OK;
   453	}
   454	
   455	static int ti_lmu_bl_probe(struct platform_device *pdev)
   456	{
   457		struct ti_lmu_bank *lmu_bank;
   458		int err;
   459	
   460		printk("lmu: bl probe\n");
   461		err = setup_of_node(pdev);
   462		if (err)
   463			return err;
   464	
   465		printk("lmu: bank\n");	
   466		lmu_bank = devm_kzalloc(&pdev->dev, sizeof(*lmu_bank), GFP_KERNEL);
   467		if (!lmu_bank)
   468			return -ENOMEM;
   469		lmu_bank->dev = &pdev->dev;
   470		dev_set_drvdata(&pdev->dev, lmu_bank);
   471	
   472		err = device_property_read_string(&pdev->dev, "label",
   473						  &lmu_bank->label);
   474		if (err)
   475			return err;
   476	
   477		if (!strcmp(lmu_bank->label, "keyboard")) {
   478			lmu_bank->label = "kbd_backlight";
   479		} else
   480			lmu_bank->default_brightness = 255;
   481	
   482		lmu_bank->leds = ti_lmu_parse_led_sources(&pdev->dev);
   483		if (lmu_bank->leds < 0)
   484			return lmu_bank->leds;
   485		else if (lmu_bank->leds == 0)
   486			return -EINVAL;
   487	
   488		device_property_read_u32(&pdev->dev, "default-brightness-level",
   489					 &lmu_bank->default_brightness);
   490		device_property_read_u32(&pdev->dev, "ti,ramp-up-ms",
   491					 &lmu_bank->ramp_up_msec);
   492		device_property_read_u32(&pdev->dev, "ti,ramp-down-ms",
   493					 &lmu_bank->ramp_down_msec);
   494		
   495		lmu_bank->lmu = dev_get_drvdata(pdev->dev.parent);
 > 496		lmu_bank->cfg = &lmu_bl_cfg[lmu_bank->lmu->id];
   497		lmu_bank->bank_id = pdev->id;
   498	
   499		ti_lmu_bl_init(lmu_bank);
   500	
   501		err = ti_lmu_bl_configure(lmu_bank);
   502		if (err)
   503			return err;
   504	
   505		err = ti_lmu_bl_add_device(lmu_bank);
   506		if (err)
   507			return err;
   508	
   509		printk("lmu: brightness\n");		
   510		err = ti_lmu_bl_set_brightness(lmu_bank,
   511						      lmu_bank->default_brightness);
   512		if (err)
   513			return err;
   514	
   515		/*
   516		 * Notifier callback is required because backlight device needs
   517		 * reconfiguration after fault detection procedure is done by
   518		 * ti-lmu-fault-monitor driver.
   519		 */
   520		if (lmu_bank->cfg->fault_monitor_used) {
   521			lmu_bank->nb.notifier_call = ti_lmu_bl_monitor_notifier;
   522			err = blocking_notifier_chain_register(&lmu_bank->lmu->notifier,
   523							       &lmu_bank->nb);
   524			if (err)
   525				return err;
   526		}
   527	
   528		return 0;
   529	}
   530	

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
Dan Murphy Aug. 31, 2018, 12:19 p.m. UTC | #4
On 08/30/2018 03:18 PM, Pavel Machek wrote:
> Hi!
> 
>>> Here's preview of driver for TI LMU. It controls LEDs on Droid 4
>>> smartphone, including keyboard and screen backlights.
>>>
>>> This adds backlight support for the following TI LMU
>>> chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
>>>
>>> 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>
>>>
>>> ---
>>>
>>> Does it looks mostly reasonable? I guess it will need some
>>> s/BACKLIGHT/LEDS/ , and I'll need to remove my debugging hacks.
>>>
>>> I'd prefer this to be LED driver, first; I'll need to figure out what
>>> to do with backlight. I guess something like existing "backlight"
>>> trigger should do the trick.
>>>
>>
>> I looked at this driver from Milo before submitting a specific LM3697 driver.
> 
> Aha. I did not realize that was for same hardware... I should have
> cc-ed you, I guess.
> 

No worries Jacek cc'd me.

>> I do not like this driver.
>> I don't like that it smashes numerous devices into some structure with varying register maps.
>>
> 
> Can you elaborate? The chips are similar enough that single driver
> makes sense, and we certainly want to maintain one driver, not 6
> drivers differing only in .. what exactly?

Well I think it is justified to have independent drivers as each device has different features from
this basic LED driver.  If we are just looking for basic support then yes this driver is fine.

But here is where a single driver will start getting messy and support difficult

LM3533 has ALS and an ADC for an ALS analog sensor
LM3631 has no ALS functionality
LM3632 has strobe functionality and no ramp support
LM3633 has indicator support coupled with the hvled support
LM3695 does not even appear to be available publicly
LM3697 is the only device that that this driver could be used for as is.

In addition if I wanted to only support a single device I have to pull in the full data
file that defines all the other devices.  That is pretty bad to increase the size of the kernel
image to have support for devices that are not even on the target product.  The ti-lmu data file
alone is ~15k, the ti-lmu code does not even build with this patch (So this is a nack on the patch as it is.)
but commenting out the offending code gives me at least ~23k more data on top
of the ti-lmu MFD framework which is ~33k.  That is ~71k of code just to support 1 LED device
that is 3x what we have now.  That is not good for IoT devices.

The LM3697 is 22k without ramp support.

It may make sense for Droid 4 but not for most other customer products.  This is why I created the
LM3697 as a customer is only using that specific part on their end product so why would they want
to pull in data structures for 5 other devices?  It won't make sense for IoT devices as memory
real estate is critical.

And I will continue. TI LMU really means nothing unless you know what LMU stands for.  But this
also now implies support for all of TI's lighting products which will confuse the heck out of
customers and TI support staff.  We have a similar issue in our SoC sound CODECs code that we are
attempting to clean up.  We have a generic driver that is supposed to support a common set of features
but when extending support for other features the code gets very complicated and it is almost more
beneficial to re-write and create a new driver.

I would be OK with creating a ti-lmu framework that commonizes the functionality and create children that
register to that framework but even that is over kill.

> 
>> Not only that but it appears that you just pulled this driver from a repo and posted it without clean up.
>>
> 
> a) No I did not, feel free to generate a diff.
> 

No I looked at this driver before and determined a re-write was a waste of time.
If you look that the LM3697 driver I submitted it configures the banks via the config file.

Only the ramp code is missing and the code is much simpler in nature.  We could adopt that
code to extend out to the other devices as well and base the register map deltas on the 
device configuration file.  I am already using the fwnode calls and setting up the banks.


> b) Even if I did, why would that be a problem?

So what you are saying is all I have to do is look for other peoples work pull it in to a branch
slap a new copyright on it, push to a list and claim support?

I don't think you checked with the original author on pushing it upstream.  Milo has not updated
his email with a public one unless you sent him a private email and received and ack on pushing

> 
>> If the devices share register maps and can be added to families I would prefer to do it that way.
>>
>> So if the LM3695 and LM3697 share the same features and register map they should be one driver
>> The LM363x series may be able to be a different driver.
> 
> Well all 6 chips this driver supports seem to be similar enough, so
> that single driver makes sense.

See above on device differentiation. So in my opinion a single driver that supports
basic functionality is good but when adding additional support this driver will get very
complicated as to only allow different features for different devices.

Dan

> 
> Best regards,
> 								Pavel
>
Pavel Machek Aug. 31, 2018, 9:30 p.m. UTC | #5
Hi!

> > Aha. I did not realize that was for same hardware... I should have
> > cc-ed you, I guess.
> 
> No worries Jacek cc'd me.

Good.

> >> I do not like this driver.
> >> I don't like that it smashes numerous devices into some structure with varying register maps.
> >>
> > 
> > Can you elaborate? The chips are similar enough that single driver
> > makes sense, and we certainly want to maintain one driver, not 6
> > drivers differing only in .. what exactly?
> 
> Well I think it is justified to have independent drivers as each device has different features from
> this basic LED driver.  If we are just looking for basic support then yes this driver is fine.

> But here is where a single driver will start getting messy and support difficult
> 
> LM3533 has ALS and an ADC for an ALS analog sensor
> LM3631 has no ALS functionality
> LM3632 has strobe functionality and no ramp support
> LM3633 has indicator support coupled with the hvled support
> LM3695 does not even appear to be available publicly
> LM3697 is the only device that that this driver could be used for as is.

Ok, so ... yes, I'm really interested in basic support. But drivers
seem to have a lot in common, voltage limits, for example. 

> In addition if I wanted to only support a single device I have to pull in the full data
> file that defines all the other devices.  That is pretty bad to increase the size of the kernel
> image to have support for devices that are not even on the target product.  The ti-lmu data file
> alone is ~15k, the ti-lmu code does not even build with this patch (So this is a nack on the patch as it is.)
> but commenting out the offending code gives me at least ~23k more data on top
> of the ti-lmu MFD framework which is ~33k.  That is ~71k of code just to support 1 LED device
> that is 3x what we have now.  That is not good for IoT devices.

> The LM3697 is 22k without ramp support.

Well, I don't think object file size is huge problem. First,
"distribution" kernel with support for 6 different chips will be ~71k,
while your proposal will result in ~136k. Second, yes, we could put
ifdefs into ti-lmu data file to make it smaller.

Anyway, clean source code and easy maintainance is more important.

> 
> And I will continue. TI LMU really means nothing unless you know what LMU stands for.  But this
> also now implies support for all of TI's lighting products which will confuse the heck out of
> customers and TI support staff.  We have a similar issue in our SoC sound CODECs code that we are
> attempting to clean up.  We have a generic driver that is supposed to support a common set of features
> but when extending support for other features the code gets very complicated and it is almost more
> beneficial to re-write and create a new driver.
> 
> I would be OK with creating a ti-lmu framework that commonizes the functionality and create children that
> register to that framework but even that is over kill.
>

I believe there's quite a lot of code that could be shared. 

> >> Not only that but it appears that you just pulled this driver from a repo and posted it without clean up.
> >>
> > 
> > a) No I did not, feel free to generate a diff.
> > 
> 
> No I looked at this driver before and determined a re-write was a waste of time.
> If you look that the LM3697 driver I submitted it configures the banks via the config file.
> 
> Only the ramp code is missing and the code is much simpler in nature.  We could adopt that
> code to extend out to the other devices as well and base the register map deltas on the 
> device configuration file.  I am already using the fwnode calls and setting up the banks.

Well, you may know that hardware better than I do. But if you generate
a diff...

> > b) Even if I did, why would that be a problem?
> 
> So what you are saying is all I have to do is look for other peoples work pull it in to a branch
> slap a new copyright on it, push to a list and claim support?

...you'll see I did quite some changes.

> I don't think you checked with the original author on pushing it upstream.  Milo has not updated
> his email with a public one unless you sent him a private email and received and ack on pushing
>

Tony Lindgren asked me to get support merged, and that's what I'm
trying to do. I have Milo's sign off, that means he is/was ok with
upstreaming that code; no, I did not have reason to contact him.

> > Well all 6 chips this driver supports seem to be similar enough, so
> > that single driver makes sense.
> 
> See above on device differentiation. So in my opinion a single driver that supports
> basic functionality is good but when adding additional support this driver will get very
> complicated as to only allow different features for different devices.

Well, the devices seem to have quite a lot in common. Yes, single
driver for all of them will be somehow complex; but I believe that's
still better than copy&pasting code.

Anyway, I showed my proposal. I need support for ti,lm3532, but I can
get basic support for many other chips with very little code.

Do you plan basic support for support other chips in near future? Is
TI willing to maintain the LED drivers?

What is your proposal for lm3532 support? Should I just cp lm3697
lm3532, then adjust register map? (Will not that cause rather ugly
code duplication?)

Best regards,
								Pavel
Dan Murphy Sept. 4, 2018, 2:34 p.m. UTC | #6
Pavel

On 08/31/2018 04:30 PM, Pavel Machek wrote:
> Hi!
> 
>>> Aha. I did not realize that was for same hardware... I should have
>>> cc-ed you, I guess.
>>
>> No worries Jacek cc'd me.
> 
> Good.
> 
>>>> I do not like this driver.
>>>> I don't like that it smashes numerous devices into some structure with varying register maps.
>>>>
>>>
>>> Can you elaborate? The chips are similar enough that single driver
>>> makes sense, and we certainly want to maintain one driver, not 6
>>> drivers differing only in .. what exactly?
>>
>> Well I think it is justified to have independent drivers as each device has different features from
>> this basic LED driver.  If we are just looking for basic support then yes this driver is fine.
> 
>> But here is where a single driver will start getting messy and support difficult
>>
>> LM3533 has ALS and an ADC for an ALS analog sensor
>> LM3631 has no ALS functionality
>> LM3632 has strobe functionality and no ramp support
>> LM3633 has indicator support coupled with the hvled support
>> LM3695 does not even appear to be available publicly
>> LM3697 is the only device that that this driver could be used for as is.
> 
> Ok, so ... yes, I'm really interested in basic support. But drivers
> seem to have a lot in common, voltage limits, for example. 
> 
>> In addition if I wanted to only support a single device I have to pull in the full data
>> file that defines all the other devices.  That is pretty bad to increase the size of the kernel
>> image to have support for devices that are not even on the target product.  The ti-lmu data file
>> alone is ~15k, the ti-lmu code does not even build with this patch (So this is a nack on the patch as it is.)
>> but commenting out the offending code gives me at least ~23k more data on top
>> of the ti-lmu MFD framework which is ~33k.  That is ~71k of code just to support 1 LED device
>> that is 3x what we have now.  That is not good for IoT devices.
> 
>> The LM3697 is 22k without ramp support.
> 
> Well, I don't think object file size is huge problem. First,
> "distribution" kernel with support for 6 different chips will be ~71k,
> while your proposal will result in ~136k. Second, yes, we could put
> ifdefs into ti-lmu data file to make it smaller.
> 
> Anyway, clean source code and easy maintainance is more important.

I am going to reply here and snip the rest of the chain.

My proposal is to create a ti-lmu-led-common file that contains all the common
code.  We can have LED drivers use that common code to perform the common tasks.
This can then be extended to other devices past, present or future that have the
same feature set.

Then the register maps and LED registration can be contained in each LED driver and any additional features
can be supported in the LED driver.  The common code will retrieve any device settings from
the firmware that it is interested in.

I can throw some code together and RFC the code.  This way we get the common core for the
chips and not the bloat or messy source with a single driver.

And yes I can put this together and support it if it is needed.  I just need to go get the EVMs
so I can test it.

Thoughts?

<snip>-- 
------------------
Dan Murphy
Pavel Machek Sept. 6, 2018, 10:16 a.m. UTC | #7
Hi!

> > Well, I don't think object file size is huge problem. First,
> > "distribution" kernel with support for 6 different chips will be ~71k,
> > while your proposal will result in ~136k. Second, yes, we could put
> > ifdefs into ti-lmu data file to make it smaller.
> > 
> > Anyway, clean source code and easy maintainance is more important.
> 
> I am going to reply here and snip the rest of the chain.
> 
> My proposal is to create a ti-lmu-led-common file that contains all the common
> code.  We can have LED drivers use that common code to perform the common tasks.
> This can then be extended to other devices past, present or future that have the
> same feature set.

Sounds good. But we'll still need to have the structure with register
info, right?

> Then the register maps and LED registration can be contained in each LED driver and any additional features
> can be supported in the LED driver.  The common code will retrieve any device settings from
> the firmware that it is interested in.

From the device tree?

> I can throw some code together and RFC the code.  This way we get the common core for the
> chips and not the bloat or messy source with a single driver.
> 
> And yes I can put this together and support it if it is needed.  I just need to go get the EVMs
> so I can test it.

Well, if possible, I'd like to see how the code would look. Example
for single LED type would be enough... If it is reasonable, I can port
it to Droid 4 or at least provide testing.

Thanks,
									Pavel
diff mbox series

Patch

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 44097a3..7b0929e 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 BACKLIGHT_TI_LMU
+       tristate "Backlight driver for TI LMU"
+       depends on BACKLIGHT_CLASS_DEVICE && MFD_TI_LMU
+       help
+         Say Y to enable the backlight 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..8edb9bf 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -78,6 +78,9 @@  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-backlight-objs                   := ti-lmu-backlight-core.o \
+                                           ti-lmu-backlight-data.o
+obj-$(CONFIG_BACKLIGHT_TI_LMU)          += ti-lmu-backlight.o
 
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_CR0014114)		+= leds-cr0014114.o
diff --git a/drivers/leds/ti-lmu-backlight-core.c b/drivers/leds/ti-lmu-backlight-core.c
new file mode 100644
index 0000000..c9af061
--- /dev/null
+++ b/drivers/leds/ti-lmu-backlight-core.c
@@ -0,0 +1,556 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2015 Texas Instruments
+ * Copyright 2018 Sebastian Reichel
+ *
+ * TI LMU Backlight 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-backlight-data.h"
+
+enum ti_lmu_bl_ramp_mode {
+	BL_RAMP_UP,
+	BL_RAMP_DOWN,
+};
+
+#define NUM_DUAL_CHANNEL			2
+#define LMU_BACKLIGHT_DUAL_CHANNEL_USED		(BIT(0) | BIT(1))
+#define LMU_BACKLIGHT_11BIT_LSB_MASK		(BIT(0) | BIT(1) | BIT(2))
+#define LMU_BACKLIGHT_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 backlight_device *backlight;
+	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_BACKLIGHT_11BIT_LSB_MASK,
+					 brightness);
+		if (ret)
+			return ret;
+
+		val = brightness >> LMU_BACKLIGHT_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_BACKLIGHT_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;
+
+	printk("lmu: register_led\n");
+
+	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;
+
+	printk("lmu: register_led\n");
+	
+	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);
+//		name = kasprintf(GFP_KERNEL, "lcd_backlight", pdev->id);
+	if (!name) {
+		printk("No memory?!\n");
+		return -ENOMEM;
+	}
+
+	printk("Searching for device in parent: %pOFn", parent_node);
+
+	pdev->dev.of_node = of_get_child_by_name(parent_node, name);
+	kfree(name);
+
+	if (!pdev->dev.of_node) {
+		printk("No such child: %s\n", name);		
+		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;
+
+	printk("lmu: set_brightness %d\n", lmu_bank->default_brightness);		
+	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;
+
+	printk("lmu: bl probe\n");
+	err = setup_of_node(pdev);
+	if (err)
+		return err;
+
+	printk("lmu: bank\n");	
+	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;
+
+	if (!strcmp(lmu_bank->label, "keyboard")) {
+		lmu_bank->label = "kbd_backlight";
+	} else
+		lmu_bank->default_brightness = 255;
+
+	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;
+
+	printk("lmu: brightness\n");		
+	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-backlight",
+	},
+};
+module_platform_driver(ti_lmu_bl_driver)
+
+MODULE_DESCRIPTION("TI LMU Backlight LED Driver");
+MODULE_AUTHOR("Sebastian Reichel");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:ti-lmu-led-backlight");
diff --git a/drivers/leds/ti-lmu-backlight-data.c b/drivers/leds/ti-lmu-backlight-data.c
new file mode 100644
index 0000000..583136c
--- /dev/null
+++ b/drivers/leds/ti-lmu-backlight-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-backlight-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-backlight-data.h b/drivers/leds/ti-lmu-backlight-data.h
new file mode 100644
index 0000000..c64e8e6
--- /dev/null
+++ b/drivers/leds/ti-lmu-backlight-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