Hi,
This adds backlight support for TI LMU devices. I updated the
patches from Milo Kim according to the comments from Daniel
Thompson and tested the result on Droid 4.
Changes since PATCHv1 [0]:
* split dt-binding documentation in its own patch
* move include/linux/mfd/ti-lmu-backlight.h to drivers/video/backlight/ti-lmu-backlight-data.h
* replace LMU_BL_REG() with a normal struct
* add "const" keyword to lots of internal structures
* improve error handling
* use atomic pwm api
[0] https://lkml.org/lkml/2017/3/19/265
-- Sebastian
Milo Kim (2):
dt-bindings: backlight: add ti-lmu-backlight binding
backlight: add TI LMU backlight driver
.../bindings/leds/backlight/ti-lmu-backlight.txt | 66 ++
drivers/video/backlight/Kconfig | 7 +
drivers/video/backlight/Makefile | 3 +
drivers/video/backlight/ti-lmu-backlight-core.c | 729 +++++++++++++++++++++
drivers/video/backlight/ti-lmu-backlight-data.c | 304 +++++++++
drivers/video/backlight/ti-lmu-backlight-data.h | 95 +++
6 files changed, 1204 insertions(+)
create mode 100644 Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt
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 drivers/video/backlight/ti-lmu-backlight-data.h
--
2.13.2
From: Milo Kim <[email protected]>
Add DT binding for ti-lmu devices.
Signed-off-by: Milo Kim <[email protected]>
Signed-off-by: Sebastian Reichel <[email protected]>
---
.../bindings/leds/backlight/ti-lmu-backlight.txt | 66 ++++++++++++++++++++++
1 file changed, 66 insertions(+)
create mode 100644 Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt
diff --git a/Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt b/Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt
new file mode 100644
index 000000000000..5fb1458a9136
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt
@@ -0,0 +1,66 @@
+TI LMU backlight device tree bindings
+
+Required property:
+ - compatible: Should be one of:
+ "ti,lm3532-backlight"
+ "ti,lm3631-backlight"
+ "ti,lm3632-backlight"
+ "ti,lm3633-backlight"
+ "ti,lm3695-backlight"
+ "ti,lm3697-backlight"
+
+Optional properties:
+ There are two backlight control mode. One is I2C, the other is PWM mode.
+ Following properties are only specified in PWM mode.
+ Please note that LMU backlight device can have only one PWM channel.
+
+ - pwms: OF device-tree PWM specification.
+ - pwm-names: a list of names for the PWM devices specified in the "pwms"
+ property.
+
+ For the PWM user nodes, please refer to [1].
+
+Child nodes:
+ LMU backlight is represented as sub-nodes of the TI LMU device [2].
+ So, LMU backlight should have more than one backlight child node.
+ Each node exactly matches with backlight control bank configuration.
+ Maximum numbers of child nodes depend on the device.
+
+ 1 = LM3631, LM3632, LM3695
+ 2 = LM3633, LM3697
+ 3 = LM3532
+
+ Required property of a child node:
+ - led-sources: List of enabled channels from 0 to 2.
+ Please refer to LED binding [3].
+ For output channels, please refer to the datasheets [4].
+
+ Optional properties of a child node:
+ - label: Backlight channel identification.
+ Please refer to LED binding [3].
+ - default-brightness-level: Backlight initial brightness value.
+ Type is <u32>. It is set as soon as backlight
+ device is created.
+ 0 ~ 2047 = LM3631, LM3632, LM3633, LM3695 and
+ LM3697
+ 0 ~ 255 = LM3532
+ - ramp-up-msec, ramp-down-msec: Light dimming effect properties.
+ Type is <u32>. Unit is millisecond.
+ 0 ~ 65 msec = LM3532
+ 0 ~ 4000 msec = LM3631
+ 0 ~ 16000 msec = LM3633 and LM3697
+ - pwm-period: PWM period. Only valid in PWM brightness mode.
+ Type is <u32>. If this property is missing, then control
+ mode is set to I2C by default.
+
+Examples: Please refer to ti-lmu dt-bindings. [2].
+
+[1] ../pwm/pwm.txt
+[2] ../mfd/ti-lmu.txt
+[3] ../leds/common.txt
+[4] LM3532: http://www.ti.com/product/LM3532/datasheet
+ LM3631: http://www.ti.com/product/LM3631/datasheet
+ LM3632: http://www.ti.com/product/LM3632A/datasheet
+ LM3633: http://www.ti.com/product/LM3633/datasheet
+ LM3695: Datasheet is not opened yet, but only two strings are used.
+ LM3697: http://www.ti.com/product/LM3697/datasheet
--
2.13.2
From: Milo Kim <[email protected]>
This is consolidated driver which supports the following
backlight devices: 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
Signed-off-by: Milo Kim <[email protected]>
Signed-off-by: Sebastian Reichel <[email protected]>
---
drivers/video/backlight/Kconfig | 7 +
drivers/video/backlight/Makefile | 3 +
drivers/video/backlight/ti-lmu-backlight-core.c | 729 ++++++++++++++++++++++++
drivers/video/backlight/ti-lmu-backlight-data.c | 304 ++++++++++
drivers/video/backlight/ti-lmu-backlight-data.h | 95 +++
5 files changed, 1138 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 drivers/video/backlight/ti-lmu-backlight-data.h
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 4e1d2ad50ba1..c3cc8334f9a2 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 8905129691e8..c532e43e344b 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 000000000000..fca95080ec27
--- /dev/null
+++ b/drivers/video/backlight/ti-lmu-backlight-core.c
@@ -0,0 +1,729 @@
+/*
+ * TI LMU (Lighting Management Unit) Backlight Driver
+ *
+ * Copyright 2015 Texas Instruments
+ *
+ * Author: Milo Kim <[email protected]>
+ *
+ * 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/device.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/kernel.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.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+
+#include "ti-lmu-backlight-data.h"
+
+enum ti_lmu_bl_ctrl_mode {
+ BL_REGISTER_BASED,
+ BL_PWM_BASED,
+};
+
+enum ti_lmu_bl_ramp_mode {
+ BL_RAMP_UP,
+ BL_RAMP_DOWN,
+};
+
+struct ti_lmu_bl;
+
+/**
+ * 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;
+};
+
+#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, bool 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);
+ 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_backlight_pwm_ctrl(struct ti_lmu_bl *lmu_bl, int brightness,
+ int max_brightness)
+{
+ struct pwm_state state = { };
+ int ret;
+
+ if (!lmu_bl->pwm) {
+ lmu_bl->pwm = devm_pwm_get(lmu_bl->chip->dev, DEFAULT_PWM_NAME);
+ if (IS_ERR(lmu_bl->pwm)) {
+ ret = PTR_ERR(lmu_bl->pwm);
+ lmu_bl->pwm = NULL;
+ dev_err(lmu_bl->chip->dev,
+ "Can not get PWM device, err: %d\n", ret);
+ return ret;
+ }
+ }
+
+ pwm_init_state(lmu_bl->pwm, &state);
+ state.period = lmu_bl->pwm_period;
+ state.duty_cycle = brightness * state.period / max_brightness;
+
+ if (state.duty_cycle)
+ state.enabled = true;
+ else
+ state.enabled = false;
+
+ ret = pwm_apply_state(lmu_bl->pwm, &state);
+ if (ret)
+ dev_err(lmu_bl->chip->dev, "Failed to configure PWM: %d", ret);
+
+ return ret;
+}
+
+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;
+
+ /*
+ * 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_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;
+ } else {
+ val = brightness;
+ }
+
+ 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);
+ const struct ti_lmu_bl_cfg *cfg = lmu_bl->chip->cfg;
+ int brightness = bl_dev->props.brightness;
+ bool enable = brightness > 0;
+ int ret;
+
+ if (bl_dev->props.state & BL_CORE_SUSPENDED)
+ brightness = 0;
+
+ ret = ti_lmu_backlight_enable(lmu_bl, enable);
+ if (ret)
+ return ret;
+
+ if (lmu_bl->mode == BL_PWM_BASED) {
+ ti_lmu_backlight_pwm_ctrl(lmu_bl, brightness,
+ bl_dev->props.max_brightness);
+
+ 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;
+ }
+ }
+
+ 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_check_channel(struct ti_lmu_bl *lmu_bl)
+{
+ const struct ti_lmu_bl_cfg *cfg = lmu_bl->chip->cfg;
+ const struct ti_lmu_bl_reg *reginfo = lmu_bl->chip->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_backlight_create_channel(struct ti_lmu_bl *lmu_bl)
+{
+ struct regmap *regmap = lmu_bl->chip->lmu->regmap;
+ const struct lmu_bl_reg_data *regdata =
+ 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.
+ */
+
+ if (num_channels == NUM_DUAL_CHANNEL) {
+ if (lmu_bl->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, &lmu_bl->led_sources)) {
+ shift = regdata->val;
+ ret = regmap_update_bits(regmap, regdata->reg,
+ regdata->mask,
+ lmu_bl->bank_id << shift);
+ if (ret)
+ return ret;
+ }
+
+ regdata++;
+ }
+
+ return 0;
+}
+
+static int ti_lmu_backlight_update_ctrl_mode(struct ti_lmu_bl *lmu_bl)
+{
+ struct regmap *regmap = lmu_bl->chip->lmu->regmap;
+ const struct lmu_bl_reg_data *regdata =
+ lmu_bl->chip->cfg->reginfo->mode + lmu_bl->bank_id;
+ u8 val = regdata->val;
+
+ if (!regdata)
+ return 0;
+
+ /*
+ * Update PWM configuration register.
+ * If the mode is register based, then clear the bit.
+ */
+ if (lmu_bl->mode != BL_PWM_BASED)
+ val = 0;
+
+ return regmap_update_bits(regmap, regdata->reg, regdata->mask, 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;
+ struct lmu_bl_reg_data regdata;
+
+ 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;
+
+ regdata = reginfo->ramp[i];
+ if (lmu_bl->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_backlight_configure(struct ti_lmu_bl *lmu_bl)
+{
+ int ret;
+
+ ret = ti_lmu_backlight_check_channel(lmu_bl);
+ if (ret)
+ return 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;
+ const struct lmu_bl_reg_data *regdata =
+ chip->cfg->reginfo->init;
+ int num_init = chip->cfg->reginfo->num_init;
+ int i, ret;
+
+ 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_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;
+
+ ret = backlight_update_status(each->bl_dev);
+ if (ret)
+ return ret;
+ }
+
+ 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);
+ }
+
+ ret = backlight_update_status(each->bl_dev);
+ if (ret) {
+ dev_err(dev, "Backlight update err: %d\n", ret);
+ return ERR_PTR(ret);
+ }
+ }
+
+ 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 000000000000..583136cb934d
--- /dev/null
+++ b/drivers/video/backlight/ti-lmu-backlight-data.c
@@ -0,0 +1,304 @@
+/*
+ * TI LMU (Lighting Management Unit) Backlight Device Data
+ *
+ * Copyright 2015 Texas Instruments
+ *
+ * Author: Milo Kim <[email protected]>
+ *
+ * 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/video/backlight/ti-lmu-backlight-data.h b/drivers/video/backlight/ti-lmu-backlight-data.h
new file mode 100644
index 000000000000..c64e8e6513e1
--- /dev/null
+++ b/drivers/video/backlight/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 <[email protected]>
+ *
+ * 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
--
2.13.2
On Mon, Jul 17, 2017 at 03:39:55PM +0200, Sebastian Reichel wrote:
> From: Milo Kim <[email protected]>
>
> Add DT binding for ti-lmu devices.
>
> Signed-off-by: Milo Kim <[email protected]>
> Signed-off-by: Sebastian Reichel <[email protected]>
> ---
> .../bindings/leds/backlight/ti-lmu-backlight.txt | 66 ++++++++++++++++++++++
> 1 file changed, 66 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt
>
> diff --git a/Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt b/Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt
> new file mode 100644
> index 000000000000..5fb1458a9136
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/leds/backlight/ti-lmu-backlight.txt
> @@ -0,0 +1,66 @@
> +TI LMU backlight device tree bindings
> +
> +Required property:
> + - compatible: Should be one of:
> + "ti,lm3532-backlight"
> + "ti,lm3631-backlight"
> + "ti,lm3632-backlight"
> + "ti,lm3633-backlight"
> + "ti,lm3695-backlight"
> + "ti,lm3697-backlight"
Some of the parts only do backlight, so drop the "-backlight" on those.
And having a sub-node makes no sense in that case. Perhaps it should be
2 binding documents separating the single function devices and MFDs. The
binding doc(s) doesn't have to align with the driver(s). You can have 1
driver serve 2 bindings.
> +
> +Optional properties:
> + There are two backlight control mode. One is I2C, the other is PWM mode.
The wording here is weird. My take of the datasheet is it is not
necessarily either I2C or PWM mode as I gathered from this description.
> + Following properties are only specified in PWM mode.
> + Please note that LMU backlight device can have only one PWM channel.
> +
> + - pwms: OF device-tree PWM specification.
> + - pwm-names: a list of names for the PWM devices specified in the "pwms"
> + property.
> +
> + For the PWM user nodes, please refer to [1].
> +
> +Child nodes:
> + LMU backlight is represented as sub-nodes of the TI LMU device [2].
> + So, LMU backlight should have more than one backlight child node.
> + Each node exactly matches with backlight control bank configuration.
> + Maximum numbers of child nodes depend on the device.
> +
> + 1 = LM3631, LM3632, LM3695
> + 2 = LM3633, LM3697
> + 3 = LM3532
> +
> + Required property of a child node:
> + - led-sources: List of enabled channels from 0 to 2.
> + Please refer to LED binding [3].
> + For output channels, please refer to the datasheets [4].
> +
> + Optional properties of a child node:
> + - label: Backlight channel identification.
> + Please refer to LED binding [3].
> + - default-brightness-level: Backlight initial brightness value.
> + Type is <u32>. It is set as soon as backlight
> + device is created.
> + 0 ~ 2047 = LM3631, LM3632, LM3633, LM3695 and
> + LM3697
> + 0 ~ 255 = LM3532
> + - ramp-up-msec, ramp-down-msec: Light dimming effect properties.
> + Type is <u32>. Unit is millisecond.
> + 0 ~ 65 msec = LM3532
> + 0 ~ 4000 msec = LM3631
> + 0 ~ 16000 msec = LM3633 and LM3697
> + - pwm-period: PWM period. Only valid in PWM brightness mode.
> + Type is <u32>. If this property is missing, then control
> + mode is set to I2C by default.
> +
> +Examples: Please refer to ti-lmu dt-bindings. [2].
> +
> +[1] ../pwm/pwm.txt
> +[2] ../mfd/ti-lmu.txt
> +[3] ../leds/common.txt
> +[4] LM3532: http://www.ti.com/product/LM3532/datasheet
> + LM3631: http://www.ti.com/product/LM3631/datasheet
> + LM3632: http://www.ti.com/product/LM3632A/datasheet
> + LM3633: http://www.ti.com/product/LM3633/datasheet
> + LM3695: Datasheet is not opened yet, but only two strings are used.
> + LM3697: http://www.ti.com/product/LM3697/datasheet
> --
> 2.13.2
>
Hi,
On Mon, Jul 24, 2017 at 11:58:26AM -0500, Rob Herring wrote:
> On Mon, Jul 17, 2017 at 03:39:55PM +0200, Sebastian Reichel wrote:
> > +Required property:
> > + - compatible: Should be one of:
> > + "ti,lm3532-backlight"
> > + "ti,lm3631-backlight"
> > + "ti,lm3632-backlight"
> > + "ti,lm3633-backlight"
> > + "ti,lm3695-backlight"
> > + "ti,lm3697-backlight"
>
> Some of the parts only do backlight, so drop the "-backlight" on those.
> And having a sub-node makes no sense in that case. Perhaps it should be
> 2 binding documents separating the single function devices and MFDs. The
> binding doc(s) doesn't have to align with the driver(s). You can have 1
> driver serve 2 bindings.
Yes, there is LM3532 and LM3695, which is backlight only as far as I
can see. Your suggestion contradicts with existing binding, though:
Documentation/devicetree/bindings/mfd/ti-lmu.txt
-- Sebastian
* Sebastian Reichel <[email protected]> [170717 06:40]:
> Hi,
>
> This adds backlight support for TI LMU devices. I updated the
> patches from Milo Kim according to the comments from Daniel
> Thompson and tested the result on Droid 4.
Works for me:
Tested-by: Tony Lindgren <[email protected]>
On 17/07/17 14:39, Sebastian Reichel wrote:
> From: Milo Kim <[email protected]>
>
> This is consolidated driver which supports the following
> backlight devices: 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
>
> Signed-off-by: Milo Kim <[email protected]>
> Signed-off-by: Sebastian Reichel <[email protected]>
Is there a v3 pending?
I just found this patch near the bottom of my TODO list but IIRC I
actually decided to defer final review of this driver until feedback on
the bindings was resolved (and as a result I've just taken it off the
TODO list).
Daniel.
> ---
> drivers/video/backlight/Kconfig | 7 +
> drivers/video/backlight/Makefile | 3 +
> drivers/video/backlight/ti-lmu-backlight-core.c | 729 ++++++++++++++++++++++++
> drivers/video/backlight/ti-lmu-backlight-data.c | 304 ++++++++++
> drivers/video/backlight/ti-lmu-backlight-data.h | 95 +++
> 5 files changed, 1138 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 drivers/video/backlight/ti-lmu-backlight-data.h
>
> diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
> index 4e1d2ad50ba1..c3cc8334f9a2 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 8905129691e8..c532e43e344b 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 000000000000..fca95080ec27
> --- /dev/null
> +++ b/drivers/video/backlight/ti-lmu-backlight-core.c
> @@ -0,0 +1,729 @@
> +/*
> + * TI LMU (Lighting Management Unit) Backlight Driver
> + *
> + * Copyright 2015 Texas Instruments
> + *
> + * Author: Milo Kim <[email protected]>
> + *
> + * 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/device.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/kernel.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.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/slab.h>
> +
> +#include "ti-lmu-backlight-data.h"
> +
> +enum ti_lmu_bl_ctrl_mode {
> + BL_REGISTER_BASED,
> + BL_PWM_BASED,
> +};
> +
> +enum ti_lmu_bl_ramp_mode {
> + BL_RAMP_UP,
> + BL_RAMP_DOWN,
> +};
> +
> +struct ti_lmu_bl;
> +
> +/**
> + * 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;
> +};
> +
> +#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, bool 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);
> + 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_backlight_pwm_ctrl(struct ti_lmu_bl *lmu_bl, int brightness,
> + int max_brightness)
> +{
> + struct pwm_state state = { };
> + int ret;
> +
> + if (!lmu_bl->pwm) {
> + lmu_bl->pwm = devm_pwm_get(lmu_bl->chip->dev, DEFAULT_PWM_NAME);
> + if (IS_ERR(lmu_bl->pwm)) {
> + ret = PTR_ERR(lmu_bl->pwm);
> + lmu_bl->pwm = NULL;
> + dev_err(lmu_bl->chip->dev,
> + "Can not get PWM device, err: %d\n", ret);
> + return ret;
> + }
> + }
> +
> + pwm_init_state(lmu_bl->pwm, &state);
> + state.period = lmu_bl->pwm_period;
> + state.duty_cycle = brightness * state.period / max_brightness;
> +
> + if (state.duty_cycle)
> + state.enabled = true;
> + else
> + state.enabled = false;
> +
> + ret = pwm_apply_state(lmu_bl->pwm, &state);
> + if (ret)
> + dev_err(lmu_bl->chip->dev, "Failed to configure PWM: %d", ret);
> +
> + return ret;
> +}
> +
> +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;
> +
> + /*
> + * 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_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;
> + } else {
> + val = brightness;
> + }
> +
> + 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);
> + const struct ti_lmu_bl_cfg *cfg = lmu_bl->chip->cfg;
> + int brightness = bl_dev->props.brightness;
> + bool enable = brightness > 0;
> + int ret;
> +
> + if (bl_dev->props.state & BL_CORE_SUSPENDED)
> + brightness = 0;
> +
> + ret = ti_lmu_backlight_enable(lmu_bl, enable);
> + if (ret)
> + return ret;
> +
> + if (lmu_bl->mode == BL_PWM_BASED) {
> + ti_lmu_backlight_pwm_ctrl(lmu_bl, brightness,
> + bl_dev->props.max_brightness);
> +
> + 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;
> + }
> + }
> +
> + 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_check_channel(struct ti_lmu_bl *lmu_bl)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bl->chip->cfg;
> + const struct ti_lmu_bl_reg *reginfo = lmu_bl->chip->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_backlight_create_channel(struct ti_lmu_bl *lmu_bl)
> +{
> + struct regmap *regmap = lmu_bl->chip->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata =
> + 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.
> + */
> +
> + if (num_channels == NUM_DUAL_CHANNEL) {
> + if (lmu_bl->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, &lmu_bl->led_sources)) {
> + shift = regdata->val;
> + ret = regmap_update_bits(regmap, regdata->reg,
> + regdata->mask,
> + lmu_bl->bank_id << shift);
> + if (ret)
> + return ret;
> + }
> +
> + regdata++;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_backlight_update_ctrl_mode(struct ti_lmu_bl *lmu_bl)
> +{
> + struct regmap *regmap = lmu_bl->chip->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata =
> + lmu_bl->chip->cfg->reginfo->mode + lmu_bl->bank_id;
> + u8 val = regdata->val;
> +
> + if (!regdata)
> + return 0;
> +
> + /*
> + * Update PWM configuration register.
> + * If the mode is register based, then clear the bit.
> + */
> + if (lmu_bl->mode != BL_PWM_BASED)
> + val = 0;
> +
> + return regmap_update_bits(regmap, regdata->reg, regdata->mask, 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;
> + struct lmu_bl_reg_data regdata;
> +
> + 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;
> +
> + regdata = reginfo->ramp[i];
> + if (lmu_bl->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_backlight_configure(struct ti_lmu_bl *lmu_bl)
> +{
> + int ret;
> +
> + ret = ti_lmu_backlight_check_channel(lmu_bl);
> + if (ret)
> + return 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;
> + const struct lmu_bl_reg_data *regdata =
> + chip->cfg->reginfo->init;
> + int num_init = chip->cfg->reginfo->num_init;
> + int i, ret;
> +
> + 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_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;
> +
> + ret = backlight_update_status(each->bl_dev);
> + if (ret)
> + return ret;
> + }
> +
> + 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);
> + }
> +
> + ret = backlight_update_status(each->bl_dev);
> + if (ret) {
> + dev_err(dev, "Backlight update err: %d\n", ret);
> + return ERR_PTR(ret);
> + }
> + }
> +
> + 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 000000000000..583136cb934d
> --- /dev/null
> +++ b/drivers/video/backlight/ti-lmu-backlight-data.c
> @@ -0,0 +1,304 @@
> +/*
> + * TI LMU (Lighting Management Unit) Backlight Device Data
> + *
> + * Copyright 2015 Texas Instruments
> + *
> + * Author: Milo Kim <[email protected]>
> + *
> + * 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/video/backlight/ti-lmu-backlight-data.h b/drivers/video/backlight/ti-lmu-backlight-data.h
> new file mode 100644
> index 000000000000..c64e8e6513e1
> --- /dev/null
> +++ b/drivers/video/backlight/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 <[email protected]>
> + *
> + * 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
>