lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20160111095702.GS14104@x1>
Date:	Mon, 11 Jan 2016 09:57:02 +0000
From:	Lee Jones <lee.jones@...aro.org>
To:	Milo Kim <milo.kim@...com>
Cc:	robh+dt@...nel.org, j.anaszewski@...sung.com, broonie@...nel.org,
	devicetree@...r.kernel.org, linux-leds@...r.kernel.org,
	linux-kernel@...r.kernel.org
Subject: Re: [PATCH v2 7/9] backlight: add TI LMU backlight driver

Jingoo (not CC'ed) needs to review this.

> This is consolidated driver which supports backlight devices below.
>   LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
> 
> Structure
> ---------
>   It consists of two parts - core and data.
> 
>   Core part supports features below.
>     - Backlight subsystem control
>     - Channel configuration from DT properties
>     - Light dimming effect control: ramp up and down.
>     - LMU fault monitor notifier handling
>     - PWM brightness control
> 
>   Data part describes device specific data.
>     - Register value configuration for each LMU device
>       : initialization, channel configuration, control mode, enable and
>         brightness.
>     - PWM action configuration
>     - Light dimming effect table
>     - Option for LMU fault monitor support
> 
> Macros for register data
> ------------------------
>   All LMU devices have 8-bit based registers. LMU_BL_REG() creates 24-bit
>   register value in data part. It consists of address, mask and value.
>   On the other hand, register value should be parsed when the driver
>   reads/writes data from/to I2C registers. Driver uses LMU_BL_GET_ADDR(),
>   LMU_BL_GET_MASK() and LMU_BL_GET_VAL() for this purpose.
> 
> Data structure
> --------------
>   ti_lmu_bl:         Backlight output channel data
>   ti_lmu_bl_chip:    Backlight device data. One device can have multiple
>                      backlight channel data.
>   ti_lmu_bl_reg:     Backlight device register data
>   ti_lmu_bl_cfg:     Backlight configuration data for each LMU device
> 
> Cc: Lee Jones <lee.jones@...aro.org> 
> Cc: Jacek Anaszewski <j.anaszewski@...sung.com>
> Cc: Mark Brown <broonie@...nel.org>
> Cc: Rob Herring <robh+dt@...nel.org>
> Cc: devicetree@...r.kernel.org
> Cc: linux-leds@...r.kernel.org 
> Cc: linux-kernel@...r.kernel.org
> Signed-off-by: Milo Kim <milo.kim@...com>
> ---
>  drivers/video/backlight/Kconfig                 |   7 +
>  drivers/video/backlight/Makefile                |   3 +
>  drivers/video/backlight/ti-lmu-backlight-core.c | 649 ++++++++++++++++++++++++
>  drivers/video/backlight/ti-lmu-backlight-data.c | 287 +++++++++++
>  include/linux/mfd/ti-lmu-backlight.h            | 290 +++++++++++
>  5 files changed, 1236 insertions(+)
>  create mode 100644 drivers/video/backlight/ti-lmu-backlight-core.c
>  create mode 100644 drivers/video/backlight/ti-lmu-backlight-data.c
>  create mode 100644 include/linux/mfd/ti-lmu-backlight.h
> 
> diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
> index 5ffa4b4..451d043 100644
> --- a/drivers/video/backlight/Kconfig
> +++ b/drivers/video/backlight/Kconfig
> @@ -427,6 +427,13 @@ config BACKLIGHT_SKY81452
>  	  To compile this driver as a module, choose M here: the module will
>  	  be called sky81452-backlight
>  
> +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.
> +
>  config BACKLIGHT_TPS65217
>  	tristate "TPS65217 Backlight"
>  	depends on BACKLIGHT_CLASS_DEVICE && MFD_TPS65217
> diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
> index 16ec534..0f74ce7 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -52,6 +52,9 @@ obj-$(CONFIG_BACKLIGHT_PM8941_WLED)	+= pm8941-wled.o
>  obj-$(CONFIG_BACKLIGHT_PWM)		+= pwm_bl.o
>  obj-$(CONFIG_BACKLIGHT_SAHARA)		+= kb3886_bl.o
>  obj-$(CONFIG_BACKLIGHT_SKY81452)	+= sky81452-backlight.o
> +ti-lmu-backlight-objs			:= ti-lmu-backlight-core.o \
> +					   ti-lmu-backlight-data.o
> +obj-$(CONFIG_BACKLIGHT_TI_LMU)		+= ti-lmu-backlight.o
>  obj-$(CONFIG_BACKLIGHT_TOSA)		+= tosa_bl.o
>  obj-$(CONFIG_BACKLIGHT_TPS65217)	+= tps65217_bl.o
>  obj-$(CONFIG_BACKLIGHT_WM831X)		+= wm831x_bl.o
> diff --git a/drivers/video/backlight/ti-lmu-backlight-core.c b/drivers/video/backlight/ti-lmu-backlight-core.c
> new file mode 100644
> index 0000000..838e2c2
> --- /dev/null
> +++ b/drivers/video/backlight/ti-lmu-backlight-core.c
> @@ -0,0 +1,649 @@
> +/*
> + * TI LMU (Lighting Management Unit) Backlight Driver
> + *
> + * Copyright 2015 Texas Instruments
> + *
> + * Author: Milo Kim <milo.kim@...com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/backlight.h>
> +#include <linux/bitops.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-backlight.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/slab.h>
> +
> +#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
> +#define DEFAULT_PWM_NAME			"lmu-backlight"
> +
> +static int ti_lmu_backlight_enable(struct ti_lmu_bl *lmu_bl, int enable)
> +{
> +	struct ti_lmu_bl_chip *chip = lmu_bl->chip;
> +	struct regmap *regmap = chip->lmu->regmap;
> +	unsigned long enable_time = chip->cfg->reginfo->enable_usec;
> +	u8 *reg = chip->cfg->reginfo->enable;
> +	u8 mask = BIT(lmu_bl->bank_id);
> +
> +	if (!reg)
> +		return -EINVAL;
> +
> +	if (enable)
> +		return regmap_update_bits(regmap, *reg, mask, mask);
> +	else
> +		return regmap_update_bits(regmap, *reg, mask, 0);
> +
> +	if (enable_time > 0)
> +		usleep_range(enable_time, enable_time + 100);
> +}
> +
> +static void ti_lmu_backlight_pwm_ctrl(struct ti_lmu_bl *lmu_bl, int brightness,
> +				      int max_brightness)
> +{
> +	struct pwm_device *pwm;
> +	unsigned int duty, period;
> +
> +	if (!lmu_bl->pwm) {
> +		pwm = devm_pwm_get(lmu_bl->chip->dev, DEFAULT_PWM_NAME);
> +		if (IS_ERR(pwm)) {
> +			dev_err(lmu_bl->chip->dev,
> +				"Can not get PWM device, err: %ld\n",
> +				PTR_ERR(pwm));
> +			return;
> +		}
> +
> +		lmu_bl->pwm = pwm;
> +	}
> +
> +	period = lmu_bl->pwm_period;
> +	duty = brightness * period / max_brightness;
> +
> +	pwm_config(lmu_bl->pwm, duty, period);
> +	if (duty)
> +		pwm_enable(lmu_bl->pwm);
> +	else
> +		pwm_disable(lmu_bl->pwm);
> +}
> +
> +static int ti_lmu_backlight_update_brightness_register(struct ti_lmu_bl *lmu_bl,
> +						       int brightness)
> +{
> +	const struct ti_lmu_bl_cfg *cfg = lmu_bl->chip->cfg;
> +	const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> +	struct regmap *regmap = lmu_bl->chip->lmu->regmap;
> +	u8 reg, val;
> +	int ret;
> +
> +	if (lmu_bl->mode == BL_PWM_BASED) {
> +		switch (cfg->pwm_action) {
> +		case UPDATE_PWM_ONLY:
> +			/* No register update is required */
> +			return 0;
> +		case UPDATE_MAX_BRT:
> +			/*
> +			 * PWM can start from any non-zero code and dim down
> +			 * to zero. So, brightness register should be updated
> +			 * even in PWM mode.
> +			 */
> +			if (brightness > 0)
> +				brightness = MAX_BRIGHTNESS_11BIT;
> +			else
> +				brightness = 0;
> +			break;
> +		default:
> +			break;
> +		}
> +	}
> +
> +	/*
> +	 * 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 (!reginfo->brightness_msb)
> +		return -EINVAL;
> +
> +	if (cfg->max_brightness == MAX_BRIGHTNESS_11BIT) {
> +		if (!reginfo->brightness_lsb)
> +			return -EINVAL;
> +
> +		reg = reginfo->brightness_lsb[lmu_bl->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) & 0xFF;
> +	} else {
> +		val = brightness & 0xFF;
> +	}
> +
> +	reg = reginfo->brightness_msb[lmu_bl->bank_id];
> +	return regmap_write(regmap, reg, val);
> +}
> +
> +static int ti_lmu_backlight_update_status(struct backlight_device *bl_dev)
> +{
> +	struct ti_lmu_bl *lmu_bl = bl_get_data(bl_dev);
> +	int brightness = bl_dev->props.brightness;
> +	int ret;
> +
> +	if (bl_dev->props.state & BL_CORE_SUSPENDED)
> +		brightness = 0;
> +
> +	if (brightness > 0)
> +		ret = ti_lmu_backlight_enable(lmu_bl, 1);
> +	else
> +		ret = ti_lmu_backlight_enable(lmu_bl, 0);
> +
> +	if (ret)
> +		return ret;
> +
> +	if (lmu_bl->mode == BL_PWM_BASED)
> +		ti_lmu_backlight_pwm_ctrl(lmu_bl, brightness,
> +					  bl_dev->props.max_brightness);
> +
> +	return ti_lmu_backlight_update_brightness_register(lmu_bl, brightness);
> +}
> +
> +static const struct backlight_ops lmu_backlight_ops = {
> +	.options = BL_CORE_SUSPENDRESUME,
> +	.update_status = ti_lmu_backlight_update_status,
> +};
> +
> +static int ti_lmu_backlight_of_get_ctrl_bank(struct device_node *np,
> +					     struct ti_lmu_bl *lmu_bl)
> +{
> +	const char *name;
> +	u32 *sources;
> +	int num_channels = lmu_bl->chip->cfg->num_channels;
> +	int ret, num_sources;
> +
> +	sources = devm_kzalloc(lmu_bl->chip->dev, num_channels, GFP_KERNEL);
> +	if (!sources)
> +		return -ENOMEM;
> +
> +	if (!of_property_read_string(np, "label", &name))
> +		lmu_bl->name = name;
> +	else
> +		lmu_bl->name = np->name;
> +
> +	ret = of_property_count_u32_elems(np, "led-sources");
> +	if (ret < 0 || ret > num_channels)
> +		return -EINVAL;
> +
> +	num_sources = ret;
> +	ret = of_property_read_u32_array(np, "led-sources", sources,
> +					 num_sources);
> +	if (ret)
> +		return ret;
> +
> +	lmu_bl->led_sources = 0;
> +	while (num_sources--)
> +		set_bit(sources[num_sources], &lmu_bl->led_sources);
> +
> +	return 0;
> +}
> +
> +static void ti_lmu_backlight_of_get_light_properties(struct device_node *np,
> +						     struct ti_lmu_bl *lmu_bl)
> +{
> +	of_property_read_u32(np, "default-brightness-level",
> +			     &lmu_bl->default_brightness);
> +
> +	of_property_read_u32(np, "ramp-up-msec",  &lmu_bl->ramp_up_msec);
> +	of_property_read_u32(np, "ramp-down-msec", &lmu_bl->ramp_down_msec);
> +}
> +
> +static void ti_lmu_backlight_of_get_brightness_mode(struct device_node *np,
> +						    struct ti_lmu_bl *lmu_bl)
> +{
> +	of_property_read_u32(np, "pwm-period", &lmu_bl->pwm_period);
> +
> +	if (lmu_bl->pwm_period > 0)
> +		lmu_bl->mode = BL_PWM_BASED;
> +	else
> +		lmu_bl->mode = BL_REGISTER_BASED;
> +}
> +
> +static int ti_lmu_backlight_of_create(struct ti_lmu_bl_chip *chip,
> +				      struct device_node *np)
> +{
> +	struct device_node *child;
> +	struct ti_lmu_bl *lmu_bl, *each;
> +	int ret, num_backlights;
> +	int i = 0;
> +
> +	num_backlights = of_get_child_count(np);
> +	if (num_backlights == 0) {
> +		dev_err(chip->dev, "No backlight strings\n");
> +		return -ENODEV;
> +	}
> +
> +	/* One chip can have mulitple backlight strings */
> +	lmu_bl = devm_kzalloc(chip->dev, sizeof(*lmu_bl) * num_backlights,
> +			      GFP_KERNEL);
> +	if (!lmu_bl)
> +		return -ENOMEM;
> +
> +	/* Child is mapped to LMU backlight control bank */
> +	for_each_child_of_node(np, child) {
> +		each = lmu_bl + i;
> +		each->bank_id = i;
> +		each->chip = chip;
> +
> +		ret = ti_lmu_backlight_of_get_ctrl_bank(child, each);
> +		if (ret) {
> +			of_node_put(np);
> +			return ret;
> +		}
> +
> +		ti_lmu_backlight_of_get_light_properties(child, each);
> +		ti_lmu_backlight_of_get_brightness_mode(child, each);
> +
> +		i++;
> +	}
> +
> +	chip->lmu_bl = lmu_bl;
> +	chip->num_backlights = num_backlights;
> +
> +	return 0;
> +}
> +
> +static int ti_lmu_backlight_create_channel(struct ti_lmu_bl *lmu_bl)
> +{
> +	struct regmap *regmap = lmu_bl->chip->lmu->regmap;
> +	u32 *reg = lmu_bl->chip->cfg->reginfo->channel;
> +	int num_channels = lmu_bl->chip->cfg->num_channels;
> +	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.
> +	 * Driver can get each data by using LMU_BL_GET_ADDR(),
> +	 * LMU_BL_GET_MASK(), LMU_BL_GET_VAL().
> +	 */
> +
> +	if (num_channels == NUM_DUAL_CHANNEL) {
> +		if (lmu_bl->led_sources == LMU_BACKLIGHT_DUAL_CHANNEL_USED)
> +			++reg;
> +
> +		return regmap_update_bits(regmap, LMU_BL_GET_ADDR(*reg),
> +					  LMU_BL_GET_MASK(*reg),
> +					  LMU_BL_GET_VAL(*reg));
> +	}
> +
> +	for (i = 0; i < num_channels; i++) {
> +		if (!reg)
> +			break;
> +
> +		/*
> +		 * Note that the result of LMU_BL_GET_VAL() is shift bit.
> +		 * The bank_id should be shifted for the channel configuration.
> +		 */
> +		if (test_bit(i, &lmu_bl->led_sources)) {
> +			shift = LMU_BL_GET_VAL(*reg);
> +			ret = regmap_update_bits(regmap, LMU_BL_GET_ADDR(*reg),
> +						 LMU_BL_GET_MASK(*reg),
> +						 lmu_bl->bank_id << shift);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		reg++;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ti_lmu_backlight_update_ctrl_mode(struct ti_lmu_bl *lmu_bl)
> +{
> +	struct regmap *regmap = lmu_bl->chip->lmu->regmap;
> +	u32 *reg = lmu_bl->chip->cfg->reginfo->mode + lmu_bl->bank_id;
> +	u8 val;
> +
> +	if (!reg)
> +		return 0;
> +
> +	/*
> +	 * Update PWM configuration register.
> +	 * If the mode is register based, then clear the bit.
> +	 */
> +	if (lmu_bl->mode == BL_PWM_BASED)
> +		val = LMU_BL_GET_VAL(*reg);
> +	else
> +		val = 0;
> +
> +	return regmap_update_bits(regmap, LMU_BL_GET_ADDR(*reg),
> +				  LMU_BL_GET_MASK(*reg), val);
> +}
> +
> +static int ti_lmu_backlight_convert_ramp_to_index(struct ti_lmu_bl *lmu_bl,
> +						  enum ti_lmu_bl_ramp_mode mode)
> +{
> +	const int *ramp_table = lmu_bl->chip->cfg->ramp_table;
> +	const int size = lmu_bl->chip->cfg->size_ramp;
> +	unsigned int msec;
> +	int i;
> +
> +	if (!ramp_table)
> +		return -EINVAL;
> +
> +	switch (mode) {
> +	case BL_RAMP_UP:
> +		msec = lmu_bl->ramp_up_msec;
> +		break;
> +	case BL_RAMP_DOWN:
> +		msec = lmu_bl->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_backlight_set_ramp(struct ti_lmu_bl *lmu_bl)
> +{
> +	struct regmap *regmap = lmu_bl->chip->lmu->regmap;
> +	const struct ti_lmu_bl_reg *reginfo = lmu_bl->chip->cfg->reginfo;
> +	int offset = reginfo->ramp_reg_offset;
> +	int i, ret, index;
> +	u32 reg;
> +
> +	for (i = BL_RAMP_UP; i <= BL_RAMP_DOWN; i++) {
> +		index = ti_lmu_backlight_convert_ramp_to_index(lmu_bl, i);
> +		if (index > 0) {
> +			if (!reginfo->ramp)
> +				break;
> +
> +			if (lmu_bl->bank_id == 0)
> +				reg = reginfo->ramp[i];
> +			else
> +				reg = reginfo->ramp[i] + offset;
> +
> +			/*
> +			 * Note that the result of LMU_BL_GET_VAL() is
> +			 * shift bit. So updated bit is shifted index value.
> +			 */
> +			ret = regmap_update_bits(regmap, LMU_BL_GET_ADDR(reg),
> +						 LMU_BL_GET_MASK(reg),
> +						 index << LMU_BL_GET_VAL(reg));
> +			if (ret)
> +				return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int ti_lmu_backlight_configure(struct ti_lmu_bl *lmu_bl)
> +{
> +	int ret;
> +
> +	ret = ti_lmu_backlight_create_channel(lmu_bl);
> +	if (ret)
> +		return ret;
> +
> +	ret = ti_lmu_backlight_update_ctrl_mode(lmu_bl);
> +	if (ret)
> +		return ret;
> +
> +	return ti_lmu_backlight_set_ramp(lmu_bl);
> +}
> +
> +static int ti_lmu_backlight_init(struct ti_lmu_bl_chip *chip)
> +{
> +	struct regmap *regmap = chip->lmu->regmap;
> +	u32 *reg = chip->cfg->reginfo->init;
> +	int num_init = chip->cfg->reginfo->num_init;
> +	int i, ret;
> +
> +	/*
> +	 * 'init' register data consists of address, mask, value.
> +	 * Driver can get each data by using LMU_BL_GET_ADDR(),
> +	 * LMU_BL_GET_MASK(), LMU_BL_GET_VAL().
> +	 */
> +
> +	for (i = 0; i < num_init; i++) {
> +		if (!reg)
> +			break;
> +
> +		ret = regmap_update_bits(regmap, LMU_BL_GET_ADDR(*reg),
> +					 LMU_BL_GET_MASK(*reg),
> +					 LMU_BL_GET_VAL(*reg));
> +		if (ret)
> +			return ret;
> +
> +		reg++;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ti_lmu_backlight_reload(struct ti_lmu_bl_chip *chip)
> +{
> +	struct ti_lmu_bl *each;
> +	int i, ret;
> +
> +	ret = ti_lmu_backlight_init(chip);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < chip->num_backlights; i++) {
> +		each = chip->lmu_bl + i;
> +		ret = ti_lmu_backlight_configure(each);
> +		if (ret)
> +			return ret;
> +
> +		backlight_update_status(each->bl_dev);
> +	}
> +
> +	return 0;
> +}
> +
> +static int ti_lmu_backlight_add_device(struct device *dev,
> +				       struct ti_lmu_bl *lmu_bl)
> +{
> +	struct backlight_device *bl_dev;
> +	struct backlight_properties props;
> +
> +	memset(&props, 0, sizeof(struct backlight_properties));
> +	props.type = BACKLIGHT_PLATFORM;
> +	props.brightness = lmu_bl->default_brightness;
> +	props.max_brightness = lmu_bl->chip->cfg->max_brightness;
> +
> +	bl_dev = devm_backlight_device_register(dev, lmu_bl->name,
> +						lmu_bl->chip->dev, lmu_bl,
> +						&lmu_backlight_ops, &props);
> +	if (IS_ERR(bl_dev))
> +		return PTR_ERR(bl_dev);
> +
> +	lmu_bl->bl_dev = bl_dev;
> +
> +	return 0;
> +}
> +
> +static struct ti_lmu_bl_chip *
> +ti_lmu_backlight_register(struct device *dev, struct ti_lmu *lmu,
> +			  const struct ti_lmu_bl_cfg *cfg)
> +{
> +	struct ti_lmu_bl_chip *chip;
> +	struct ti_lmu_bl *each;
> +	int i, ret;
> +
> +	if (!cfg) {
> +		dev_err(dev, "Operation is not configured\n");
> +		return ERR_PTR(-EINVAL);
> +	}
> +
> +	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
> +	if (!chip)
> +		return ERR_PTR(-ENOMEM);
> +
> +	chip->dev = dev;
> +	chip->lmu = lmu;
> +	chip->cfg = cfg;
> +
> +	ret = ti_lmu_backlight_of_create(chip, dev->of_node);
> +	if (ret)
> +		return ERR_PTR(ret);
> +
> +	ret = ti_lmu_backlight_init(chip);
> +	if (ret) {
> +		dev_err(dev, "Backlight init err: %d\n", ret);
> +		return ERR_PTR(ret);
> +	}
> +
> +	for (i = 0; i < chip->num_backlights; i++) {
> +		each = chip->lmu_bl + i;
> +
> +		ret = ti_lmu_backlight_configure(each);
> +		if (ret) {
> +			dev_err(dev, "Backlight config err: %d\n", ret);
> +			return ERR_PTR(ret);
> +		}
> +
> +		ret = ti_lmu_backlight_add_device(dev, each);
> +		if (ret) {
> +			dev_err(dev, "Backlight device err: %d\n", ret);
> +			return ERR_PTR(ret);
> +		}
> +
> +		backlight_update_status(each->bl_dev);
> +	}
> +
> +	return chip;
> +}
> +
> +static void ti_lmu_backlight_unregister(struct ti_lmu_bl_chip *chip)
> +{
> +	struct ti_lmu_bl *each;
> +	int i;
> +
> +	/* Turn off the brightness */
> +	for (i = 0; i < chip->num_backlights; i++) {
> +		each = chip->lmu_bl + i;
> +		each->bl_dev->props.brightness = 0;
> +		backlight_update_status(each->bl_dev);
> +	}
> +}
> +
> +static int ti_lmu_backlight_monitor_notifier(struct notifier_block *nb,
> +					     unsigned long action, void *unused)
> +{
> +	struct ti_lmu_bl_chip *chip = container_of(nb, struct ti_lmu_bl_chip,
> +						   nb);
> +
> +	if (action == LMU_EVENT_MONITOR_DONE) {
> +		if (ti_lmu_backlight_reload(chip))
> +			return NOTIFY_STOP;
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +static int ti_lmu_backlight_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct ti_lmu *lmu = dev_get_drvdata(dev->parent);
> +	struct ti_lmu_bl_chip *chip;
> +	int ret;
> +
> +	chip = ti_lmu_backlight_register(dev, lmu, &lmu_bl_cfg[pdev->id]);
> +	if (IS_ERR(chip))
> +		return PTR_ERR(chip);
> +
> +	/*
> +	 * Notifier callback is required because backlight device needs
> +	 * reconfiguration after fault detection procedure is done by
> +	 * ti-lmu-fault-monitor driver.
> +	 */
> +	if (chip->cfg->fault_monitor_used) {
> +		chip->nb.notifier_call = ti_lmu_backlight_monitor_notifier;
> +		ret = blocking_notifier_chain_register(&chip->lmu->notifier,
> +						       &chip->nb);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	platform_set_drvdata(pdev, chip);
> +
> +	return 0;
> +}
> +
> +static int ti_lmu_backlight_remove(struct platform_device *pdev)
> +{
> +	struct ti_lmu_bl_chip *chip = platform_get_drvdata(pdev);
> +
> +	if (chip->cfg->fault_monitor_used)
> +		blocking_notifier_chain_unregister(&chip->lmu->notifier,
> +						   &chip->nb);
> +
> +	ti_lmu_backlight_unregister(chip);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver ti_lmu_backlight_driver = {
> +	.probe  = ti_lmu_backlight_probe,
> +	.remove = ti_lmu_backlight_remove,
> +	.driver = {
> +		.name = "ti-lmu-backlight",
> +	},
> +};
> +
> +module_platform_driver(ti_lmu_backlight_driver)
> +
> +MODULE_DESCRIPTION("TI LMU Backlight Driver");
> +MODULE_AUTHOR("Milo Kim");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:ti-lmu-backlight");
> diff --git a/drivers/video/backlight/ti-lmu-backlight-data.c b/drivers/video/backlight/ti-lmu-backlight-data.c
> new file mode 100644
> index 0000000..0a486b2
> --- /dev/null
> +++ b/drivers/video/backlight/ti-lmu-backlight-data.c
> @@ -0,0 +1,287 @@
> +/*
> + * TI LMU (Lighting Management Unit) Backlight Device Data
> + *
> + * Copyright 2015 Texas Instruments
> + *
> + * Author: Milo Kim <milo.kim@...com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-backlight.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +#include <linux/module.h>
> +
> +/* LM3532 */
> +static u32 lm3532_init_regs[] = {
> +	LM3532_INIT_ZONE_0,
> +	LM3532_INIT_ZONE_1,
> +	LM3532_INIT_ZONE_2,
> +};
> +
> +static u32 lm3532_channel_regs[] = {
> +	LM3532_CHANNEL_1,
> +	LM3532_CHANNEL_2,
> +	LM3532_CHANNEL_3,
> +};
> +
> +static u32 lm3532_mode_regs[] = {
> +	LM3532_MODE_PWM_A,
> +	LM3532_MODE_PWM_B,
> +	LM3532_MODE_PWM_C,
> +};
> +
> +static u32 lm3532_ramp_regs[] = {
> +	LM3532_RAMPUP,
> +	LM3532_RAMPDN,
> +};
> +
> +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_regs,
> +	.num_init	= ARRAY_SIZE(lm3532_init_regs),
> +	.channel	= lm3532_channel_regs,
> +	.mode		= lm3532_mode_regs,
> +	.ramp		= lm3532_ramp_regs,
> +	.enable		= &lm3532_enable_reg,
> +	.brightness_msb	= lm3532_brightness_regs,
> +};
> +
> +/* LM3631 */
> +static u32 lm3631_init_regs[] = {
> +	LM3631_INIT_BRT_MODE,
> +	LM3631_INIT_DIMMING_MODE,
> +};
> +
> +static u32 lm3631_channel_regs[]  = {
> +	LM3631_SINGLE_CHANNEL,
> +	LM3631_DUAL_CHANNEL,
> +};
> +
> +static u32 lm3631_ramp_reg = LM3631_RAMP;
> +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_regs,
> +	.num_init	= ARRAY_SIZE(lm3631_init_regs),
> +	.channel	= lm3631_channel_regs,
> +	.ramp		= &lm3631_ramp_reg,
> +	.enable		= &lm3631_enable_reg,
> +	.brightness_msb	= &lm3631_brightness_msb_reg,
> +	.brightness_lsb	= &lm3631_brightness_lsb_reg,
> +};
> +
> +/* LM3632 */
> +static u32 lm3632_init_regs[] = {
> +	LM3632_INIT_OVP_25V,
> +	LM3632_INIT_SWFREQ_1MHZ,
> +};
> +
> +static u32 lm3632_channel_regs[]  = {
> +	LM3632_SINGLE_CHANNEL,
> +	LM3632_DUAL_CHANNEL,
> +};
> +
> +static u32 lm3632_mode_reg = LM3632_MODE_PWM;
> +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_regs,
> +	.num_init	= ARRAY_SIZE(lm3632_init_regs),
> +	.channel	= lm3632_channel_regs,
> +	.mode		= &lm3632_mode_reg,
> +	.enable		= &lm3632_enable_reg,
> +	.brightness_msb	= &lm3632_brightness_msb_reg,
> +	.brightness_lsb	= &lm3632_brightness_lsb_reg,
> +};
> +
> +/* LM3633 */
> +static u32 lm3633_init_regs[] = {
> +	LM3633_INIT_OVP_40V,
> +	LM3633_INIT_RAMP_SELECT,
> +};
> +
> +static u32 lm3633_channel_regs[]  = {
> +	LM3633_CHANNEL_HVLED1,
> +	LM3633_CHANNEL_HVLED2,
> +	LM3633_CHANNEL_HVLED3,
> +};
> +
> +static u32 lm3633_mode_regs[] = {
> +	LM3633_MODE_PWM_A,
> +	LM3633_MODE_PWM_B,
> +};
> +
> +static u32 lm3633_ramp_regs[] = {
> +	LM3633_RAMPUP,
> +	LM3633_RAMPDN,
> +};
> +
> +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_regs,
> +	.num_init	 = ARRAY_SIZE(lm3633_init_regs),
> +	.channel	 = lm3633_channel_regs,
> +	.mode		 = lm3633_mode_regs,
> +	.ramp		 = lm3633_ramp_regs,
> +	.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 u32 lm3695_init_regs[] = {
> +	LM3695_INIT_BRT_MODE,
> +};
> +
> +static u32 lm3695_channel_regs[]  = {
> +	LM3695_SINGLE_CHANNEL,
> +	LM3695_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_regs,
> +	.num_init	= ARRAY_SIZE(lm3695_init_regs),
> +	.channel	= lm3695_channel_regs,
> +	.enable		= &lm3695_enable_reg,
> +	.enable_usec	= 600,
> +	.brightness_msb	= &lm3695_brightness_msb_reg,
> +	.brightness_lsb	= &lm3695_brightness_lsb_reg,
> +};
> +
> +/* LM3697 */
> +static u32 lm3697_init_regs[] = {
> +	LM3697_INIT_RAMP_SELECT,
> +};
> +
> +static u32 lm3697_channel_regs[]  = {
> +	LM3697_CHANNEL_1,
> +	LM3697_CHANNEL_2,
> +	LM3697_CHANNEL_3,
> +};
> +
> +static u32 lm3697_mode_regs[] = {
> +	LM3697_MODE_PWM_A,
> +	LM3697_MODE_PWM_B,
> +};
> +
> +static u32 lm3697_ramp_regs[] = {
> +	LM3697_RAMPUP,
> +	LM3697_RAMPDN,
> +};
> +
> +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_regs,
> +	.num_init	 = ARRAY_SIZE(lm3697_init_regs),
> +	.channel	 = lm3697_channel_regs,
> +	.mode		 = lm3697_mode_regs,
> +	.ramp		 = lm3697_ramp_regs,
> +	.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,
> +};
> +
> +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,
> +	},
> +};
> +EXPORT_SYMBOL_GPL(lmu_bl_cfg);
> diff --git a/include/linux/mfd/ti-lmu-backlight.h b/include/linux/mfd/ti-lmu-backlight.h
> new file mode 100644
> index 0000000..43e5300
> --- /dev/null
> +++ b/include/linux/mfd/ti-lmu-backlight.h
> @@ -0,0 +1,290 @@
> +/*
> + * TI LMU (Lighting Management Unit) Backlight Common Driver
> + *
> + * Copyright 2015 Texas Instruments
> + *
> + * Author: Milo Kim <milo.kim@...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/backlight.h>
> +#include <linux/device.h>
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +#include <linux/notifier.h>
> +
> +/**
> + * LMU backlight register data
> + *	value[23:16] | mask[15:8] | address[7:0]
> + */
> +#define LMU_BL_REG(addr, mask, value)				\
> +	((value << 16) | (mask << 8) | addr)
> +
> +#define LMU_BL_GET_ADDR(x)	(x & 0xFF)
> +#define LMU_BL_GET_MASK(x)	((x >> 8) & 0xFF)
> +#define LMU_BL_GET_VAL(x)	((x >> 16) & 0xFF)
> +
> +#define LM3532_INIT_ZONE_0						\
> +	LMU_BL_REG(LM3532_REG_ZONE_CFG_A, LM3532_ZONE_MASK, LM3532_ZONE_0)
> +#define LM3532_INIT_ZONE_1						\
> +	LMU_BL_REG(LM3532_REG_ZONE_CFG_B, LM3532_ZONE_MASK, LM3532_ZONE_1)
> +#define LM3532_INIT_ZONE_2						\
> +	LMU_BL_REG(LM3532_REG_ZONE_CFG_C, LM3532_ZONE_MASK, LM3532_ZONE_2)
> +#define LM3532_CHANNEL_1						\
> +	LMU_BL_REG(LM3532_REG_OUTPUT_CFG, LM3532_ILED1_CFG_MASK,	\
> +	LM3532_ILED1_CFG_SHIFT)
> +#define LM3532_CHANNEL_2						\
> +	LMU_BL_REG(LM3532_REG_OUTPUT_CFG, LM3532_ILED2_CFG_MASK,	\
> +	LM3532_ILED2_CFG_SHIFT)
> +#define LM3532_CHANNEL_3						\
> +	LMU_BL_REG(LM3532_REG_OUTPUT_CFG, LM3532_ILED3_CFG_MASK,	\
> +	LM3532_ILED3_CFG_SHIFT)
> +#define LM3532_MODE_PWM_A						\
> +	LMU_BL_REG(LM3532_REG_PWM_A_CFG, LM3532_PWM_A_MASK, LM3532_PWM_ZONE_0)
> +#define LM3532_MODE_PWM_B						\
> +	LMU_BL_REG(LM3532_REG_PWM_B_CFG, LM3532_PWM_B_MASK, LM3532_PWM_ZONE_1)
> +#define LM3532_MODE_PWM_C						\
> +	LMU_BL_REG(LM3532_REG_PWM_C_CFG, LM3532_PWM_C_MASK, LM3532_PWM_ZONE_2)
> +#define LM3532_RAMPUP							\
> +	LMU_BL_REG(LM3532_REG_RAMPUP, LM3532_RAMPUP_MASK, LM3532_RAMPUP_SHIFT)
> +#define LM3532_RAMPDN							\
> +	LMU_BL_REG(LM3532_REG_RAMPDN, LM3532_RAMPDN_MASK, LM3532_RAMPDN_SHIFT)
> +
> +#define LM3631_INIT_BRT_MODE						\
> +	LMU_BL_REG(LM3631_REG_BRT_MODE, LM3631_MODE_MASK, LM3631_DEFAULT_MODE)
> +#define LM3631_INIT_DIMMING_MODE					\
> +	LMU_BL_REG(LM3631_REG_BL_CFG, LM3631_MAP_MASK, LM3631_EXPONENTIAL_MAP)
> +#define LM3631_SINGLE_CHANNEL						\
> +	LMU_BL_REG(LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK,		\
> +	LM3631_BL_SINGLE_CHANNEL)
> +#define LM3631_DUAL_CHANNEL						\
> +	LMU_BL_REG(LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK,		\
> +	LM3631_BL_DUAL_CHANNEL)
> +#define LM3631_RAMP							\
> +	LMU_BL_REG(LM3631_REG_SLOPE, LM3631_SLOPE_MASK, LM3631_SLOPE_SHIFT)
> +
> +#define LM3632_INIT_OVP_25V						\
> +	LMU_BL_REG(LM3632_REG_CONFIG1, LM3632_OVP_MASK, LM3632_OVP_25V)
> +#define LM3632_INIT_SWFREQ_1MHZ						\
> +	LMU_BL_REG(LM3632_REG_CONFIG2, LM3632_SWFREQ_MASK, LM3632_SWFREQ_1MHZ)
> +#define LM3632_SINGLE_CHANNEL						\
> +	LMU_BL_REG(LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK,		\
> +	LM3632_BL_SINGLE_CHANNEL)
> +#define LM3632_DUAL_CHANNEL						\
> +	LMU_BL_REG(LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK,		\
> +	LM3632_BL_DUAL_CHANNEL)
> +#define LM3632_MODE_PWM							\
> +	LMU_BL_REG(LM3632_REG_IO_CTRL, LM3632_PWM_MASK, LM3632_PWM_MODE)
> +
> +#define LM3633_INIT_OVP_40V						\
> +	LMU_BL_REG(LM3633_REG_BOOST_CFG, LM3633_OVP_MASK, LM3633_OVP_40V)
> +#define LM3633_INIT_RAMP_SELECT						\
> +	LMU_BL_REG(LM3633_REG_BL_RAMP_CONF, LM3633_BL_RAMP_MASK,	\
> +	LM3633_BL_RAMP_EACH)
> +#define LM3633_CHANNEL_HVLED1						\
> +	LMU_BL_REG(LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED1_CFG_MASK,	\
> +	LM3633_HVLED1_CFG_SHIFT)
> +#define LM3633_CHANNEL_HVLED2						\
> +	LMU_BL_REG(LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED2_CFG_MASK,	\
> +	LM3633_HVLED2_CFG_SHIFT)
> +#define LM3633_CHANNEL_HVLED3						\
> +	LMU_BL_REG(LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED3_CFG_MASK,	\
> +	LM3633_HVLED3_CFG_SHIFT)
> +#define LM3633_MODE_PWM_A						\
> +	LMU_BL_REG(LM3633_REG_PWM_CFG, LM3633_PWM_A_MASK, LM3633_PWM_A_MASK)
> +#define LM3633_MODE_PWM_B						\
> +	LMU_BL_REG(LM3633_REG_PWM_CFG, LM3633_PWM_B_MASK, LM3633_PWM_B_MASK)
> +#define LM3633_RAMPUP							\
> +	LMU_BL_REG(LM3633_REG_BL0_RAMP, LM3633_BL_RAMPUP_MASK,		\
> +	LM3633_BL_RAMPUP_SHIFT)
> +#define LM3633_RAMPDN							\
> +	LMU_BL_REG(LM3633_REG_BL0_RAMP, LM3633_BL_RAMPDN_MASK,		\
> +	LM3633_BL_RAMPDN_SHIFT)
> +
> +#define LM3695_INIT_BRT_MODE						\
> +	LMU_BL_REG(LM3695_REG_GP, LM3695_BRT_RW_MASK, LM3695_BRT_RW_MASK)
> +#define LM3695_SINGLE_CHANNEL						\
> +	LMU_BL_REG(LM3695_REG_GP, LM3695_BL_CHANNEL_MASK,		\
> +	LM3695_BL_SINGLE_CHANNEL)
> +#define LM3695_DUAL_CHANNEL						\
> +	LMU_BL_REG(LM3695_REG_GP, LM3695_BL_CHANNEL_MASK,		\
> +	LM3695_BL_DUAL_CHANNEL)
> +
> +#define LM3697_INIT_RAMP_SELECT						\
> +	LMU_BL_REG(LM3697_REG_RAMP_CONF, LM3697_RAMP_MASK, LM3697_RAMP_EACH)
> +#define LM3697_CHANNEL_1						\
> +	LMU_BL_REG(LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED1_CFG_MASK,	\
> +	LM3697_HVLED1_CFG_SHIFT)
> +#define LM3697_CHANNEL_2						\
> +	LMU_BL_REG(LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED2_CFG_MASK,	\
> +	LM3697_HVLED2_CFG_SHIFT)
> +#define LM3697_CHANNEL_3						\
> +	LMU_BL_REG(LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED3_CFG_MASK,	\
> +	LM3697_HVLED3_CFG_SHIFT)
> +#define LM3697_MODE_PWM_A						\
> +	LMU_BL_REG(LM3697_REG_PWM_CFG, LM3697_PWM_A_MASK, LM3697_PWM_A_MASK)
> +#define LM3697_MODE_PWM_B						\
> +	LMU_BL_REG(LM3697_REG_PWM_CFG, LM3697_PWM_B_MASK, LM3697_PWM_B_MASK)
> +#define LM3697_RAMPUP							\
> +	LMU_BL_REG(LM3697_REG_BL0_RAMP, LM3697_RAMPUP_MASK, LM3697_RAMPUP_SHIFT)
> +#define LM3697_RAMPDN							\
> +	LMU_BL_REG(LM3697_REG_BL0_RAMP, LM3697_RAMPDN_MASK, LM3697_RAMPDN_SHIFT)
> +
> +#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
> +
> +#define MAX_BRIGHTNESS_8BIT		255
> +#define MAX_BRIGHTNESS_11BIT		2047
> +
> +enum ti_lmu_bl_ctrl_mode {
> +	BL_REGISTER_BASED,
> +	BL_PWM_BASED,
> +};
> +
> +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,
> +};
> +
> +enum ti_lmu_bl_ramp_mode {
> +	BL_RAMP_UP,
> +	BL_RAMP_DOWN,
> +};
> +
> +struct ti_lmu_bl;
> +struct ti_lmu_bl_chip;
> +
> +/**
> + * 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 {
> +	u32 *init;
> +	int num_init;
> +	u32 *channel;
> +	u32 *mode;
> +	u32 *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;
> +};
> +
> +/**
> + * struct ti_lmu_bl_chip
> + *
> + * @dev:		Parent device pointer
> + * @lmu:		LMU structure.
> + *			Used for register R/W access and notification.
> + * @cfg:		Device configuration data
> + * @lmu_bl:		Multiple backlight channels
> + * @num_backlights:	Number of backlight channels
> + * @nb:			Notifier block for handling LMU fault monitor event
> + *
> + * One backlight chip can have multiple backlight channels, 'ti_lmu_bl'.
> + */
> +struct ti_lmu_bl_chip {
> +	struct device *dev;
> +	struct ti_lmu *lmu;
> +	const struct ti_lmu_bl_cfg *cfg;
> +	struct ti_lmu_bl *lmu_bl;
> +	int num_backlights;
> +	struct notifier_block nb;
> +};
> +
> +/**
> + * struct ti_lmu_bl
> + *
> + * @chip:		Pointer to parent backlight device
> + * @bl_dev:		Backlight subsystem device structure
> + * @bank_id:		Backlight bank ID
> + * @name:		Backlight channel name
> + * @mode:		Backlight control mode
> + * @led_sources:	Backlight output channel configuration.
> + *			Bit mask is set on parsing DT.
> + * @default_brightness:	[Optional] Initial brightness value
> + * @ramp_up_msec:	[Optional] Ramp up time
> + * @ramp_down_msec:	[Optional] Ramp down time
> + * @pwm_period:		[Optional] PWM period
> + * @pwm:		[Optional] PWM subsystem structure
> + *
> + * Each backlight device has its own channel configuration.
> + * For chip control, parent chip data structure is used.
> + */
> +struct ti_lmu_bl {
> +	struct ti_lmu_bl_chip *chip;
> +	struct backlight_device *bl_dev;
> +
> +	int bank_id;
> +	const char *name;
> +	enum ti_lmu_bl_ctrl_mode mode;
> +	unsigned long led_sources;
> +
> +	unsigned int default_brightness;
> +
> +	/* Used for lighting effect */
> +	unsigned int ramp_up_msec;
> +	unsigned int ramp_down_msec;
> +
> +	/* Only valid in PWM mode */
> +	unsigned int pwm_period;
> +	struct pwm_device *pwm;
> +};
> +
> +extern struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID];
> +#endif

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ