* [PATCHv4 08/10] backlight: add TI LMU backlight driver
@ 2018-03-30 17:24 ` Sebastian Reichel
0 siblings, 0 replies; 62+ messages in thread
From: Sebastian Reichel @ 2018-03-30 17:24 UTC (permalink / raw)
To: Sebastian Reichel, Milo Kim, Lee Jones, Daniel Thompson,
Rob Herring, Tony Lindgren
Cc: Jingoo Han, Mark Rutland, linux-kernel, linux-fbdev, devicetree,
linux-omap, Sebastian Reichel, Milo Kim
This adds backlight support for the following TI LMU
chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
Signed-off-by: Milo Kim <milo.kim@ti.com>
[add LED subsystem support for keyboard backlight and rework DT
binding according to Rob Herrings feedback]
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
---
drivers/video/backlight/Kconfig | 7 +
drivers/video/backlight/Makefile | 3 +
drivers/video/backlight/ti-lmu-backlight-core.c | 666 ++++++++++++++++++++++++
drivers/video/backlight/ti-lmu-backlight-data.c | 304 +++++++++++
drivers/video/backlight/ti-lmu-backlight-data.h | 95 ++++
5 files changed, 1075 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 5d2d0d7e8100..27e6c5a0add8 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 19da71d518bf..a1132d3dfd4c 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -53,6 +53,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..a6099581edd7
--- /dev/null
+++ b/drivers/video/backlight/ti-lmu-backlight-core.c
@@ -0,0 +1,666 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2015 Texas Instruments
+ * Copyright 2018 Sebastian Reichel
+ *
+ * TI LMU Backlight driver, based on previous work from
+ * Milo Kim <milo.kim@ti.com>
+ */
+
+#include <linux/backlight.h>
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/leds.h>
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#include "ti-lmu-backlight-data.h"
+
+enum ti_lmu_bl_ctrl_mode {
+ BL_REGISTER_BASED,
+ BL_PWM_BASED,
+};
+
+enum ti_lmu_bl_type {
+ TI_LMU_BL, /* backlight userspace interface */
+ TI_LMU_LED, /* led userspace interface */
+};
+
+enum ti_lmu_bl_ramp_mode {
+ BL_RAMP_UP,
+ BL_RAMP_DOWN,
+};
+
+#define DEFAULT_PWM_NAME "lmu-backlight"
+#define NUM_DUAL_CHANNEL 2
+#define LMU_BACKLIGHT_DUAL_CHANNEL_USED (BIT(0) | BIT(1))
+#define LMU_BACKLIGHT_11BIT_LSB_MASK (BIT(0) | BIT(1) | BIT(2))
+#define LMU_BACKLIGHT_11BIT_MSB_SHIFT 3
+
+struct ti_lmu_bank {
+ struct device *dev;
+ int bank_id;
+ const struct ti_lmu_bl_cfg *cfg;
+ struct ti_lmu *lmu;
+ const char *label;
+ int leds;
+ int current_brightness;
+ u32 default_brightness;
+ u32 ramp_up_msec;
+ u32 ramp_down_msec;
+ u32 pwm_period;
+ enum ti_lmu_bl_ctrl_mode mode;
+ enum ti_lmu_bl_type type;
+
+ struct notifier_block nb;
+
+ struct backlight_device *backlight;
+ struct led_classdev *led;
+};
+
+static int ti_lmu_bl_enable(struct ti_lmu_bank *lmu_bank, bool enable)
+{
+ struct regmap *regmap = lmu_bank->lmu->regmap;
+ unsigned long enable_time = lmu_bank->cfg->reginfo->enable_usec;
+ u8 *reg = lmu_bank->cfg->reginfo->enable;
+ u8 mask = BIT(lmu_bank->bank_id);
+ u8 val = (enable == true) ? mask : 0;
+ int ret;
+
+ if (!reg)
+ return -EINVAL;
+
+ ret = regmap_update_bits(regmap, *reg, mask, val);
+ if (ret)
+ return ret;
+
+ if (enable_time > 0)
+ usleep_range(enable_time, enable_time + 100);
+
+ return 0;
+}
+
+static int ti_lmu_bl_update_brightness_register(struct ti_lmu_bank *lmu_bank,
+ int brightness)
+{
+ const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
+ const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
+ struct regmap *regmap = lmu_bank->lmu->regmap;
+ u8 reg, val;
+ int ret;
+
+ /*
+ * Brightness register update
+ *
+ * 11 bit dimming: update LSB bits and write MSB byte.
+ * MSB brightness should be shifted.
+ * 8 bit dimming: write MSB byte.
+ */
+ if (cfg->max_brightness == MAX_BRIGHTNESS_11BIT) {
+ reg = reginfo->brightness_lsb[lmu_bank->bank_id];
+ ret = regmap_update_bits(regmap, reg,
+ LMU_BACKLIGHT_11BIT_LSB_MASK,
+ brightness);
+ if (ret)
+ return ret;
+
+ val = brightness >> LMU_BACKLIGHT_11BIT_MSB_SHIFT;
+ } else {
+ val = brightness;
+ }
+
+ reg = reginfo->brightness_msb[lmu_bank->bank_id];
+ return regmap_write(regmap, reg, val);
+}
+
+static int ti_lmu_bl_pwm_ctrl(struct ti_lmu_bank *lmu_bank, int brightness)
+{
+ int max_brightness = lmu_bank->cfg->max_brightness;
+ struct pwm_state state = { };
+ int ret;
+
+ if (!lmu_bank->lmu->pwm) {
+ dev_err(lmu_bank->dev, "Missing PWM device!\n");
+ return -ENODEV;
+ }
+
+ pwm_init_state(lmu_bank->lmu->pwm, &state);
+ state.period = lmu_bank->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_bank->lmu->pwm, &state);
+ if (ret)
+ dev_err(lmu_bank->dev, "Failed to configure PWM: %d", ret);
+
+ return ret;
+}
+
+static int ti_lmu_bl_set_brightness(struct ti_lmu_bank *lmu_bank,
+ int brightness)
+{
+ const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
+ bool enable = brightness > 0;
+ int ret;
+
+ ret = ti_lmu_bl_enable(lmu_bank, enable);
+ if (ret)
+ return ret;
+
+ if (lmu_bank->mode == BL_PWM_BASED) {
+ ti_lmu_bl_pwm_ctrl(lmu_bank, 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;
+ }
+ }
+
+ lmu_bank->current_brightness = brightness;
+
+ return ti_lmu_bl_update_brightness_register(lmu_bank, brightness);
+}
+
+static int ti_lmu_bl_update_status(struct backlight_device *bl_dev)
+{
+ struct ti_lmu_bank *lmu_bank = bl_get_data(bl_dev);
+ int brightness = bl_dev->props.brightness;
+
+ if (bl_dev->props.state & BL_CORE_SUSPENDED)
+ brightness = 0;
+
+ return ti_lmu_bl_set_brightness(lmu_bank, brightness);
+}
+
+static const struct backlight_ops lmu_backlight_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .update_status = ti_lmu_bl_update_status,
+};
+
+static int ti_lmu_bl_set_led_blocking(struct led_classdev *ledc,
+ enum led_brightness value)
+{
+ struct ti_lmu_bank *lmu_bank = dev_get_drvdata(ledc->dev->parent);
+ int brightness = value;
+
+ return ti_lmu_bl_set_brightness(lmu_bank, brightness);
+}
+
+static int ti_lmu_bl_check_channel(struct ti_lmu_bank *lmu_bank)
+{
+ const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
+ const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
+
+ if (!reginfo->brightness_msb)
+ return -EINVAL;
+
+ if (cfg->max_brightness > MAX_BRIGHTNESS_8BIT) {
+ if (!reginfo->brightness_lsb)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ti_lmu_bl_create_channel(struct ti_lmu_bank *lmu_bank)
+{
+ struct regmap *regmap = lmu_bank->lmu->regmap;
+ const struct lmu_bl_reg_data *regdata = lmu_bank->cfg->reginfo->channel;
+ int num_channels = lmu_bank->cfg->num_channels;
+ unsigned long led_sources = lmu_bank->leds;
+ int i, ret;
+ u8 shift;
+
+ /*
+ * How to create backlight output channels:
+ * Check 'led_sources' bit and update registers.
+ *
+ * 1) Dual channel configuration
+ * The 1st register data is used for single channel.
+ * The 2nd register data is used for dual channel.
+ *
+ * 2) Multiple channel configuration
+ * Each register data is mapped to bank ID.
+ * Bit shift operation is defined in channel registers.
+ *
+ * Channel register data consists of address, mask, value.
+ */
+
+ if (num_channels == NUM_DUAL_CHANNEL) {
+ if (led_sources == LMU_BACKLIGHT_DUAL_CHANNEL_USED)
+ regdata++;
+
+ return regmap_update_bits(regmap, regdata->reg, regdata->mask,
+ regdata->val);
+ }
+
+ for (i = 0; regdata && i < num_channels; i++) {
+ /*
+ * Note that the result of regdata->val is shift bit.
+ * The bank_id should be shifted for the channel configuration.
+ */
+ if (test_bit(i, &led_sources)) {
+ shift = regdata->val;
+ ret = regmap_update_bits(regmap, regdata->reg,
+ regdata->mask,
+ lmu_bank->bank_id << shift);
+ if (ret)
+ return ret;
+ }
+
+ regdata++;
+ }
+
+ return 0;
+}
+
+static int ti_lmu_bl_update_ctrl_mode(struct ti_lmu_bank *lmu_bank)
+{
+ struct regmap *regmap = lmu_bank->lmu->regmap;
+ const struct lmu_bl_reg_data *regdata =
+ lmu_bank->cfg->reginfo->mode + lmu_bank->bank_id;
+ u8 val = regdata->val;
+
+ if (!regdata)
+ return 0;
+
+ /*
+ * Update PWM configuration register.
+ * If the mode is register based, then clear the bit.
+ */
+ if (lmu_bank->mode != BL_PWM_BASED)
+ val = 0;
+
+ return regmap_update_bits(regmap, regdata->reg, regdata->mask, val);
+}
+
+static int ti_lmu_bl_convert_ramp_to_index(struct ti_lmu_bank *lmu_bank,
+ enum ti_lmu_bl_ramp_mode mode)
+{
+ const int *ramp_table = lmu_bank->cfg->ramp_table;
+ const int size = lmu_bank->cfg->size_ramp;
+ unsigned int msec;
+ int i;
+
+ if (!ramp_table)
+ return -EINVAL;
+
+ switch (mode) {
+ case BL_RAMP_UP:
+ msec = lmu_bank->ramp_up_msec;
+ break;
+ case BL_RAMP_DOWN:
+ msec = lmu_bank->ramp_down_msec;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (msec <= ramp_table[0])
+ return 0;
+
+ if (msec > ramp_table[size - 1])
+ return size - 1;
+
+ for (i = 1; i < size; i++) {
+ if (msec == ramp_table[i])
+ return i;
+
+ /* Find an approximate index by looking up the table */
+ if (msec > ramp_table[i - 1] && msec < ramp_table[i]) {
+ if (msec - ramp_table[i - 1] < ramp_table[i] - msec)
+ return i - 1;
+ else
+ return i;
+ }
+ }
+
+ return -EINVAL;
+}
+
+
+static int ti_lmu_bl_set_ramp(struct ti_lmu_bank *lmu_bank)
+{
+ struct regmap *regmap = lmu_bank->lmu->regmap;
+ const struct ti_lmu_bl_reg *reginfo = lmu_bank->cfg->reginfo;
+ int offset = reginfo->ramp_reg_offset;
+ int i, ret, index;
+ struct lmu_bl_reg_data regdata;
+
+ for (i = BL_RAMP_UP; i <= BL_RAMP_DOWN; i++) {
+ index = ti_lmu_bl_convert_ramp_to_index(lmu_bank, i);
+ if (index > 0) {
+ if (!reginfo->ramp)
+ break;
+
+ regdata = reginfo->ramp[i];
+ if (lmu_bank->bank_id != 0)
+ regdata.val += offset;
+
+ /* regdata.val is shift bit */
+ ret = regmap_update_bits(regmap, regdata.reg,
+ regdata.mask,
+ index << regdata.val);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int ti_lmu_bl_configure(struct ti_lmu_bank *lmu_bank)
+{
+ int ret;
+
+ ret = ti_lmu_bl_check_channel(lmu_bank);
+ if (ret)
+ return ret;
+
+ ret = ti_lmu_bl_create_channel(lmu_bank);
+ if (ret)
+ return ret;
+
+ ret = ti_lmu_bl_update_ctrl_mode(lmu_bank);
+ if (ret)
+ return ret;
+
+ return ti_lmu_bl_set_ramp(lmu_bank);
+}
+
+static int ti_lmu_bl_register_backlight(struct ti_lmu_bank *lmu_bank)
+{
+ struct backlight_device *bl_dev;
+ struct backlight_properties props;
+
+ if (lmu_bank->type != TI_LMU_BL)
+ return -EINVAL;
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.brightness = lmu_bank->default_brightness;
+ props.max_brightness = lmu_bank->cfg->max_brightness;
+
+ bl_dev = devm_backlight_device_register(lmu_bank->dev, lmu_bank->label,
+ lmu_bank->dev, lmu_bank,
+ &lmu_backlight_ops, &props);
+ if (IS_ERR(bl_dev))
+ return PTR_ERR(bl_dev);
+
+ lmu_bank->backlight = bl_dev;
+
+ return 0;
+}
+
+static int ti_lmu_bl_register_led(struct ti_lmu_bank *lmu_bank)
+{
+ int err;
+
+ if (lmu_bank->type != TI_LMU_LED)
+ return -EINVAL;
+
+ lmu_bank->led = devm_kzalloc(lmu_bank->dev, sizeof(*lmu_bank->led),
+ GFP_KERNEL);
+ if (!lmu_bank->led)
+ return -ENOMEM;
+
+ lmu_bank->led->name = lmu_bank->label;
+ lmu_bank->led->max_brightness = lmu_bank->cfg->max_brightness;
+ lmu_bank->led->brightness_set_blocking =
+ ti_lmu_bl_set_led_blocking;
+
+ err = devm_led_classdev_register(lmu_bank->dev, lmu_bank->led);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
+{
+ switch (lmu_bank->type) {
+ case TI_LMU_BL:
+ return ti_lmu_bl_register_backlight(lmu_bank);
+ case TI_LMU_LED:
+ return ti_lmu_bl_register_led(lmu_bank);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int setup_of_node(struct platform_device *pdev)
+{
+ struct device_node *parent_node = pdev->dev.parent->of_node;
+ char *name;
+
+ if (!parent_node)
+ return 0;
+
+ name = kasprintf(GFP_KERNEL, "bank%d", pdev->id);
+ if (!name)
+ return -ENOMEM;
+
+ pdev->dev.of_node = of_get_child_by_name(parent_node, name);
+ kfree(name);
+
+ if (!pdev->dev.of_node)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int ti_lmu_parse_led_sources(struct device *dev)
+{
+ unsigned long mask = 0;
+ int ret;
+ int size, i;
+ u32 *leds;
+
+ size = device_property_read_u32_array(dev, "ti,led-sources", NULL, 0);
+ if (size <= 0) {
+ dev_err(dev, "Missing or malformed property led-sources: %d\n",
+ size);
+ return size < 0 ? size : -EINVAL;
+ }
+
+ leds = kmalloc_array(size, sizeof(u32), GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
+
+ ret = device_property_read_u32_array(dev, "ti,led-sources", leds, size);
+ if (ret) {
+ dev_err(dev, "Failed to read led-sources property: %d\n", ret);
+ goto out;
+ }
+
+ for (i = 0; i < size; i++)
+ set_bit(leds[i], &mask);
+
+ ret = mask;
+
+out:
+ kfree(leds);
+ return ret;
+}
+
+static int ti_lmu_bl_init(struct ti_lmu_bank *lmu_bank)
+{
+ struct regmap *regmap = lmu_bank->lmu->regmap;
+ const struct lmu_bl_reg_data *regdata =
+ lmu_bank->cfg->reginfo->init;
+ int num_init = lmu_bank->cfg->reginfo->num_init;
+ int i, ret;
+
+ if (lmu_bank->lmu->backlight_initialized)
+ return 0;
+ lmu_bank->lmu->backlight_initialized = true;
+
+ for (i = 0; regdata && i < num_init; i++) {
+ ret = regmap_update_bits(regmap, regdata->reg, regdata->mask,
+ regdata->val);
+ if (ret)
+ return ret;
+
+ regdata++;
+ }
+
+ return 0;
+}
+
+static int ti_lmu_bl_reload(struct ti_lmu_bank *lmu_bank)
+{
+ int err;
+
+ ti_lmu_bl_init(lmu_bank);
+
+ err = ti_lmu_bl_configure(lmu_bank);
+ if (err)
+ return err;
+
+ return ti_lmu_bl_set_brightness(lmu_bank, lmu_bank->current_brightness);
+}
+
+static int ti_lmu_bl_monitor_notifier(struct notifier_block *nb,
+ unsigned long action, void *unused)
+{
+ struct ti_lmu_bank *lmu_bank = container_of(nb, struct ti_lmu_bank, nb);
+
+ if (action == LMU_EVENT_MONITOR_DONE) {
+ if (ti_lmu_bl_reload(lmu_bank))
+ return NOTIFY_STOP;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int ti_lmu_bl_probe(struct platform_device *pdev)
+{
+ struct ti_lmu_bank *lmu_bank;
+ int err;
+
+ err = setup_of_node(pdev);
+ if (err)
+ return err;
+
+ lmu_bank = devm_kzalloc(&pdev->dev, sizeof(*lmu_bank), GFP_KERNEL);
+ if (!lmu_bank)
+ return -ENOMEM;
+ lmu_bank->dev = &pdev->dev;
+ dev_set_drvdata(&pdev->dev, lmu_bank);
+
+ err = device_property_read_string(&pdev->dev, "label",
+ &lmu_bank->label);
+ if (err)
+ return err;
+
+ lmu_bank->type = TI_LMU_BL;
+ if (!strcmp(lmu_bank->label, "keyboard")) {
+ lmu_bank->type = TI_LMU_LED;
+ lmu_bank->label = "kbd_backlight";
+ }
+
+ lmu_bank->leds = ti_lmu_parse_led_sources(&pdev->dev);
+ if (lmu_bank->leds < 0)
+ return lmu_bank->leds;
+ else if (lmu_bank->leds == 0)
+ return -EINVAL;
+
+ device_property_read_u32(&pdev->dev, "default-brightness-level",
+ &lmu_bank->default_brightness);
+ device_property_read_u32(&pdev->dev, "ti,ramp-up-ms",
+ &lmu_bank->ramp_up_msec);
+ device_property_read_u32(&pdev->dev, "ti,ramp-down-ms",
+ &lmu_bank->ramp_down_msec);
+ device_property_read_u32(&pdev->dev, "pwm-period",
+ &lmu_bank->pwm_period);
+
+ if (lmu_bank->pwm_period > 0)
+ lmu_bank->mode = BL_PWM_BASED;
+ else
+ lmu_bank->mode = BL_REGISTER_BASED;
+
+ lmu_bank->lmu = dev_get_drvdata(pdev->dev.parent);
+ lmu_bank->cfg = &lmu_bl_cfg[lmu_bank->lmu->id];
+ lmu_bank->bank_id = pdev->id;
+
+ ti_lmu_bl_init(lmu_bank);
+
+ err = ti_lmu_bl_configure(lmu_bank);
+ if (err)
+ return err;
+
+ err = ti_lmu_bl_add_device(lmu_bank);
+ if (err)
+ return err;
+
+ err = ti_lmu_bl_set_brightness(lmu_bank,
+ lmu_bank->default_brightness);
+ if (err)
+ return err;
+
+ /*
+ * Notifier callback is required because backlight device needs
+ * reconfiguration after fault detection procedure is done by
+ * ti-lmu-fault-monitor driver.
+ */
+ if (lmu_bank->cfg->fault_monitor_used) {
+ lmu_bank->nb.notifier_call = ti_lmu_bl_monitor_notifier;
+ err = blocking_notifier_chain_register(&lmu_bank->lmu->notifier,
+ &lmu_bank->nb);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int ti_lmu_bl_remove(struct platform_device *pdev)
+{
+ struct ti_lmu_bank *lmu_bank = platform_get_drvdata(pdev);
+
+ if (lmu_bank->cfg->fault_monitor_used)
+ blocking_notifier_chain_unregister(&lmu_bank->lmu->notifier,
+ &lmu_bank->nb);
+
+ ti_lmu_bl_set_brightness(lmu_bank, 0);
+
+ return 0;
+}
+
+static struct platform_driver ti_lmu_bl_driver = {
+ .probe = ti_lmu_bl_probe,
+ .remove = ti_lmu_bl_remove,
+ .driver = {
+ .name = "ti-lmu-led-backlight",
+ },
+};
+module_platform_driver(ti_lmu_bl_driver)
+
+MODULE_DESCRIPTION("TI LMU Backlight LED Driver");
+MODULE_AUTHOR("Sebastian Reichel");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:ti-lmu-led-backlight");
diff --git a/drivers/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 <milo.kim@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "ti-lmu-backlight-data.h"
+
+/* LM3532 */
+static const struct lmu_bl_reg_data lm3532_init_data[] = {
+ { LM3532_REG_ZONE_CFG_A, LM3532_ZONE_MASK, LM3532_ZONE_0 },
+ { LM3532_REG_ZONE_CFG_B, LM3532_ZONE_MASK, LM3532_ZONE_1 },
+ { LM3532_REG_ZONE_CFG_C, LM3532_ZONE_MASK, LM3532_ZONE_2 },
+};
+
+static const struct lmu_bl_reg_data lm3532_channel_data[] = {
+ { LM3532_REG_OUTPUT_CFG, LM3532_ILED1_CFG_MASK,
+ LM3532_ILED1_CFG_SHIFT },
+ { LM3532_REG_OUTPUT_CFG, LM3532_ILED2_CFG_MASK,
+ LM3532_ILED2_CFG_SHIFT },
+ { LM3532_REG_OUTPUT_CFG, LM3532_ILED3_CFG_MASK,
+ LM3532_ILED3_CFG_SHIFT },
+};
+
+static const struct lmu_bl_reg_data lm3532_mode_data[] = {
+ { LM3532_REG_PWM_A_CFG, LM3532_PWM_A_MASK, LM3532_PWM_ZONE_0 },
+ { LM3532_REG_PWM_B_CFG, LM3532_PWM_B_MASK, LM3532_PWM_ZONE_1 },
+ { LM3532_REG_PWM_C_CFG, LM3532_PWM_C_MASK, LM3532_PWM_ZONE_2 },
+};
+
+static const struct lmu_bl_reg_data lm3532_ramp_data[] = {
+ { LM3532_REG_RAMPUP, LM3532_RAMPUP_MASK, LM3532_RAMPUP_SHIFT },
+ { LM3532_REG_RAMPDN, LM3532_RAMPDN_MASK, LM3532_RAMPDN_SHIFT },
+};
+
+static u8 lm3532_enable_reg = LM3532_REG_ENABLE;
+
+static u8 lm3532_brightness_regs[] = {
+ LM3532_REG_BRT_A,
+ LM3532_REG_BRT_B,
+ LM3532_REG_BRT_C,
+};
+
+static const struct ti_lmu_bl_reg lm3532_reg_info = {
+ .init = lm3532_init_data,
+ .num_init = ARRAY_SIZE(lm3532_init_data),
+ .channel = lm3532_channel_data,
+ .mode = lm3532_mode_data,
+ .ramp = lm3532_ramp_data,
+ .enable = &lm3532_enable_reg,
+ .brightness_msb = lm3532_brightness_regs,
+};
+
+/* LM3631 */
+static const struct lmu_bl_reg_data lm3631_init_data[] = {
+ { LM3631_REG_BRT_MODE, LM3631_MODE_MASK, LM3631_DEFAULT_MODE },
+ { LM3631_REG_BL_CFG, LM3631_MAP_MASK, LM3631_EXPONENTIAL_MAP },
+};
+
+static const struct lmu_bl_reg_data lm3631_channel_data[] = {
+ { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_SINGLE_CHANNEL },
+ { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_DUAL_CHANNEL },
+};
+
+static const struct lmu_bl_reg_data lm3631_ramp_data[] = {
+ { LM3631_REG_SLOPE, LM3631_SLOPE_MASK, LM3631_SLOPE_SHIFT },
+};
+
+static u8 lm3631_enable_reg = LM3631_REG_DEVCTRL;
+static u8 lm3631_brightness_msb_reg = LM3631_REG_BRT_MSB;
+static u8 lm3631_brightness_lsb_reg = LM3631_REG_BRT_LSB;
+
+static const struct ti_lmu_bl_reg lm3631_reg_info = {
+ .init = lm3631_init_data,
+ .num_init = ARRAY_SIZE(lm3631_init_data),
+ .channel = lm3631_channel_data,
+ .ramp = lm3631_ramp_data,
+ .enable = &lm3631_enable_reg,
+ .brightness_msb = &lm3631_brightness_msb_reg,
+ .brightness_lsb = &lm3631_brightness_lsb_reg,
+};
+
+/* LM3632 */
+static const struct lmu_bl_reg_data lm3632_init_data[] = {
+ { LM3632_REG_CONFIG1, LM3632_OVP_MASK, LM3632_OVP_25V },
+ { LM3632_REG_CONFIG2, LM3632_SWFREQ_MASK, LM3632_SWFREQ_1MHZ },
+};
+
+static const struct lmu_bl_reg_data lm3632_channel_data[] = {
+ { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_SINGLE_CHANNEL },
+ { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_DUAL_CHANNEL },
+};
+
+static const struct lmu_bl_reg_data lm3632_mode_data[] = {
+ { LM3632_REG_IO_CTRL, LM3632_PWM_MASK, LM3632_PWM_MODE },
+};
+
+static u8 lm3632_enable_reg = LM3632_REG_ENABLE;
+static u8 lm3632_brightness_msb_reg = LM3632_REG_BRT_MSB;
+static u8 lm3632_brightness_lsb_reg = LM3632_REG_BRT_LSB;
+
+static const struct ti_lmu_bl_reg lm3632_reg_info = {
+ .init = lm3632_init_data,
+ .num_init = ARRAY_SIZE(lm3632_init_data),
+ .channel = lm3632_channel_data,
+ .mode = lm3632_mode_data,
+ .enable = &lm3632_enable_reg,
+ .brightness_msb = &lm3632_brightness_msb_reg,
+ .brightness_lsb = &lm3632_brightness_lsb_reg,
+};
+
+/* LM3633 */
+static const struct lmu_bl_reg_data lm3633_init_data[] = {
+ { LM3633_REG_BOOST_CFG, LM3633_OVP_MASK, LM3633_OVP_40V },
+ { LM3633_REG_BL_RAMP_CONF, LM3633_BL_RAMP_MASK, LM3633_BL_RAMP_EACH },
+};
+
+static const struct lmu_bl_reg_data lm3633_channel_data[] = {
+ { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED1_CFG_MASK,
+ LM3633_HVLED1_CFG_SHIFT },
+ { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED2_CFG_MASK,
+ LM3633_HVLED2_CFG_SHIFT },
+ { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED3_CFG_MASK,
+ LM3633_HVLED3_CFG_SHIFT },
+};
+
+static const struct lmu_bl_reg_data lm3633_mode_data[] = {
+ { LM3633_REG_PWM_CFG, LM3633_PWM_A_MASK, LM3633_PWM_A_MASK },
+ { LM3633_REG_PWM_CFG, LM3633_PWM_B_MASK, LM3633_PWM_B_MASK },
+};
+
+static const struct lmu_bl_reg_data lm3633_ramp_data[] = {
+ { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPUP_MASK, LM3633_BL_RAMPUP_SHIFT },
+ { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPDN_MASK, LM3633_BL_RAMPDN_SHIFT },
+};
+
+static u8 lm3633_enable_reg = LM3633_REG_ENABLE;
+
+static u8 lm3633_brightness_msb_regs[] = {
+ LM3633_REG_BRT_HVLED_A_MSB,
+ LM3633_REG_BRT_HVLED_B_MSB,
+};
+
+static u8 lm3633_brightness_lsb_regs[] = {
+ LM3633_REG_BRT_HVLED_A_LSB,
+ LM3633_REG_BRT_HVLED_B_LSB,
+};
+
+static const struct ti_lmu_bl_reg lm3633_reg_info = {
+ .init = lm3633_init_data,
+ .num_init = ARRAY_SIZE(lm3633_init_data),
+ .channel = lm3633_channel_data,
+ .mode = lm3633_mode_data,
+ .ramp = lm3633_ramp_data,
+ .ramp_reg_offset = 1, /* For LM3633_REG_BL1_RAMPUP/DN */
+ .enable = &lm3633_enable_reg,
+ .brightness_msb = lm3633_brightness_msb_regs,
+ .brightness_lsb = lm3633_brightness_lsb_regs,
+};
+
+/* LM3695 */
+static const struct lmu_bl_reg_data lm3695_init_data[] = {
+ { LM3695_REG_GP, LM3695_BRT_RW_MASK, LM3695_BRT_RW_MASK },
+};
+
+static const struct lmu_bl_reg_data lm3695_channel_data[] = {
+ { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_SINGLE_CHANNEL },
+ { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_DUAL_CHANNEL },
+};
+
+static u8 lm3695_enable_reg = LM3695_REG_GP;
+static u8 lm3695_brightness_msb_reg = LM3695_REG_BRT_MSB;
+static u8 lm3695_brightness_lsb_reg = LM3695_REG_BRT_LSB;
+
+static const struct ti_lmu_bl_reg lm3695_reg_info = {
+ .init = lm3695_init_data,
+ .num_init = ARRAY_SIZE(lm3695_init_data),
+ .channel = lm3695_channel_data,
+ .enable = &lm3695_enable_reg,
+ .enable_usec = 600,
+ .brightness_msb = &lm3695_brightness_msb_reg,
+ .brightness_lsb = &lm3695_brightness_lsb_reg,
+};
+
+/* LM3697 */
+static const struct lmu_bl_reg_data lm3697_init_data[] = {
+ { LM3697_REG_RAMP_CONF, LM3697_RAMP_MASK, LM3697_RAMP_EACH },
+};
+
+static const struct lmu_bl_reg_data lm3697_channel_data[] = {
+ { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED1_CFG_MASK,
+ LM3697_HVLED1_CFG_SHIFT },
+ { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED2_CFG_MASK,
+ LM3697_HVLED2_CFG_SHIFT },
+ { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED3_CFG_MASK,
+ LM3697_HVLED3_CFG_SHIFT },
+};
+
+static const struct lmu_bl_reg_data lm3697_mode_data[] = {
+ { LM3697_REG_PWM_CFG, LM3697_PWM_A_MASK, LM3697_PWM_A_MASK },
+ { LM3697_REG_PWM_CFG, LM3697_PWM_B_MASK, LM3697_PWM_B_MASK },
+};
+
+static const struct lmu_bl_reg_data lm3697_ramp_data[] = {
+ { LM3697_REG_BL0_RAMP, LM3697_RAMPUP_MASK, LM3697_RAMPUP_SHIFT },
+ { LM3697_REG_BL0_RAMP, LM3697_RAMPDN_MASK, LM3697_RAMPDN_SHIFT },
+};
+
+static u8 lm3697_enable_reg = LM3697_REG_ENABLE;
+
+static u8 lm3697_brightness_msb_regs[] = {
+ LM3697_REG_BRT_A_MSB,
+ LM3697_REG_BRT_B_MSB,
+};
+
+static u8 lm3697_brightness_lsb_regs[] = {
+ LM3697_REG_BRT_A_LSB,
+ LM3697_REG_BRT_B_LSB,
+};
+
+static const struct ti_lmu_bl_reg lm3697_reg_info = {
+ .init = lm3697_init_data,
+ .num_init = ARRAY_SIZE(lm3697_init_data),
+ .channel = lm3697_channel_data,
+ .mode = lm3697_mode_data,
+ .ramp = lm3697_ramp_data,
+ .ramp_reg_offset = 1, /* For LM3697_REG_BL1_RAMPUP/DN */
+ .enable = &lm3697_enable_reg,
+ .brightness_msb = lm3697_brightness_msb_regs,
+ .brightness_lsb = lm3697_brightness_lsb_regs,
+};
+
+static int lm3532_ramp_table[] = { 0, 1, 2, 4, 8, 16, 32, 65 };
+
+static int lm3631_ramp_table[] = {
+ 0, 1, 2, 5, 10, 20, 50, 100,
+ 250, 500, 750, 1000, 1500, 2000, 3000, 4000,
+};
+
+static int common_ramp_table[] = {
+ 2, 250, 500, 1000, 2000, 4000, 8000, 16000,
+};
+
+#define LM3532_MAX_CHANNELS 3
+#define LM3631_MAX_CHANNELS 2
+#define LM3632_MAX_CHANNELS 2
+#define LM3633_MAX_CHANNELS 3
+#define LM3695_MAX_CHANNELS 2
+#define LM3697_MAX_CHANNELS 3
+
+const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID] = {
+ {
+ .reginfo = &lm3532_reg_info,
+ .num_channels = LM3532_MAX_CHANNELS,
+ .max_brightness = MAX_BRIGHTNESS_8BIT,
+ .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
+ .ramp_table = lm3532_ramp_table,
+ .size_ramp = ARRAY_SIZE(lm3532_ramp_table),
+ },
+ {
+ .reginfo = &lm3631_reg_info,
+ .num_channels = LM3631_MAX_CHANNELS,
+ .max_brightness = MAX_BRIGHTNESS_11BIT,
+ .pwm_action = UPDATE_PWM_ONLY,
+ .ramp_table = lm3631_ramp_table,
+ .size_ramp = ARRAY_SIZE(lm3631_ramp_table),
+ },
+ {
+ .reginfo = &lm3632_reg_info,
+ .num_channels = LM3632_MAX_CHANNELS,
+ .max_brightness = MAX_BRIGHTNESS_11BIT,
+ .pwm_action = UPDATE_PWM_ONLY,
+ },
+ {
+ .reginfo = &lm3633_reg_info,
+ .num_channels = LM3633_MAX_CHANNELS,
+ .max_brightness = MAX_BRIGHTNESS_11BIT,
+ .pwm_action = UPDATE_MAX_BRT,
+ .ramp_table = common_ramp_table,
+ .size_ramp = ARRAY_SIZE(common_ramp_table),
+ .fault_monitor_used = true,
+ },
+ {
+ .reginfo = &lm3695_reg_info,
+ .num_channels = LM3695_MAX_CHANNELS,
+ .max_brightness = MAX_BRIGHTNESS_11BIT,
+ .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
+ },
+ {
+ .reginfo = &lm3697_reg_info,
+ .num_channels = LM3697_MAX_CHANNELS,
+ .max_brightness = MAX_BRIGHTNESS_11BIT,
+ .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
+ .ramp_table = common_ramp_table,
+ .size_ramp = ARRAY_SIZE(common_ramp_table),
+ .fault_monitor_used = true,
+ },
+};
diff --git a/drivers/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 <milo.kim@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __TI_LMU_BACKLIGHT_H__
+#define __TI_LMU_BACKLIGHT_H__
+
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+
+#define MAX_BRIGHTNESS_8BIT 255
+#define MAX_BRIGHTNESS_11BIT 2047
+
+enum ti_lmu_bl_pwm_action {
+ /* Update PWM duty, no brightness register update is required */
+ UPDATE_PWM_ONLY,
+ /* Update not only duty but also brightness register */
+ UPDATE_PWM_AND_BRT_REGISTER,
+ /* Update max value in brightness registers */
+ UPDATE_MAX_BRT,
+};
+
+struct lmu_bl_reg_data {
+ u8 reg;
+ u8 mask;
+ u8 val;
+};
+
+/**
+ * struct ti_lmu_bl_reg
+ *
+ * @init: Device initialization registers
+ * @num_init: Numbers of initialization registers
+ * @channel: Backlight channel configuration registers
+ * @mode: Brightness control mode registers
+ * @ramp: Ramp registers for lighting effect
+ * @ramp_reg_offset: Ramp register offset.
+ * Only used for multiple ramp registers.
+ * @enable: Enable control register address
+ * @enable_usec: Delay time for updating enable register.
+ * Unit is microsecond.
+ * @brightness_msb: Brightness MSB(Upper 8 bits) registers.
+ * Concatenated with LSB in 11 bit dimming mode.
+ * In 8 bit dimming, only MSB is used.
+ * @brightness_lsb: Brightness LSB(Lower 3 bits) registers.
+ * Only valid in 11 bit dimming mode.
+ */
+struct ti_lmu_bl_reg {
+ const struct lmu_bl_reg_data *init;
+ int num_init;
+ const struct lmu_bl_reg_data *channel;
+ const struct lmu_bl_reg_data *mode;
+ const struct lmu_bl_reg_data *ramp;
+ int ramp_reg_offset;
+ u8 *enable;
+ unsigned long enable_usec;
+ u8 *brightness_msb;
+ u8 *brightness_lsb;
+};
+
+/**
+ * struct ti_lmu_bl_cfg
+ *
+ * @reginfo: Device register configuration
+ * @num_channels: Number of backlight channels
+ * @max_brightness: Max brightness value of backlight device
+ * @pwm_action: How to control brightness registers in PWM mode
+ * @ramp_table: [Optional] Ramp time table for lighting effect.
+ * It's used for searching approximate register index.
+ * @size_ramp: [Optional] Size of ramp table
+ * @fault_monitor_used: [Optional] Set true if the device needs to handle
+ * LMU fault monitor event.
+ *
+ * This structure is used for device specific data configuration.
+ */
+struct ti_lmu_bl_cfg {
+ const struct ti_lmu_bl_reg *reginfo;
+ int num_channels;
+ int max_brightness;
+ enum ti_lmu_bl_pwm_action pwm_action;
+ int *ramp_table;
+ int size_ramp;
+ bool fault_monitor_used;
+};
+
+extern const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID];
+#endif
--
2.16.2
^ permalink raw reply related [flat|nested] 62+ messages in thread* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
2018-03-30 17:24 ` Sebastian Reichel
@ 2018-04-03 10:49 ` Pavel Machek
-1 siblings, 0 replies; 62+ messages in thread
From: Pavel Machek @ 2018-04-03 10:49 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Sebastian Reichel, Milo Kim, Lee Jones, Daniel Thompson,
Rob Herring, Tony Lindgren, Jingoo Han, Mark Rutland,
linux-kernel, linux-fbdev, devicetree, linux-omap
[-- Attachment #1: Type: text/plain, Size: 909 bytes --]
Hi!
> +enum ti_lmu_bl_type {
> + TI_LMU_BL, /* backlight userspace interface */
> + TI_LMU_LED, /* led userspace interface */
> +};
...
> +static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
> +{
> + switch (lmu_bank->type) {
> + case TI_LMU_BL:
> + return ti_lmu_bl_register_backlight(lmu_bank);
> + case TI_LMU_LED:
> + return ti_lmu_bl_register_led(lmu_bank);
> + default:
> + return -EINVAL;
> + }
> +}
Ok, this is somehow unusual/crazy. Single driver with two interfaces.
Do we need the LED interface for something?
If yes, I believe reasonable solution would be to always provide LED
interface, and then have "backlight-trigger" which that would provide
backlight interface for arbitrary LED.
Thanks,
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]
^ permalink raw reply [flat|nested] 62+ messages in thread* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
@ 2018-04-03 10:49 ` Pavel Machek
0 siblings, 0 replies; 62+ messages in thread
From: Pavel Machek @ 2018-04-03 10:49 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Sebastian Reichel, Milo Kim, Lee Jones, Daniel Thompson,
Rob Herring, Tony Lindgren, Jingoo Han, Mark Rutland,
linux-kernel, linux-fbdev, devicetree, linux-omap
[-- Attachment #1: Type: text/plain, Size: 909 bytes --]
Hi!
> +enum ti_lmu_bl_type {
> + TI_LMU_BL, /* backlight userspace interface */
> + TI_LMU_LED, /* led userspace interface */
> +};
...
> +static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
> +{
> + switch (lmu_bank->type) {
> + case TI_LMU_BL:
> + return ti_lmu_bl_register_backlight(lmu_bank);
> + case TI_LMU_LED:
> + return ti_lmu_bl_register_led(lmu_bank);
> + default:
> + return -EINVAL;
> + }
> +}
Ok, this is somehow unusual/crazy. Single driver with two interfaces.
Do we need the LED interface for something?
If yes, I believe reasonable solution would be to always provide LED
interface, and then have "backlight-trigger" which that would provide
backlight interface for arbitrary LED.
Thanks,
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]
^ permalink raw reply [flat|nested] 62+ messages in thread* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
2018-04-03 10:49 ` Pavel Machek
@ 2018-04-09 15:54 ` Sebastian Reichel
-1 siblings, 0 replies; 62+ messages in thread
From: Sebastian Reichel @ 2018-04-09 15:54 UTC (permalink / raw)
To: Pavel Machek
Cc: Milo Kim, Lee Jones, Daniel Thompson, Rob Herring, Tony Lindgren,
Jingoo Han, Mark Rutland, linux-kernel, linux-fbdev, devicetree,
linux-omap
[-- Attachment #1: Type: text/plain, Size: 1340 bytes --]
Hi,
On Tue, Apr 03, 2018 at 12:49:08PM +0200, Pavel Machek wrote:
> > +enum ti_lmu_bl_type {
> > + TI_LMU_BL, /* backlight userspace interface */
> > + TI_LMU_LED, /* led userspace interface */
> > +};
> ...
> > +static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
> > +{
> > + switch (lmu_bank->type) {
> > + case TI_LMU_BL:
> > + return ti_lmu_bl_register_backlight(lmu_bank);
> > + case TI_LMU_LED:
> > + return ti_lmu_bl_register_led(lmu_bank);
> > + default:
> > + return -EINVAL;
> > + }
> > +}
>
> Ok, this is somehow unusual/crazy. Single driver with two
> interfaces.
>
> Do we need the LED interface for something?
>
> If yes, I believe reasonable solution would be to always provide LED
> interface, and then have "backlight-trigger" which that would provide
> backlight interface for arbitrary LED.
Userspace expects keyboard backlight to be exposed via the LED
subsystem and display backlight via the backlight subsystem.
I considered always exposing the banks via the LED subsystem and
using a generic backlight driver. That brings its own problems,
since there is a dependency between the display and the backlight.
This is described in DT using a phandle. Getting the right backlight
device from the phandle will become very tricky with this approach.
-- Sebastian
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 62+ messages in thread* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
@ 2018-04-09 15:54 ` Sebastian Reichel
0 siblings, 0 replies; 62+ messages in thread
From: Sebastian Reichel @ 2018-04-09 15:54 UTC (permalink / raw)
To: Pavel Machek
Cc: Milo Kim, Lee Jones, Daniel Thompson, Rob Herring, Tony Lindgren,
Jingoo Han, Mark Rutland, linux-kernel, linux-fbdev, devicetree,
linux-omap
[-- Attachment #1: Type: text/plain, Size: 1340 bytes --]
Hi,
On Tue, Apr 03, 2018 at 12:49:08PM +0200, Pavel Machek wrote:
> > +enum ti_lmu_bl_type {
> > + TI_LMU_BL, /* backlight userspace interface */
> > + TI_LMU_LED, /* led userspace interface */
> > +};
> ...
> > +static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
> > +{
> > + switch (lmu_bank->type) {
> > + case TI_LMU_BL:
> > + return ti_lmu_bl_register_backlight(lmu_bank);
> > + case TI_LMU_LED:
> > + return ti_lmu_bl_register_led(lmu_bank);
> > + default:
> > + return -EINVAL;
> > + }
> > +}
>
> Ok, this is somehow unusual/crazy. Single driver with two
> interfaces.
>
> Do we need the LED interface for something?
>
> If yes, I believe reasonable solution would be to always provide LED
> interface, and then have "backlight-trigger" which that would provide
> backlight interface for arbitrary LED.
Userspace expects keyboard backlight to be exposed via the LED
subsystem and display backlight via the backlight subsystem.
I considered always exposing the banks via the LED subsystem and
using a generic backlight driver. That brings its own problems,
since there is a dependency between the display and the backlight.
This is described in DT using a phandle. Getting the right backlight
device from the phandle will become very tricky with this approach.
-- Sebastian
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 62+ messages in thread* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
2018-04-09 15:54 ` Sebastian Reichel
@ 2018-04-10 6:38 ` Pavel Machek
-1 siblings, 0 replies; 62+ messages in thread
From: Pavel Machek @ 2018-04-10 6:38 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Milo Kim, Lee Jones, Daniel Thompson, Rob Herring, Tony Lindgren,
Jingoo Han, Mark Rutland, linux-kernel, linux-fbdev, devicetree,
linux-omap
[-- Attachment #1: Type: text/plain, Size: 1684 bytes --]
Hi!
> > > +static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
> > > +{
> > > + switch (lmu_bank->type) {
> > > + case TI_LMU_BL:
> > > + return ti_lmu_bl_register_backlight(lmu_bank);
> > > + case TI_LMU_LED:
> > > + return ti_lmu_bl_register_led(lmu_bank);
> > > + default:
> > > + return -EINVAL;
> > > + }
> > > +}
> >
> > Ok, this is somehow unusual/crazy. Single driver with two
> > interfaces.
> >
> > Do we need the LED interface for something?
> >
> > If yes, I believe reasonable solution would be to always provide LED
> > interface, and then have "backlight-trigger" which that would provide
> > backlight interface for arbitrary LED.
>
> Userspace expects keyboard backlight to be exposed via the LED
> subsystem and display backlight via the backlight subsystem.
Ok.
> I considered always exposing the banks via the LED subsystem and
> using a generic backlight driver. That brings its own problems,
> since there is a dependency between the display and the backlight.
> This is described in DT using a phandle. Getting the right backlight
> device from the phandle will become very tricky with this approach.
I believe we have to do this.
Virtually any LED can be used as a backlight, and we don't really want
to add two personalities to all the LED drivers.
And it should not be too bad: LED will just have default trigger,
which will say this LED corresponds to this display device. I believe
someone wanted to do that for USB/ethernet activity.
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]
^ permalink raw reply [flat|nested] 62+ messages in thread* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
@ 2018-04-10 6:38 ` Pavel Machek
0 siblings, 0 replies; 62+ messages in thread
From: Pavel Machek @ 2018-04-10 6:38 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Milo Kim, Lee Jones, Daniel Thompson, Rob Herring, Tony Lindgren,
Jingoo Han, Mark Rutland, linux-kernel, linux-fbdev, devicetree,
linux-omap
[-- Attachment #1: Type: text/plain, Size: 1684 bytes --]
Hi!
> > > +static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
> > > +{
> > > + switch (lmu_bank->type) {
> > > + case TI_LMU_BL:
> > > + return ti_lmu_bl_register_backlight(lmu_bank);
> > > + case TI_LMU_LED:
> > > + return ti_lmu_bl_register_led(lmu_bank);
> > > + default:
> > > + return -EINVAL;
> > > + }
> > > +}
> >
> > Ok, this is somehow unusual/crazy. Single driver with two
> > interfaces.
> >
> > Do we need the LED interface for something?
> >
> > If yes, I believe reasonable solution would be to always provide LED
> > interface, and then have "backlight-trigger" which that would provide
> > backlight interface for arbitrary LED.
>
> Userspace expects keyboard backlight to be exposed via the LED
> subsystem and display backlight via the backlight subsystem.
Ok.
> I considered always exposing the banks via the LED subsystem and
> using a generic backlight driver. That brings its own problems,
> since there is a dependency between the display and the backlight.
> This is described in DT using a phandle. Getting the right backlight
> device from the phandle will become very tricky with this approach.
I believe we have to do this.
Virtually any LED can be used as a backlight, and we don't really want
to add two personalities to all the LED drivers.
And it should not be too bad: LED will just have default trigger,
which will say this LED corresponds to this display device. I believe
someone wanted to do that for USB/ethernet activity.
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
2018-03-30 17:24 ` Sebastian Reichel
@ 2018-04-04 14:57 ` Daniel Thompson
-1 siblings, 0 replies; 62+ messages in thread
From: Daniel Thompson @ 2018-04-04 14:57 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Sebastian Reichel, Milo Kim, Lee Jones, Rob Herring,
Tony Lindgren, Jingoo Han, Mark Rutland, linux-kernel,
linux-fbdev, devicetree, linux-omap
On Fri, Mar 30, 2018 at 07:24:12PM +0200, Sebastian Reichel wrote:
> This adds backlight support for the following TI LMU
> chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
>
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> [add LED subsystem support for keyboard backlight and rework DT
Milo's mail has be bouncing for a very long time now. Did they really
sign off this code or is this intended to be an authorship credit?
> binding according to Rob Herrings feedback]
> Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> ---
> drivers/video/backlight/Kconfig | 7 +
> drivers/video/backlight/Makefile | 3 +
> drivers/video/backlight/ti-lmu-backlight-core.c | 666 ++++++++++++++++++++++++
> drivers/video/backlight/ti-lmu-backlight-data.c | 304 +++++++++++
> drivers/video/backlight/ti-lmu-backlight-data.h | 95 ++++
> 5 files changed, 1075 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 5d2d0d7e8100..27e6c5a0add8 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 19da71d518bf..a1132d3dfd4c 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -53,6 +53,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..a6099581edd7
> --- /dev/null
> +++ b/drivers/video/backlight/ti-lmu-backlight-core.c
> @@ -0,0 +1,666 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2015 Texas Instruments
> + * Copyright 2018 Sebastian Reichel
> + *
> + * TI LMU Backlight driver, based on previous work from
> + * Milo Kim <milo.kim@ti.com>
> + */
> +
> +#include <linux/backlight.h>
> +#include <linux/bitops.h>
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/leds.h>
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +
> +#include "ti-lmu-backlight-data.h"
> +
> +enum ti_lmu_bl_ctrl_mode {
> + BL_REGISTER_BASED,
> + BL_PWM_BASED,
> +};
> +
> +enum ti_lmu_bl_type {
> + TI_LMU_BL, /* backlight userspace interface */
> + TI_LMU_LED, /* led userspace interface */
> +};
> +
> +enum ti_lmu_bl_ramp_mode {
> + BL_RAMP_UP,
> + BL_RAMP_DOWN,
> +};
> +
> +#define DEFAULT_PWM_NAME "lmu-backlight"
> +#define NUM_DUAL_CHANNEL 2
> +#define LMU_BACKLIGHT_DUAL_CHANNEL_USED (BIT(0) | BIT(1))
> +#define LMU_BACKLIGHT_11BIT_LSB_MASK (BIT(0) | BIT(1) | BIT(2))
> +#define LMU_BACKLIGHT_11BIT_MSB_SHIFT 3
> +
> +struct ti_lmu_bank {
> + struct device *dev;
> + int bank_id;
> + const struct ti_lmu_bl_cfg *cfg;
> + struct ti_lmu *lmu;
> + const char *label;
> + int leds;
> + int current_brightness;
> + u32 default_brightness;
> + u32 ramp_up_msec;
> + u32 ramp_down_msec;
> + u32 pwm_period;
> + enum ti_lmu_bl_ctrl_mode mode;
> + enum ti_lmu_bl_type type;
> +
> + struct notifier_block nb;
> +
> + struct backlight_device *backlight;
> + struct led_classdev *led;
> +};
> +
> +static int ti_lmu_bl_enable(struct ti_lmu_bank *lmu_bank, bool enable)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + unsigned long enable_time = lmu_bank->cfg->reginfo->enable_usec;
> + u8 *reg = lmu_bank->cfg->reginfo->enable;
> + u8 mask = BIT(lmu_bank->bank_id);
> + u8 val = (enable = true) ? mask : 0;
> + int ret;
> +
> + if (!reg)
> + return -EINVAL;
> +
> + ret = regmap_update_bits(regmap, *reg, mask, val);
> + if (ret)
> + return ret;
> +
> + if (enable_time > 0)
> + usleep_range(enable_time, enable_time + 100);
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_update_brightness_register(struct ti_lmu_bank *lmu_bank,
> + int brightness)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + u8 reg, val;
> + int ret;
> +
> + /*
> + * Brightness register update
> + *
> + * 11 bit dimming: update LSB bits and write MSB byte.
> + * MSB brightness should be shifted.
> + * 8 bit dimming: write MSB byte.
> + */
> + if (cfg->max_brightness = MAX_BRIGHTNESS_11BIT) {
> + reg = reginfo->brightness_lsb[lmu_bank->bank_id];
> + ret = regmap_update_bits(regmap, reg,
> + LMU_BACKLIGHT_11BIT_LSB_MASK,
> + brightness);
> + if (ret)
> + return ret;
> +
> + val = brightness >> LMU_BACKLIGHT_11BIT_MSB_SHIFT;
> + } else {
> + val = brightness;
> + }
> +
> + reg = reginfo->brightness_msb[lmu_bank->bank_id];
> + return regmap_write(regmap, reg, val);
> +}
> +
> +static int ti_lmu_bl_pwm_ctrl(struct ti_lmu_bank *lmu_bank, int brightness)
> +{
> + int max_brightness = lmu_bank->cfg->max_brightness;
> + struct pwm_state state = { };
> + int ret;
> +
> + if (!lmu_bank->lmu->pwm) {
> + dev_err(lmu_bank->dev, "Missing PWM device!\n");
> + return -ENODEV;
> + }
> +
> + pwm_init_state(lmu_bank->lmu->pwm, &state);
> + state.period = lmu_bank->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_bank->lmu->pwm, &state);
> + if (ret)
> + dev_err(lmu_bank->dev, "Failed to configure PWM: %d", ret);
> +
> + return ret;
> +}
> +
> +static int ti_lmu_bl_set_brightness(struct ti_lmu_bank *lmu_bank,
> + int brightness)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + bool enable = brightness > 0;
> + int ret;
> +
> + ret = ti_lmu_bl_enable(lmu_bank, enable);
> + if (ret)
> + return ret;
> +
> + if (lmu_bank->mode = BL_PWM_BASED) {
> + ti_lmu_bl_pwm_ctrl(lmu_bank, 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.
> + */
This comment could do with a little expansion (I assume the bank's
brightness register means something different when in PWM mode but the
flow of the code is tricky to read).
> + if (brightness > 0)
Isn't this "enable"?
> + brightness = MAX_BRIGHTNESS_11BIT;
> + else
> + brightness = 0;
> + break;
> + default:
case UPDATE_PWM_AND_BRT_REGISTER:
> + break;
> + }
> + }
> +
> + lmu_bank->current_brightness = brightness;
> +
> + return ti_lmu_bl_update_brightness_register(lmu_bank, brightness);
> +}
> +
> +static int ti_lmu_bl_update_status(struct backlight_device *bl_dev)
> +{
> + struct ti_lmu_bank *lmu_bank = bl_get_data(bl_dev);
> + int brightness = bl_dev->props.brightness;
> +
> + if (bl_dev->props.state & BL_CORE_SUSPENDED)
> + brightness = 0;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> +}
> +
> +static const struct backlight_ops lmu_backlight_ops = {
> + .options = BL_CORE_SUSPENDRESUME,
> + .update_status = ti_lmu_bl_update_status,
> +};
> +
> +static int ti_lmu_bl_set_led_blocking(struct led_classdev *ledc,
> + enum led_brightness value)
> +{
> + struct ti_lmu_bank *lmu_bank = dev_get_drvdata(ledc->dev->parent);
> + int brightness = value;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> +}
> +
> +static int ti_lmu_bl_check_channel(struct ti_lmu_bank *lmu_bank)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> +
> + if (!reginfo->brightness_msb)
> + return -EINVAL;
> +
> + if (cfg->max_brightness > MAX_BRIGHTNESS_8BIT) {
> + if (!reginfo->brightness_lsb)
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_create_channel(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata = lmu_bank->cfg->reginfo->channel;
> + int num_channels = lmu_bank->cfg->num_channels;
> + unsigned long led_sources = lmu_bank->leds;
> + int i, ret;
> + u8 shift;
> +
> + /*
> + * How to create backlight output channels:
> + * Check 'led_sources' bit and update registers.
> + *
> + * 1) Dual channel configuration
> + * The 1st register data is used for single channel.
> + * The 2nd register data is used for dual channel.
> + *
> + * 2) Multiple channel configuration
> + * Each register data is mapped to bank ID.
> + * Bit shift operation is defined in channel registers.
> + *
> + * Channel register data consists of address, mask, value.
> + */
> +
> + if (num_channels = NUM_DUAL_CHANNEL) {
> + if (led_sources = LMU_BACKLIGHT_DUAL_CHANNEL_USED)
> + regdata++;
> +
> + return regmap_update_bits(regmap, regdata->reg, regdata->mask,
> + regdata->val);
> + }
> +
> + for (i = 0; regdata && i < num_channels; i++) {
> + /*
> + * Note that the result of regdata->val is shift bit.
> + * The bank_id should be shifted for the channel configuration.
> + */
> + if (test_bit(i, &led_sources)) {
> + shift = regdata->val;
> + ret = regmap_update_bits(regmap, regdata->reg,
> + regdata->mask,
> + lmu_bank->bank_id << shift);
> + if (ret)
> + return ret;
> + }
> +
> + regdata++;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_update_ctrl_mode(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata > + lmu_bank->cfg->reginfo->mode + lmu_bank->bank_id;
> + u8 val = regdata->val;
> +
> + if (!regdata)
> + return 0;
> +
> + /*
> + * Update PWM configuration register.
> + * If the mode is register based, then clear the bit.
> + */
> + if (lmu_bank->mode != BL_PWM_BASED)
> + val = 0;
> +
> + return regmap_update_bits(regmap, regdata->reg, regdata->mask, val);
> +}
> +
> +static int ti_lmu_bl_convert_ramp_to_index(struct ti_lmu_bank *lmu_bank,
> + enum ti_lmu_bl_ramp_mode mode)
> +{
> + const int *ramp_table = lmu_bank->cfg->ramp_table;
> + const int size = lmu_bank->cfg->size_ramp;
> + unsigned int msec;
> + int i;
> +
> + if (!ramp_table)
> + return -EINVAL;
> +
> + switch (mode) {
> + case BL_RAMP_UP:
> + msec = lmu_bank->ramp_up_msec;
> + break;
> + case BL_RAMP_DOWN:
> + msec = lmu_bank->ramp_down_msec;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (msec <= ramp_table[0])
> + return 0;
> +
> + if (msec > ramp_table[size - 1])
> + return size - 1;
> +
> + for (i = 1; i < size; i++) {
> + if (msec = ramp_table[i])
> + return i;
> +
> + /* Find an approximate index by looking up the table */
> + if (msec > ramp_table[i - 1] && msec < ramp_table[i]) {
> + if (msec - ramp_table[i - 1] < ramp_table[i] - msec)
> + return i - 1;
> + else
> + return i;
> + }
> + }
> +
> + return -EINVAL;
> +}
> +
> +
> +static int ti_lmu_bl_set_ramp(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct ti_lmu_bl_reg *reginfo = lmu_bank->cfg->reginfo;
> + int offset = reginfo->ramp_reg_offset;
> + int i, ret, index;
> + struct lmu_bl_reg_data regdata;
> +
> + for (i = BL_RAMP_UP; i <= BL_RAMP_DOWN; i++) {
> + index = ti_lmu_bl_convert_ramp_to_index(lmu_bank, i);
> + if (index > 0) {
> + if (!reginfo->ramp)
> + break;
> +
> + regdata = reginfo->ramp[i];
> + if (lmu_bank->bank_id != 0)
> + regdata.val += offset;
> +
> + /* regdata.val is shift bit */
> + ret = regmap_update_bits(regmap, regdata.reg,
> + regdata.mask,
> + index << regdata.val);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_configure(struct ti_lmu_bank *lmu_bank)
> +{
> + int ret;
> +
> + ret = ti_lmu_bl_check_channel(lmu_bank);
> + if (ret)
> + return ret;
> +
> + ret = ti_lmu_bl_create_channel(lmu_bank);
> + if (ret)
> + return ret;
> +
> + ret = ti_lmu_bl_update_ctrl_mode(lmu_bank);
> + if (ret)
> + return ret;
> +
> + return ti_lmu_bl_set_ramp(lmu_bank);
> +}
> +
> +static int ti_lmu_bl_register_backlight(struct ti_lmu_bank *lmu_bank)
> +{
> + struct backlight_device *bl_dev;
> + struct backlight_properties props;
> +
> + if (lmu_bank->type != TI_LMU_BL)
> + return -EINVAL;
> +
> + memset(&props, 0, sizeof(struct backlight_properties));
> + props.type = BACKLIGHT_PLATFORM;
> + props.brightness = lmu_bank->default_brightness;
> + props.max_brightness = lmu_bank->cfg->max_brightness;
> +
> + bl_dev = devm_backlight_device_register(lmu_bank->dev, lmu_bank->label,
> + lmu_bank->dev, lmu_bank,
> + &lmu_backlight_ops, &props);
> + if (IS_ERR(bl_dev))
> + return PTR_ERR(bl_dev);
> +
> + lmu_bank->backlight = bl_dev;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_register_led(struct ti_lmu_bank *lmu_bank)
> +{
> + int err;
> +
> + if (lmu_bank->type != TI_LMU_LED)
> + return -EINVAL;
> +
> + lmu_bank->led = devm_kzalloc(lmu_bank->dev, sizeof(*lmu_bank->led),
> + GFP_KERNEL);
> + if (!lmu_bank->led)
> + return -ENOMEM;
> +
> + lmu_bank->led->name = lmu_bank->label;
> + lmu_bank->led->max_brightness = lmu_bank->cfg->max_brightness;
> + lmu_bank->led->brightness_set_blocking > + ti_lmu_bl_set_led_blocking;
> +
> + err = devm_led_classdev_register(lmu_bank->dev, lmu_bank->led);
> + if (err)
> + return err;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
> +{
> + switch (lmu_bank->type) {
> + case TI_LMU_BL:
> + return ti_lmu_bl_register_backlight(lmu_bank);
> + case TI_LMU_LED:
> + return ti_lmu_bl_register_led(lmu_bank);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int setup_of_node(struct platform_device *pdev)
> +{
> + struct device_node *parent_node = pdev->dev.parent->of_node;
> + char *name;
> +
> + if (!parent_node)
> + return 0;
> +
> + name = kasprintf(GFP_KERNEL, "bank%d", pdev->id);
> + if (!name)
> + return -ENOMEM;
> +
> + pdev->dev.of_node = of_get_child_by_name(parent_node, name);
> + kfree(name);
> +
> + if (!pdev->dev.of_node)
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_parse_led_sources(struct device *dev)
> +{
> + unsigned long mask = 0;
> + int ret;
> + int size, i;
> + u32 *leds;
> +
> + size = device_property_read_u32_array(dev, "ti,led-sources", NULL, 0);
> + if (size <= 0) {
> + dev_err(dev, "Missing or malformed property led-sources: %d\n",
> + size);
> + return size < 0 ? size : -EINVAL;
> + }
> +
> + leds = kmalloc_array(size, sizeof(u32), GFP_KERNEL);
> + if (!leds)
> + return -ENOMEM;
> +
> + ret = device_property_read_u32_array(dev, "ti,led-sources", leds, size);
> + if (ret) {
> + dev_err(dev, "Failed to read led-sources property: %d\n", ret);
> + goto out;
> + }
> +
> + for (i = 0; i < size; i++)
> + set_bit(leds[i], &mask);
> +
> + ret = mask;
> +
> +out:
> + kfree(leds);
> + return ret;
> +}
> +
> +static int ti_lmu_bl_init(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata > + lmu_bank->cfg->reginfo->init;
> + int num_init = lmu_bank->cfg->reginfo->num_init;
> + int i, ret;
> +
> + if (lmu_bank->lmu->backlight_initialized)
> + return 0;
> + lmu_bank->lmu->backlight_initialized = true;
> +
> + for (i = 0; regdata && i < num_init; i++) {
> + ret = regmap_update_bits(regmap, regdata->reg, regdata->mask,
> + regdata->val);
> + if (ret)
> + return ret;
> +
> + regdata++;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_reload(struct ti_lmu_bank *lmu_bank)
> +{
> + int err;
> +
> + ti_lmu_bl_init(lmu_bank);
> +
> + err = ti_lmu_bl_configure(lmu_bank);
> + if (err)
> + return err;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, lmu_bank->current_brightness);
> +}
> +
> +static int ti_lmu_bl_monitor_notifier(struct notifier_block *nb,
> + unsigned long action, void *unused)
> +{
> + struct ti_lmu_bank *lmu_bank = container_of(nb, struct ti_lmu_bank, nb);
> +
> + if (action = LMU_EVENT_MONITOR_DONE) {
> + if (ti_lmu_bl_reload(lmu_bank))
> + return NOTIFY_STOP;
> + }
> +
> + return NOTIFY_OK;
> +}
> +
> +static int ti_lmu_bl_probe(struct platform_device *pdev)
> +{
> + struct ti_lmu_bank *lmu_bank;
> + int err;
> +
> + err = setup_of_node(pdev);
> + if (err)
> + return err;
> +
> + lmu_bank = devm_kzalloc(&pdev->dev, sizeof(*lmu_bank), GFP_KERNEL);
> + if (!lmu_bank)
> + return -ENOMEM;
> + lmu_bank->dev = &pdev->dev;
> + dev_set_drvdata(&pdev->dev, lmu_bank);
> +
> + err = device_property_read_string(&pdev->dev, "label",
> + &lmu_bank->label);
> + if (err)
> + return err;
> +
> + lmu_bank->type = TI_LMU_BL;
> + if (!strcmp(lmu_bank->label, "keyboard")) {
> + lmu_bank->type = TI_LMU_LED;
> + lmu_bank->label = "kbd_backlight";
> + }
> +
> + lmu_bank->leds = ti_lmu_parse_led_sources(&pdev->dev);
> + if (lmu_bank->leds < 0)
> + return lmu_bank->leds;
> + else if (lmu_bank->leds = 0)
> + return -EINVAL;
> +
> + device_property_read_u32(&pdev->dev, "default-brightness-level",
> + &lmu_bank->default_brightness);
> + device_property_read_u32(&pdev->dev, "ti,ramp-up-ms",
> + &lmu_bank->ramp_up_msec);
> + device_property_read_u32(&pdev->dev, "ti,ramp-down-ms",
> + &lmu_bank->ramp_down_msec);
> + device_property_read_u32(&pdev->dev, "pwm-period",
> + &lmu_bank->pwm_period);
> +
> + if (lmu_bank->pwm_period > 0)
> + lmu_bank->mode = BL_PWM_BASED;
> + else
> + lmu_bank->mode = BL_REGISTER_BASED;
> +
> + lmu_bank->lmu = dev_get_drvdata(pdev->dev.parent);
> + lmu_bank->cfg = &lmu_bl_cfg[lmu_bank->lmu->id];
> + lmu_bank->bank_id = pdev->id;
> +
> + ti_lmu_bl_init(lmu_bank);
> +
> + err = ti_lmu_bl_configure(lmu_bank);
> + if (err)
> + return err;
> +
> + err = ti_lmu_bl_add_device(lmu_bank);
> + if (err)
> + return err;
> +
> + err = ti_lmu_bl_set_brightness(lmu_bank,
> + lmu_bank->default_brightness);
> + if (err)
> + return err;
> +
> + /*
> + * Notifier callback is required because backlight device needs
> + * reconfiguration after fault detection procedure is done by
> + * ti-lmu-fault-monitor driver.
> + */
> + if (lmu_bank->cfg->fault_monitor_used) {
> + lmu_bank->nb.notifier_call = ti_lmu_bl_monitor_notifier;
> + err = blocking_notifier_chain_register(&lmu_bank->lmu->notifier,
> + &lmu_bank->nb);
> + if (err)
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_remove(struct platform_device *pdev)
> +{
> + struct ti_lmu_bank *lmu_bank = platform_get_drvdata(pdev);
> +
> + if (lmu_bank->cfg->fault_monitor_used)
> + blocking_notifier_chain_unregister(&lmu_bank->lmu->notifier,
> + &lmu_bank->nb);
> +
> + ti_lmu_bl_set_brightness(lmu_bank, 0);
> +
> + return 0;
> +}
> +
> +static struct platform_driver ti_lmu_bl_driver = {
> + .probe = ti_lmu_bl_probe,
> + .remove = ti_lmu_bl_remove,
> + .driver = {
> + .name = "ti-lmu-led-backlight",
> + },
> +};
> +module_platform_driver(ti_lmu_bl_driver)
> +
> +MODULE_DESCRIPTION("TI LMU Backlight LED Driver");
> +MODULE_AUTHOR("Sebastian Reichel");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:ti-lmu-led-backlight");
> diff --git a/drivers/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 <milo.kim@ti.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include "ti-lmu-backlight-data.h"
> +
> +/* LM3532 */
> +static const struct lmu_bl_reg_data lm3532_init_data[] = {
> + { LM3532_REG_ZONE_CFG_A, LM3532_ZONE_MASK, LM3532_ZONE_0 },
> + { LM3532_REG_ZONE_CFG_B, LM3532_ZONE_MASK, LM3532_ZONE_1 },
> + { LM3532_REG_ZONE_CFG_C, LM3532_ZONE_MASK, LM3532_ZONE_2 },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_channel_data[] = {
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED1_CFG_MASK,
> + LM3532_ILED1_CFG_SHIFT },
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED2_CFG_MASK,
> + LM3532_ILED2_CFG_SHIFT },
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED3_CFG_MASK,
> + LM3532_ILED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_mode_data[] = {
> + { LM3532_REG_PWM_A_CFG, LM3532_PWM_A_MASK, LM3532_PWM_ZONE_0 },
> + { LM3532_REG_PWM_B_CFG, LM3532_PWM_B_MASK, LM3532_PWM_ZONE_1 },
> + { LM3532_REG_PWM_C_CFG, LM3532_PWM_C_MASK, LM3532_PWM_ZONE_2 },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_ramp_data[] = {
> + { LM3532_REG_RAMPUP, LM3532_RAMPUP_MASK, LM3532_RAMPUP_SHIFT },
> + { LM3532_REG_RAMPDN, LM3532_RAMPDN_MASK, LM3532_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3532_enable_reg = LM3532_REG_ENABLE;
> +
> +static u8 lm3532_brightness_regs[] = {
> + LM3532_REG_BRT_A,
> + LM3532_REG_BRT_B,
> + LM3532_REG_BRT_C,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3532_reg_info = {
> + .init = lm3532_init_data,
> + .num_init = ARRAY_SIZE(lm3532_init_data),
> + .channel = lm3532_channel_data,
> + .mode = lm3532_mode_data,
> + .ramp = lm3532_ramp_data,
> + .enable = &lm3532_enable_reg,
> + .brightness_msb = lm3532_brightness_regs,
> +};
> +
> +/* LM3631 */
> +static const struct lmu_bl_reg_data lm3631_init_data[] = {
> + { LM3631_REG_BRT_MODE, LM3631_MODE_MASK, LM3631_DEFAULT_MODE },
> + { LM3631_REG_BL_CFG, LM3631_MAP_MASK, LM3631_EXPONENTIAL_MAP },
> +};
> +
> +static const struct lmu_bl_reg_data lm3631_channel_data[] = {
> + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_SINGLE_CHANNEL },
> + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_DUAL_CHANNEL },
> +};
> +
> +static const struct lmu_bl_reg_data lm3631_ramp_data[] = {
> + { LM3631_REG_SLOPE, LM3631_SLOPE_MASK, LM3631_SLOPE_SHIFT },
> +};
> +
> +static u8 lm3631_enable_reg = LM3631_REG_DEVCTRL;
> +static u8 lm3631_brightness_msb_reg = LM3631_REG_BRT_MSB;
> +static u8 lm3631_brightness_lsb_reg = LM3631_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3631_reg_info = {
> + .init = lm3631_init_data,
> + .num_init = ARRAY_SIZE(lm3631_init_data),
> + .channel = lm3631_channel_data,
> + .ramp = lm3631_ramp_data,
> + .enable = &lm3631_enable_reg,
> + .brightness_msb = &lm3631_brightness_msb_reg,
> + .brightness_lsb = &lm3631_brightness_lsb_reg,
> +};
> +
> +/* LM3632 */
> +static const struct lmu_bl_reg_data lm3632_init_data[] = {
> + { LM3632_REG_CONFIG1, LM3632_OVP_MASK, LM3632_OVP_25V },
> + { LM3632_REG_CONFIG2, LM3632_SWFREQ_MASK, LM3632_SWFREQ_1MHZ },
> +};
> +
> +static const struct lmu_bl_reg_data lm3632_channel_data[] = {
> + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_SINGLE_CHANNEL },
> + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_DUAL_CHANNEL },
> +};
> +
> +static const struct lmu_bl_reg_data lm3632_mode_data[] = {
> + { LM3632_REG_IO_CTRL, LM3632_PWM_MASK, LM3632_PWM_MODE },
> +};
> +
> +static u8 lm3632_enable_reg = LM3632_REG_ENABLE;
> +static u8 lm3632_brightness_msb_reg = LM3632_REG_BRT_MSB;
> +static u8 lm3632_brightness_lsb_reg = LM3632_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3632_reg_info = {
> + .init = lm3632_init_data,
> + .num_init = ARRAY_SIZE(lm3632_init_data),
> + .channel = lm3632_channel_data,
> + .mode = lm3632_mode_data,
> + .enable = &lm3632_enable_reg,
> + .brightness_msb = &lm3632_brightness_msb_reg,
> + .brightness_lsb = &lm3632_brightness_lsb_reg,
> +};
> +
> +/* LM3633 */
> +static const struct lmu_bl_reg_data lm3633_init_data[] = {
> + { LM3633_REG_BOOST_CFG, LM3633_OVP_MASK, LM3633_OVP_40V },
> + { LM3633_REG_BL_RAMP_CONF, LM3633_BL_RAMP_MASK, LM3633_BL_RAMP_EACH },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_channel_data[] = {
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED1_CFG_MASK,
> + LM3633_HVLED1_CFG_SHIFT },
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED2_CFG_MASK,
> + LM3633_HVLED2_CFG_SHIFT },
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED3_CFG_MASK,
> + LM3633_HVLED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_mode_data[] = {
> + { LM3633_REG_PWM_CFG, LM3633_PWM_A_MASK, LM3633_PWM_A_MASK },
> + { LM3633_REG_PWM_CFG, LM3633_PWM_B_MASK, LM3633_PWM_B_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_ramp_data[] = {
> + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPUP_MASK, LM3633_BL_RAMPUP_SHIFT },
> + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPDN_MASK, LM3633_BL_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3633_enable_reg = LM3633_REG_ENABLE;
> +
> +static u8 lm3633_brightness_msb_regs[] = {
> + LM3633_REG_BRT_HVLED_A_MSB,
> + LM3633_REG_BRT_HVLED_B_MSB,
> +};
> +
> +static u8 lm3633_brightness_lsb_regs[] = {
> + LM3633_REG_BRT_HVLED_A_LSB,
> + LM3633_REG_BRT_HVLED_B_LSB,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3633_reg_info = {
> + .init = lm3633_init_data,
> + .num_init = ARRAY_SIZE(lm3633_init_data),
> + .channel = lm3633_channel_data,
> + .mode = lm3633_mode_data,
> + .ramp = lm3633_ramp_data,
> + .ramp_reg_offset = 1, /* For LM3633_REG_BL1_RAMPUP/DN */
> + .enable = &lm3633_enable_reg,
> + .brightness_msb = lm3633_brightness_msb_regs,
> + .brightness_lsb = lm3633_brightness_lsb_regs,
> +};
> +
> +/* LM3695 */
> +static const struct lmu_bl_reg_data lm3695_init_data[] = {
> + { LM3695_REG_GP, LM3695_BRT_RW_MASK, LM3695_BRT_RW_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3695_channel_data[] = {
> + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_SINGLE_CHANNEL },
> + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_DUAL_CHANNEL },
> +};
> +
> +static u8 lm3695_enable_reg = LM3695_REG_GP;
> +static u8 lm3695_brightness_msb_reg = LM3695_REG_BRT_MSB;
> +static u8 lm3695_brightness_lsb_reg = LM3695_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3695_reg_info = {
> + .init = lm3695_init_data,
> + .num_init = ARRAY_SIZE(lm3695_init_data),
> + .channel = lm3695_channel_data,
> + .enable = &lm3695_enable_reg,
> + .enable_usec = 600,
> + .brightness_msb = &lm3695_brightness_msb_reg,
> + .brightness_lsb = &lm3695_brightness_lsb_reg,
> +};
> +
> +/* LM3697 */
> +static const struct lmu_bl_reg_data lm3697_init_data[] = {
> + { LM3697_REG_RAMP_CONF, LM3697_RAMP_MASK, LM3697_RAMP_EACH },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_channel_data[] = {
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED1_CFG_MASK,
> + LM3697_HVLED1_CFG_SHIFT },
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED2_CFG_MASK,
> + LM3697_HVLED2_CFG_SHIFT },
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED3_CFG_MASK,
> + LM3697_HVLED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_mode_data[] = {
> + { LM3697_REG_PWM_CFG, LM3697_PWM_A_MASK, LM3697_PWM_A_MASK },
> + { LM3697_REG_PWM_CFG, LM3697_PWM_B_MASK, LM3697_PWM_B_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_ramp_data[] = {
> + { LM3697_REG_BL0_RAMP, LM3697_RAMPUP_MASK, LM3697_RAMPUP_SHIFT },
> + { LM3697_REG_BL0_RAMP, LM3697_RAMPDN_MASK, LM3697_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3697_enable_reg = LM3697_REG_ENABLE;
> +
> +static u8 lm3697_brightness_msb_regs[] = {
> + LM3697_REG_BRT_A_MSB,
> + LM3697_REG_BRT_B_MSB,
> +};
> +
> +static u8 lm3697_brightness_lsb_regs[] = {
> + LM3697_REG_BRT_A_LSB,
> + LM3697_REG_BRT_B_LSB,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3697_reg_info = {
> + .init = lm3697_init_data,
> + .num_init = ARRAY_SIZE(lm3697_init_data),
> + .channel = lm3697_channel_data,
> + .mode = lm3697_mode_data,
> + .ramp = lm3697_ramp_data,
> + .ramp_reg_offset = 1, /* For LM3697_REG_BL1_RAMPUP/DN */
> + .enable = &lm3697_enable_reg,
> + .brightness_msb = lm3697_brightness_msb_regs,
> + .brightness_lsb = lm3697_brightness_lsb_regs,
> +};
> +
> +static int lm3532_ramp_table[] = { 0, 1, 2, 4, 8, 16, 32, 65 };
> +
> +static int lm3631_ramp_table[] = {
> + 0, 1, 2, 5, 10, 20, 50, 100,
> + 250, 500, 750, 1000, 1500, 2000, 3000, 4000,
> +};
> +
> +static int common_ramp_table[] = {
> + 2, 250, 500, 1000, 2000, 4000, 8000, 16000,
> +};
> +
> +#define LM3532_MAX_CHANNELS 3
> +#define LM3631_MAX_CHANNELS 2
> +#define LM3632_MAX_CHANNELS 2
> +#define LM3633_MAX_CHANNELS 3
> +#define LM3695_MAX_CHANNELS 2
> +#define LM3697_MAX_CHANNELS 3
> +
> +const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID] = {
> + {
> + .reginfo = &lm3532_reg_info,
> + .num_channels = LM3532_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_8BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + .ramp_table = lm3532_ramp_table,
> + .size_ramp = ARRAY_SIZE(lm3532_ramp_table),
> + },
> + {
> + .reginfo = &lm3631_reg_info,
> + .num_channels = LM3631_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_ONLY,
> + .ramp_table = lm3631_ramp_table,
> + .size_ramp = ARRAY_SIZE(lm3631_ramp_table),
> + },
> + {
> + .reginfo = &lm3632_reg_info,
> + .num_channels = LM3632_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_ONLY,
> + },
> + {
> + .reginfo = &lm3633_reg_info,
> + .num_channels = LM3633_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_MAX_BRT,
> + .ramp_table = common_ramp_table,
> + .size_ramp = ARRAY_SIZE(common_ramp_table),
> + .fault_monitor_used = true,
> + },
> + {
> + .reginfo = &lm3695_reg_info,
> + .num_channels = LM3695_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + },
> + {
> + .reginfo = &lm3697_reg_info,
> + .num_channels = LM3697_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + .ramp_table = common_ramp_table,
> + .size_ramp = ARRAY_SIZE(common_ramp_table),
> + .fault_monitor_used = true,
> + },
> +};
> diff --git a/drivers/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 <milo.kim@ti.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#ifndef __TI_LMU_BACKLIGHT_H__
> +#define __TI_LMU_BACKLIGHT_H__
> +
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +
> +#define MAX_BRIGHTNESS_8BIT 255
> +#define MAX_BRIGHTNESS_11BIT 2047
> +
> +enum ti_lmu_bl_pwm_action {
> + /* Update PWM duty, no brightness register update is required */
> + UPDATE_PWM_ONLY,
> + /* Update not only duty but also brightness register */
> + UPDATE_PWM_AND_BRT_REGISTER,
> + /* Update max value in brightness registers */
> + UPDATE_MAX_BRT,
> +};
> +
> +struct lmu_bl_reg_data {
> + u8 reg;
> + u8 mask;
> + u8 val;
> +};
> +
> +/**
> + * struct ti_lmu_bl_reg
> + *
> + * @init: Device initialization registers
> + * @num_init: Numbers of initialization registers
> + * @channel: Backlight channel configuration registers
> + * @mode: Brightness control mode registers
> + * @ramp: Ramp registers for lighting effect
> + * @ramp_reg_offset: Ramp register offset.
> + * Only used for multiple ramp registers.
> + * @enable: Enable control register address
> + * @enable_usec: Delay time for updating enable register.
> + * Unit is microsecond.
> + * @brightness_msb: Brightness MSB(Upper 8 bits) registers.
> + * Concatenated with LSB in 11 bit dimming mode.
> + * In 8 bit dimming, only MSB is used.
> + * @brightness_lsb: Brightness LSB(Lower 3 bits) registers.
> + * Only valid in 11 bit dimming mode.
> + */
> +struct ti_lmu_bl_reg {
> + const struct lmu_bl_reg_data *init;
> + int num_init;
> + const struct lmu_bl_reg_data *channel;
> + const struct lmu_bl_reg_data *mode;
> + const struct lmu_bl_reg_data *ramp;
> + int ramp_reg_offset;
> + u8 *enable;
> + unsigned long enable_usec;
> + u8 *brightness_msb;
> + u8 *brightness_lsb;
> +};
> +
> +/**
> + * struct ti_lmu_bl_cfg
> + *
> + * @reginfo: Device register configuration
> + * @num_channels: Number of backlight channels
> + * @max_brightness: Max brightness value of backlight device
> + * @pwm_action: How to control brightness registers in PWM mode
> + * @ramp_table: [Optional] Ramp time table for lighting effect.
> + * It's used for searching approximate register index.
> + * @size_ramp: [Optional] Size of ramp table
> + * @fault_monitor_used: [Optional] Set true if the device needs to handle
> + * LMU fault monitor event.
> + *
> + * This structure is used for device specific data configuration.
> + */
> +struct ti_lmu_bl_cfg {
> + const struct ti_lmu_bl_reg *reginfo;
> + int num_channels;
> + int max_brightness;
> + enum ti_lmu_bl_pwm_action pwm_action;
> + int *ramp_table;
> + int size_ramp;
> + bool fault_monitor_used;
> +};
> +
> +extern const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID];
> +#endif
> --
> 2.16.2
>
^ permalink raw reply [flat|nested] 62+ messages in thread* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
@ 2018-04-04 14:57 ` Daniel Thompson
0 siblings, 0 replies; 62+ messages in thread
From: Daniel Thompson @ 2018-04-04 14:57 UTC (permalink / raw)
To: Sebastian Reichel
Cc: Sebastian Reichel, Milo Kim, Lee Jones, Rob Herring,
Tony Lindgren, Jingoo Han, Mark Rutland, linux-kernel,
linux-fbdev, devicetree, linux-omap
On Fri, Mar 30, 2018 at 07:24:12PM +0200, Sebastian Reichel wrote:
> This adds backlight support for the following TI LMU
> chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
>
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> [add LED subsystem support for keyboard backlight and rework DT
Milo's mail has be bouncing for a very long time now. Did they really
sign off this code or is this intended to be an authorship credit?
> binding according to Rob Herrings feedback]
> Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> ---
> drivers/video/backlight/Kconfig | 7 +
> drivers/video/backlight/Makefile | 3 +
> drivers/video/backlight/ti-lmu-backlight-core.c | 666 ++++++++++++++++++++++++
> drivers/video/backlight/ti-lmu-backlight-data.c | 304 +++++++++++
> drivers/video/backlight/ti-lmu-backlight-data.h | 95 ++++
> 5 files changed, 1075 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 5d2d0d7e8100..27e6c5a0add8 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 19da71d518bf..a1132d3dfd4c 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -53,6 +53,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..a6099581edd7
> --- /dev/null
> +++ b/drivers/video/backlight/ti-lmu-backlight-core.c
> @@ -0,0 +1,666 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2015 Texas Instruments
> + * Copyright 2018 Sebastian Reichel
> + *
> + * TI LMU Backlight driver, based on previous work from
> + * Milo Kim <milo.kim@ti.com>
> + */
> +
> +#include <linux/backlight.h>
> +#include <linux/bitops.h>
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/leds.h>
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +
> +#include "ti-lmu-backlight-data.h"
> +
> +enum ti_lmu_bl_ctrl_mode {
> + BL_REGISTER_BASED,
> + BL_PWM_BASED,
> +};
> +
> +enum ti_lmu_bl_type {
> + TI_LMU_BL, /* backlight userspace interface */
> + TI_LMU_LED, /* led userspace interface */
> +};
> +
> +enum ti_lmu_bl_ramp_mode {
> + BL_RAMP_UP,
> + BL_RAMP_DOWN,
> +};
> +
> +#define DEFAULT_PWM_NAME "lmu-backlight"
> +#define NUM_DUAL_CHANNEL 2
> +#define LMU_BACKLIGHT_DUAL_CHANNEL_USED (BIT(0) | BIT(1))
> +#define LMU_BACKLIGHT_11BIT_LSB_MASK (BIT(0) | BIT(1) | BIT(2))
> +#define LMU_BACKLIGHT_11BIT_MSB_SHIFT 3
> +
> +struct ti_lmu_bank {
> + struct device *dev;
> + int bank_id;
> + const struct ti_lmu_bl_cfg *cfg;
> + struct ti_lmu *lmu;
> + const char *label;
> + int leds;
> + int current_brightness;
> + u32 default_brightness;
> + u32 ramp_up_msec;
> + u32 ramp_down_msec;
> + u32 pwm_period;
> + enum ti_lmu_bl_ctrl_mode mode;
> + enum ti_lmu_bl_type type;
> +
> + struct notifier_block nb;
> +
> + struct backlight_device *backlight;
> + struct led_classdev *led;
> +};
> +
> +static int ti_lmu_bl_enable(struct ti_lmu_bank *lmu_bank, bool enable)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + unsigned long enable_time = lmu_bank->cfg->reginfo->enable_usec;
> + u8 *reg = lmu_bank->cfg->reginfo->enable;
> + u8 mask = BIT(lmu_bank->bank_id);
> + u8 val = (enable == true) ? mask : 0;
> + int ret;
> +
> + if (!reg)
> + return -EINVAL;
> +
> + ret = regmap_update_bits(regmap, *reg, mask, val);
> + if (ret)
> + return ret;
> +
> + if (enable_time > 0)
> + usleep_range(enable_time, enable_time + 100);
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_update_brightness_register(struct ti_lmu_bank *lmu_bank,
> + int brightness)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + u8 reg, val;
> + int ret;
> +
> + /*
> + * Brightness register update
> + *
> + * 11 bit dimming: update LSB bits and write MSB byte.
> + * MSB brightness should be shifted.
> + * 8 bit dimming: write MSB byte.
> + */
> + if (cfg->max_brightness == MAX_BRIGHTNESS_11BIT) {
> + reg = reginfo->brightness_lsb[lmu_bank->bank_id];
> + ret = regmap_update_bits(regmap, reg,
> + LMU_BACKLIGHT_11BIT_LSB_MASK,
> + brightness);
> + if (ret)
> + return ret;
> +
> + val = brightness >> LMU_BACKLIGHT_11BIT_MSB_SHIFT;
> + } else {
> + val = brightness;
> + }
> +
> + reg = reginfo->brightness_msb[lmu_bank->bank_id];
> + return regmap_write(regmap, reg, val);
> +}
> +
> +static int ti_lmu_bl_pwm_ctrl(struct ti_lmu_bank *lmu_bank, int brightness)
> +{
> + int max_brightness = lmu_bank->cfg->max_brightness;
> + struct pwm_state state = { };
> + int ret;
> +
> + if (!lmu_bank->lmu->pwm) {
> + dev_err(lmu_bank->dev, "Missing PWM device!\n");
> + return -ENODEV;
> + }
> +
> + pwm_init_state(lmu_bank->lmu->pwm, &state);
> + state.period = lmu_bank->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_bank->lmu->pwm, &state);
> + if (ret)
> + dev_err(lmu_bank->dev, "Failed to configure PWM: %d", ret);
> +
> + return ret;
> +}
> +
> +static int ti_lmu_bl_set_brightness(struct ti_lmu_bank *lmu_bank,
> + int brightness)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + bool enable = brightness > 0;
> + int ret;
> +
> + ret = ti_lmu_bl_enable(lmu_bank, enable);
> + if (ret)
> + return ret;
> +
> + if (lmu_bank->mode == BL_PWM_BASED) {
> + ti_lmu_bl_pwm_ctrl(lmu_bank, 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.
> + */
This comment could do with a little expansion (I assume the bank's
brightness register means something different when in PWM mode but the
flow of the code is tricky to read).
> + if (brightness > 0)
Isn't this "enable"?
> + brightness = MAX_BRIGHTNESS_11BIT;
> + else
> + brightness = 0;
> + break;
> + default:
case UPDATE_PWM_AND_BRT_REGISTER:
> + break;
> + }
> + }
> +
> + lmu_bank->current_brightness = brightness;
> +
> + return ti_lmu_bl_update_brightness_register(lmu_bank, brightness);
> +}
> +
> +static int ti_lmu_bl_update_status(struct backlight_device *bl_dev)
> +{
> + struct ti_lmu_bank *lmu_bank = bl_get_data(bl_dev);
> + int brightness = bl_dev->props.brightness;
> +
> + if (bl_dev->props.state & BL_CORE_SUSPENDED)
> + brightness = 0;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> +}
> +
> +static const struct backlight_ops lmu_backlight_ops = {
> + .options = BL_CORE_SUSPENDRESUME,
> + .update_status = ti_lmu_bl_update_status,
> +};
> +
> +static int ti_lmu_bl_set_led_blocking(struct led_classdev *ledc,
> + enum led_brightness value)
> +{
> + struct ti_lmu_bank *lmu_bank = dev_get_drvdata(ledc->dev->parent);
> + int brightness = value;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> +}
> +
> +static int ti_lmu_bl_check_channel(struct ti_lmu_bank *lmu_bank)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> +
> + if (!reginfo->brightness_msb)
> + return -EINVAL;
> +
> + if (cfg->max_brightness > MAX_BRIGHTNESS_8BIT) {
> + if (!reginfo->brightness_lsb)
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_create_channel(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata = lmu_bank->cfg->reginfo->channel;
> + int num_channels = lmu_bank->cfg->num_channels;
> + unsigned long led_sources = lmu_bank->leds;
> + int i, ret;
> + u8 shift;
> +
> + /*
> + * How to create backlight output channels:
> + * Check 'led_sources' bit and update registers.
> + *
> + * 1) Dual channel configuration
> + * The 1st register data is used for single channel.
> + * The 2nd register data is used for dual channel.
> + *
> + * 2) Multiple channel configuration
> + * Each register data is mapped to bank ID.
> + * Bit shift operation is defined in channel registers.
> + *
> + * Channel register data consists of address, mask, value.
> + */
> +
> + if (num_channels == NUM_DUAL_CHANNEL) {
> + if (led_sources == LMU_BACKLIGHT_DUAL_CHANNEL_USED)
> + regdata++;
> +
> + return regmap_update_bits(regmap, regdata->reg, regdata->mask,
> + regdata->val);
> + }
> +
> + for (i = 0; regdata && i < num_channels; i++) {
> + /*
> + * Note that the result of regdata->val is shift bit.
> + * The bank_id should be shifted for the channel configuration.
> + */
> + if (test_bit(i, &led_sources)) {
> + shift = regdata->val;
> + ret = regmap_update_bits(regmap, regdata->reg,
> + regdata->mask,
> + lmu_bank->bank_id << shift);
> + if (ret)
> + return ret;
> + }
> +
> + regdata++;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_update_ctrl_mode(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata =
> + lmu_bank->cfg->reginfo->mode + lmu_bank->bank_id;
> + u8 val = regdata->val;
> +
> + if (!regdata)
> + return 0;
> +
> + /*
> + * Update PWM configuration register.
> + * If the mode is register based, then clear the bit.
> + */
> + if (lmu_bank->mode != BL_PWM_BASED)
> + val = 0;
> +
> + return regmap_update_bits(regmap, regdata->reg, regdata->mask, val);
> +}
> +
> +static int ti_lmu_bl_convert_ramp_to_index(struct ti_lmu_bank *lmu_bank,
> + enum ti_lmu_bl_ramp_mode mode)
> +{
> + const int *ramp_table = lmu_bank->cfg->ramp_table;
> + const int size = lmu_bank->cfg->size_ramp;
> + unsigned int msec;
> + int i;
> +
> + if (!ramp_table)
> + return -EINVAL;
> +
> + switch (mode) {
> + case BL_RAMP_UP:
> + msec = lmu_bank->ramp_up_msec;
> + break;
> + case BL_RAMP_DOWN:
> + msec = lmu_bank->ramp_down_msec;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (msec <= ramp_table[0])
> + return 0;
> +
> + if (msec > ramp_table[size - 1])
> + return size - 1;
> +
> + for (i = 1; i < size; i++) {
> + if (msec == ramp_table[i])
> + return i;
> +
> + /* Find an approximate index by looking up the table */
> + if (msec > ramp_table[i - 1] && msec < ramp_table[i]) {
> + if (msec - ramp_table[i - 1] < ramp_table[i] - msec)
> + return i - 1;
> + else
> + return i;
> + }
> + }
> +
> + return -EINVAL;
> +}
> +
> +
> +static int ti_lmu_bl_set_ramp(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct ti_lmu_bl_reg *reginfo = lmu_bank->cfg->reginfo;
> + int offset = reginfo->ramp_reg_offset;
> + int i, ret, index;
> + struct lmu_bl_reg_data regdata;
> +
> + for (i = BL_RAMP_UP; i <= BL_RAMP_DOWN; i++) {
> + index = ti_lmu_bl_convert_ramp_to_index(lmu_bank, i);
> + if (index > 0) {
> + if (!reginfo->ramp)
> + break;
> +
> + regdata = reginfo->ramp[i];
> + if (lmu_bank->bank_id != 0)
> + regdata.val += offset;
> +
> + /* regdata.val is shift bit */
> + ret = regmap_update_bits(regmap, regdata.reg,
> + regdata.mask,
> + index << regdata.val);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_configure(struct ti_lmu_bank *lmu_bank)
> +{
> + int ret;
> +
> + ret = ti_lmu_bl_check_channel(lmu_bank);
> + if (ret)
> + return ret;
> +
> + ret = ti_lmu_bl_create_channel(lmu_bank);
> + if (ret)
> + return ret;
> +
> + ret = ti_lmu_bl_update_ctrl_mode(lmu_bank);
> + if (ret)
> + return ret;
> +
> + return ti_lmu_bl_set_ramp(lmu_bank);
> +}
> +
> +static int ti_lmu_bl_register_backlight(struct ti_lmu_bank *lmu_bank)
> +{
> + struct backlight_device *bl_dev;
> + struct backlight_properties props;
> +
> + if (lmu_bank->type != TI_LMU_BL)
> + return -EINVAL;
> +
> + memset(&props, 0, sizeof(struct backlight_properties));
> + props.type = BACKLIGHT_PLATFORM;
> + props.brightness = lmu_bank->default_brightness;
> + props.max_brightness = lmu_bank->cfg->max_brightness;
> +
> + bl_dev = devm_backlight_device_register(lmu_bank->dev, lmu_bank->label,
> + lmu_bank->dev, lmu_bank,
> + &lmu_backlight_ops, &props);
> + if (IS_ERR(bl_dev))
> + return PTR_ERR(bl_dev);
> +
> + lmu_bank->backlight = bl_dev;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_register_led(struct ti_lmu_bank *lmu_bank)
> +{
> + int err;
> +
> + if (lmu_bank->type != TI_LMU_LED)
> + return -EINVAL;
> +
> + lmu_bank->led = devm_kzalloc(lmu_bank->dev, sizeof(*lmu_bank->led),
> + GFP_KERNEL);
> + if (!lmu_bank->led)
> + return -ENOMEM;
> +
> + lmu_bank->led->name = lmu_bank->label;
> + lmu_bank->led->max_brightness = lmu_bank->cfg->max_brightness;
> + lmu_bank->led->brightness_set_blocking =
> + ti_lmu_bl_set_led_blocking;
> +
> + err = devm_led_classdev_register(lmu_bank->dev, lmu_bank->led);
> + if (err)
> + return err;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
> +{
> + switch (lmu_bank->type) {
> + case TI_LMU_BL:
> + return ti_lmu_bl_register_backlight(lmu_bank);
> + case TI_LMU_LED:
> + return ti_lmu_bl_register_led(lmu_bank);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int setup_of_node(struct platform_device *pdev)
> +{
> + struct device_node *parent_node = pdev->dev.parent->of_node;
> + char *name;
> +
> + if (!parent_node)
> + return 0;
> +
> + name = kasprintf(GFP_KERNEL, "bank%d", pdev->id);
> + if (!name)
> + return -ENOMEM;
> +
> + pdev->dev.of_node = of_get_child_by_name(parent_node, name);
> + kfree(name);
> +
> + if (!pdev->dev.of_node)
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_parse_led_sources(struct device *dev)
> +{
> + unsigned long mask = 0;
> + int ret;
> + int size, i;
> + u32 *leds;
> +
> + size = device_property_read_u32_array(dev, "ti,led-sources", NULL, 0);
> + if (size <= 0) {
> + dev_err(dev, "Missing or malformed property led-sources: %d\n",
> + size);
> + return size < 0 ? size : -EINVAL;
> + }
> +
> + leds = kmalloc_array(size, sizeof(u32), GFP_KERNEL);
> + if (!leds)
> + return -ENOMEM;
> +
> + ret = device_property_read_u32_array(dev, "ti,led-sources", leds, size);
> + if (ret) {
> + dev_err(dev, "Failed to read led-sources property: %d\n", ret);
> + goto out;
> + }
> +
> + for (i = 0; i < size; i++)
> + set_bit(leds[i], &mask);
> +
> + ret = mask;
> +
> +out:
> + kfree(leds);
> + return ret;
> +}
> +
> +static int ti_lmu_bl_init(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata =
> + lmu_bank->cfg->reginfo->init;
> + int num_init = lmu_bank->cfg->reginfo->num_init;
> + int i, ret;
> +
> + if (lmu_bank->lmu->backlight_initialized)
> + return 0;
> + lmu_bank->lmu->backlight_initialized = true;
> +
> + for (i = 0; regdata && i < num_init; i++) {
> + ret = regmap_update_bits(regmap, regdata->reg, regdata->mask,
> + regdata->val);
> + if (ret)
> + return ret;
> +
> + regdata++;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_reload(struct ti_lmu_bank *lmu_bank)
> +{
> + int err;
> +
> + ti_lmu_bl_init(lmu_bank);
> +
> + err = ti_lmu_bl_configure(lmu_bank);
> + if (err)
> + return err;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, lmu_bank->current_brightness);
> +}
> +
> +static int ti_lmu_bl_monitor_notifier(struct notifier_block *nb,
> + unsigned long action, void *unused)
> +{
> + struct ti_lmu_bank *lmu_bank = container_of(nb, struct ti_lmu_bank, nb);
> +
> + if (action == LMU_EVENT_MONITOR_DONE) {
> + if (ti_lmu_bl_reload(lmu_bank))
> + return NOTIFY_STOP;
> + }
> +
> + return NOTIFY_OK;
> +}
> +
> +static int ti_lmu_bl_probe(struct platform_device *pdev)
> +{
> + struct ti_lmu_bank *lmu_bank;
> + int err;
> +
> + err = setup_of_node(pdev);
> + if (err)
> + return err;
> +
> + lmu_bank = devm_kzalloc(&pdev->dev, sizeof(*lmu_bank), GFP_KERNEL);
> + if (!lmu_bank)
> + return -ENOMEM;
> + lmu_bank->dev = &pdev->dev;
> + dev_set_drvdata(&pdev->dev, lmu_bank);
> +
> + err = device_property_read_string(&pdev->dev, "label",
> + &lmu_bank->label);
> + if (err)
> + return err;
> +
> + lmu_bank->type = TI_LMU_BL;
> + if (!strcmp(lmu_bank->label, "keyboard")) {
> + lmu_bank->type = TI_LMU_LED;
> + lmu_bank->label = "kbd_backlight";
> + }
> +
> + lmu_bank->leds = ti_lmu_parse_led_sources(&pdev->dev);
> + if (lmu_bank->leds < 0)
> + return lmu_bank->leds;
> + else if (lmu_bank->leds == 0)
> + return -EINVAL;
> +
> + device_property_read_u32(&pdev->dev, "default-brightness-level",
> + &lmu_bank->default_brightness);
> + device_property_read_u32(&pdev->dev, "ti,ramp-up-ms",
> + &lmu_bank->ramp_up_msec);
> + device_property_read_u32(&pdev->dev, "ti,ramp-down-ms",
> + &lmu_bank->ramp_down_msec);
> + device_property_read_u32(&pdev->dev, "pwm-period",
> + &lmu_bank->pwm_period);
> +
> + if (lmu_bank->pwm_period > 0)
> + lmu_bank->mode = BL_PWM_BASED;
> + else
> + lmu_bank->mode = BL_REGISTER_BASED;
> +
> + lmu_bank->lmu = dev_get_drvdata(pdev->dev.parent);
> + lmu_bank->cfg = &lmu_bl_cfg[lmu_bank->lmu->id];
> + lmu_bank->bank_id = pdev->id;
> +
> + ti_lmu_bl_init(lmu_bank);
> +
> + err = ti_lmu_bl_configure(lmu_bank);
> + if (err)
> + return err;
> +
> + err = ti_lmu_bl_add_device(lmu_bank);
> + if (err)
> + return err;
> +
> + err = ti_lmu_bl_set_brightness(lmu_bank,
> + lmu_bank->default_brightness);
> + if (err)
> + return err;
> +
> + /*
> + * Notifier callback is required because backlight device needs
> + * reconfiguration after fault detection procedure is done by
> + * ti-lmu-fault-monitor driver.
> + */
> + if (lmu_bank->cfg->fault_monitor_used) {
> + lmu_bank->nb.notifier_call = ti_lmu_bl_monitor_notifier;
> + err = blocking_notifier_chain_register(&lmu_bank->lmu->notifier,
> + &lmu_bank->nb);
> + if (err)
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_remove(struct platform_device *pdev)
> +{
> + struct ti_lmu_bank *lmu_bank = platform_get_drvdata(pdev);
> +
> + if (lmu_bank->cfg->fault_monitor_used)
> + blocking_notifier_chain_unregister(&lmu_bank->lmu->notifier,
> + &lmu_bank->nb);
> +
> + ti_lmu_bl_set_brightness(lmu_bank, 0);
> +
> + return 0;
> +}
> +
> +static struct platform_driver ti_lmu_bl_driver = {
> + .probe = ti_lmu_bl_probe,
> + .remove = ti_lmu_bl_remove,
> + .driver = {
> + .name = "ti-lmu-led-backlight",
> + },
> +};
> +module_platform_driver(ti_lmu_bl_driver)
> +
> +MODULE_DESCRIPTION("TI LMU Backlight LED Driver");
> +MODULE_AUTHOR("Sebastian Reichel");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:ti-lmu-led-backlight");
> diff --git a/drivers/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 <milo.kim@ti.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include "ti-lmu-backlight-data.h"
> +
> +/* LM3532 */
> +static const struct lmu_bl_reg_data lm3532_init_data[] = {
> + { LM3532_REG_ZONE_CFG_A, LM3532_ZONE_MASK, LM3532_ZONE_0 },
> + { LM3532_REG_ZONE_CFG_B, LM3532_ZONE_MASK, LM3532_ZONE_1 },
> + { LM3532_REG_ZONE_CFG_C, LM3532_ZONE_MASK, LM3532_ZONE_2 },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_channel_data[] = {
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED1_CFG_MASK,
> + LM3532_ILED1_CFG_SHIFT },
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED2_CFG_MASK,
> + LM3532_ILED2_CFG_SHIFT },
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED3_CFG_MASK,
> + LM3532_ILED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_mode_data[] = {
> + { LM3532_REG_PWM_A_CFG, LM3532_PWM_A_MASK, LM3532_PWM_ZONE_0 },
> + { LM3532_REG_PWM_B_CFG, LM3532_PWM_B_MASK, LM3532_PWM_ZONE_1 },
> + { LM3532_REG_PWM_C_CFG, LM3532_PWM_C_MASK, LM3532_PWM_ZONE_2 },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_ramp_data[] = {
> + { LM3532_REG_RAMPUP, LM3532_RAMPUP_MASK, LM3532_RAMPUP_SHIFT },
> + { LM3532_REG_RAMPDN, LM3532_RAMPDN_MASK, LM3532_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3532_enable_reg = LM3532_REG_ENABLE;
> +
> +static u8 lm3532_brightness_regs[] = {
> + LM3532_REG_BRT_A,
> + LM3532_REG_BRT_B,
> + LM3532_REG_BRT_C,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3532_reg_info = {
> + .init = lm3532_init_data,
> + .num_init = ARRAY_SIZE(lm3532_init_data),
> + .channel = lm3532_channel_data,
> + .mode = lm3532_mode_data,
> + .ramp = lm3532_ramp_data,
> + .enable = &lm3532_enable_reg,
> + .brightness_msb = lm3532_brightness_regs,
> +};
> +
> +/* LM3631 */
> +static const struct lmu_bl_reg_data lm3631_init_data[] = {
> + { LM3631_REG_BRT_MODE, LM3631_MODE_MASK, LM3631_DEFAULT_MODE },
> + { LM3631_REG_BL_CFG, LM3631_MAP_MASK, LM3631_EXPONENTIAL_MAP },
> +};
> +
> +static const struct lmu_bl_reg_data lm3631_channel_data[] = {
> + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_SINGLE_CHANNEL },
> + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_DUAL_CHANNEL },
> +};
> +
> +static const struct lmu_bl_reg_data lm3631_ramp_data[] = {
> + { LM3631_REG_SLOPE, LM3631_SLOPE_MASK, LM3631_SLOPE_SHIFT },
> +};
> +
> +static u8 lm3631_enable_reg = LM3631_REG_DEVCTRL;
> +static u8 lm3631_brightness_msb_reg = LM3631_REG_BRT_MSB;
> +static u8 lm3631_brightness_lsb_reg = LM3631_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3631_reg_info = {
> + .init = lm3631_init_data,
> + .num_init = ARRAY_SIZE(lm3631_init_data),
> + .channel = lm3631_channel_data,
> + .ramp = lm3631_ramp_data,
> + .enable = &lm3631_enable_reg,
> + .brightness_msb = &lm3631_brightness_msb_reg,
> + .brightness_lsb = &lm3631_brightness_lsb_reg,
> +};
> +
> +/* LM3632 */
> +static const struct lmu_bl_reg_data lm3632_init_data[] = {
> + { LM3632_REG_CONFIG1, LM3632_OVP_MASK, LM3632_OVP_25V },
> + { LM3632_REG_CONFIG2, LM3632_SWFREQ_MASK, LM3632_SWFREQ_1MHZ },
> +};
> +
> +static const struct lmu_bl_reg_data lm3632_channel_data[] = {
> + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_SINGLE_CHANNEL },
> + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_DUAL_CHANNEL },
> +};
> +
> +static const struct lmu_bl_reg_data lm3632_mode_data[] = {
> + { LM3632_REG_IO_CTRL, LM3632_PWM_MASK, LM3632_PWM_MODE },
> +};
> +
> +static u8 lm3632_enable_reg = LM3632_REG_ENABLE;
> +static u8 lm3632_brightness_msb_reg = LM3632_REG_BRT_MSB;
> +static u8 lm3632_brightness_lsb_reg = LM3632_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3632_reg_info = {
> + .init = lm3632_init_data,
> + .num_init = ARRAY_SIZE(lm3632_init_data),
> + .channel = lm3632_channel_data,
> + .mode = lm3632_mode_data,
> + .enable = &lm3632_enable_reg,
> + .brightness_msb = &lm3632_brightness_msb_reg,
> + .brightness_lsb = &lm3632_brightness_lsb_reg,
> +};
> +
> +/* LM3633 */
> +static const struct lmu_bl_reg_data lm3633_init_data[] = {
> + { LM3633_REG_BOOST_CFG, LM3633_OVP_MASK, LM3633_OVP_40V },
> + { LM3633_REG_BL_RAMP_CONF, LM3633_BL_RAMP_MASK, LM3633_BL_RAMP_EACH },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_channel_data[] = {
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED1_CFG_MASK,
> + LM3633_HVLED1_CFG_SHIFT },
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED2_CFG_MASK,
> + LM3633_HVLED2_CFG_SHIFT },
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED3_CFG_MASK,
> + LM3633_HVLED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_mode_data[] = {
> + { LM3633_REG_PWM_CFG, LM3633_PWM_A_MASK, LM3633_PWM_A_MASK },
> + { LM3633_REG_PWM_CFG, LM3633_PWM_B_MASK, LM3633_PWM_B_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_ramp_data[] = {
> + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPUP_MASK, LM3633_BL_RAMPUP_SHIFT },
> + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPDN_MASK, LM3633_BL_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3633_enable_reg = LM3633_REG_ENABLE;
> +
> +static u8 lm3633_brightness_msb_regs[] = {
> + LM3633_REG_BRT_HVLED_A_MSB,
> + LM3633_REG_BRT_HVLED_B_MSB,
> +};
> +
> +static u8 lm3633_brightness_lsb_regs[] = {
> + LM3633_REG_BRT_HVLED_A_LSB,
> + LM3633_REG_BRT_HVLED_B_LSB,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3633_reg_info = {
> + .init = lm3633_init_data,
> + .num_init = ARRAY_SIZE(lm3633_init_data),
> + .channel = lm3633_channel_data,
> + .mode = lm3633_mode_data,
> + .ramp = lm3633_ramp_data,
> + .ramp_reg_offset = 1, /* For LM3633_REG_BL1_RAMPUP/DN */
> + .enable = &lm3633_enable_reg,
> + .brightness_msb = lm3633_brightness_msb_regs,
> + .brightness_lsb = lm3633_brightness_lsb_regs,
> +};
> +
> +/* LM3695 */
> +static const struct lmu_bl_reg_data lm3695_init_data[] = {
> + { LM3695_REG_GP, LM3695_BRT_RW_MASK, LM3695_BRT_RW_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3695_channel_data[] = {
> + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_SINGLE_CHANNEL },
> + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_DUAL_CHANNEL },
> +};
> +
> +static u8 lm3695_enable_reg = LM3695_REG_GP;
> +static u8 lm3695_brightness_msb_reg = LM3695_REG_BRT_MSB;
> +static u8 lm3695_brightness_lsb_reg = LM3695_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3695_reg_info = {
> + .init = lm3695_init_data,
> + .num_init = ARRAY_SIZE(lm3695_init_data),
> + .channel = lm3695_channel_data,
> + .enable = &lm3695_enable_reg,
> + .enable_usec = 600,
> + .brightness_msb = &lm3695_brightness_msb_reg,
> + .brightness_lsb = &lm3695_brightness_lsb_reg,
> +};
> +
> +/* LM3697 */
> +static const struct lmu_bl_reg_data lm3697_init_data[] = {
> + { LM3697_REG_RAMP_CONF, LM3697_RAMP_MASK, LM3697_RAMP_EACH },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_channel_data[] = {
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED1_CFG_MASK,
> + LM3697_HVLED1_CFG_SHIFT },
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED2_CFG_MASK,
> + LM3697_HVLED2_CFG_SHIFT },
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED3_CFG_MASK,
> + LM3697_HVLED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_mode_data[] = {
> + { LM3697_REG_PWM_CFG, LM3697_PWM_A_MASK, LM3697_PWM_A_MASK },
> + { LM3697_REG_PWM_CFG, LM3697_PWM_B_MASK, LM3697_PWM_B_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_ramp_data[] = {
> + { LM3697_REG_BL0_RAMP, LM3697_RAMPUP_MASK, LM3697_RAMPUP_SHIFT },
> + { LM3697_REG_BL0_RAMP, LM3697_RAMPDN_MASK, LM3697_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3697_enable_reg = LM3697_REG_ENABLE;
> +
> +static u8 lm3697_brightness_msb_regs[] = {
> + LM3697_REG_BRT_A_MSB,
> + LM3697_REG_BRT_B_MSB,
> +};
> +
> +static u8 lm3697_brightness_lsb_regs[] = {
> + LM3697_REG_BRT_A_LSB,
> + LM3697_REG_BRT_B_LSB,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3697_reg_info = {
> + .init = lm3697_init_data,
> + .num_init = ARRAY_SIZE(lm3697_init_data),
> + .channel = lm3697_channel_data,
> + .mode = lm3697_mode_data,
> + .ramp = lm3697_ramp_data,
> + .ramp_reg_offset = 1, /* For LM3697_REG_BL1_RAMPUP/DN */
> + .enable = &lm3697_enable_reg,
> + .brightness_msb = lm3697_brightness_msb_regs,
> + .brightness_lsb = lm3697_brightness_lsb_regs,
> +};
> +
> +static int lm3532_ramp_table[] = { 0, 1, 2, 4, 8, 16, 32, 65 };
> +
> +static int lm3631_ramp_table[] = {
> + 0, 1, 2, 5, 10, 20, 50, 100,
> + 250, 500, 750, 1000, 1500, 2000, 3000, 4000,
> +};
> +
> +static int common_ramp_table[] = {
> + 2, 250, 500, 1000, 2000, 4000, 8000, 16000,
> +};
> +
> +#define LM3532_MAX_CHANNELS 3
> +#define LM3631_MAX_CHANNELS 2
> +#define LM3632_MAX_CHANNELS 2
> +#define LM3633_MAX_CHANNELS 3
> +#define LM3695_MAX_CHANNELS 2
> +#define LM3697_MAX_CHANNELS 3
> +
> +const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID] = {
> + {
> + .reginfo = &lm3532_reg_info,
> + .num_channels = LM3532_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_8BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + .ramp_table = lm3532_ramp_table,
> + .size_ramp = ARRAY_SIZE(lm3532_ramp_table),
> + },
> + {
> + .reginfo = &lm3631_reg_info,
> + .num_channels = LM3631_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_ONLY,
> + .ramp_table = lm3631_ramp_table,
> + .size_ramp = ARRAY_SIZE(lm3631_ramp_table),
> + },
> + {
> + .reginfo = &lm3632_reg_info,
> + .num_channels = LM3632_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_ONLY,
> + },
> + {
> + .reginfo = &lm3633_reg_info,
> + .num_channels = LM3633_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_MAX_BRT,
> + .ramp_table = common_ramp_table,
> + .size_ramp = ARRAY_SIZE(common_ramp_table),
> + .fault_monitor_used = true,
> + },
> + {
> + .reginfo = &lm3695_reg_info,
> + .num_channels = LM3695_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + },
> + {
> + .reginfo = &lm3697_reg_info,
> + .num_channels = LM3697_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + .ramp_table = common_ramp_table,
> + .size_ramp = ARRAY_SIZE(common_ramp_table),
> + .fault_monitor_used = true,
> + },
> +};
> diff --git a/drivers/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 <milo.kim@ti.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#ifndef __TI_LMU_BACKLIGHT_H__
> +#define __TI_LMU_BACKLIGHT_H__
> +
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +
> +#define MAX_BRIGHTNESS_8BIT 255
> +#define MAX_BRIGHTNESS_11BIT 2047
> +
> +enum ti_lmu_bl_pwm_action {
> + /* Update PWM duty, no brightness register update is required */
> + UPDATE_PWM_ONLY,
> + /* Update not only duty but also brightness register */
> + UPDATE_PWM_AND_BRT_REGISTER,
> + /* Update max value in brightness registers */
> + UPDATE_MAX_BRT,
> +};
> +
> +struct lmu_bl_reg_data {
> + u8 reg;
> + u8 mask;
> + u8 val;
> +};
> +
> +/**
> + * struct ti_lmu_bl_reg
> + *
> + * @init: Device initialization registers
> + * @num_init: Numbers of initialization registers
> + * @channel: Backlight channel configuration registers
> + * @mode: Brightness control mode registers
> + * @ramp: Ramp registers for lighting effect
> + * @ramp_reg_offset: Ramp register offset.
> + * Only used for multiple ramp registers.
> + * @enable: Enable control register address
> + * @enable_usec: Delay time for updating enable register.
> + * Unit is microsecond.
> + * @brightness_msb: Brightness MSB(Upper 8 bits) registers.
> + * Concatenated with LSB in 11 bit dimming mode.
> + * In 8 bit dimming, only MSB is used.
> + * @brightness_lsb: Brightness LSB(Lower 3 bits) registers.
> + * Only valid in 11 bit dimming mode.
> + */
> +struct ti_lmu_bl_reg {
> + const struct lmu_bl_reg_data *init;
> + int num_init;
> + const struct lmu_bl_reg_data *channel;
> + const struct lmu_bl_reg_data *mode;
> + const struct lmu_bl_reg_data *ramp;
> + int ramp_reg_offset;
> + u8 *enable;
> + unsigned long enable_usec;
> + u8 *brightness_msb;
> + u8 *brightness_lsb;
> +};
> +
> +/**
> + * struct ti_lmu_bl_cfg
> + *
> + * @reginfo: Device register configuration
> + * @num_channels: Number of backlight channels
> + * @max_brightness: Max brightness value of backlight device
> + * @pwm_action: How to control brightness registers in PWM mode
> + * @ramp_table: [Optional] Ramp time table for lighting effect.
> + * It's used for searching approximate register index.
> + * @size_ramp: [Optional] Size of ramp table
> + * @fault_monitor_used: [Optional] Set true if the device needs to handle
> + * LMU fault monitor event.
> + *
> + * This structure is used for device specific data configuration.
> + */
> +struct ti_lmu_bl_cfg {
> + const struct ti_lmu_bl_reg *reginfo;
> + int num_channels;
> + int max_brightness;
> + enum ti_lmu_bl_pwm_action pwm_action;
> + int *ramp_table;
> + int size_ramp;
> + bool fault_monitor_used;
> +};
> +
> +extern const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID];
> +#endif
> --
> 2.16.2
>
^ permalink raw reply [flat|nested] 62+ messages in thread* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
2018-04-04 14:57 ` Daniel Thompson
@ 2018-04-09 16:14 ` Sebastian Reichel
-1 siblings, 0 replies; 62+ messages in thread
From: Sebastian Reichel @ 2018-04-09 16:14 UTC (permalink / raw)
To: Daniel Thompson
Cc: Milo Kim, Lee Jones, Rob Herring, Tony Lindgren, Jingoo Han,
Mark Rutland, linux-kernel, linux-fbdev, devicetree, linux-omap
[-- Attachment #1: Type: text/plain, Size: 9196 bytes --]
Hi Daniel,
On Wed, Apr 04, 2018 at 03:57:39PM +0100, Daniel Thompson wrote:
> On Fri, Mar 30, 2018 at 07:24:12PM +0200, Sebastian Reichel wrote:
> > This adds backlight support for the following TI LMU
> > chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
> >
> > Signed-off-by: Milo Kim <milo.kim@ti.com>
> > [add LED subsystem support for keyboard backlight and rework DT
>
> Milo's mail has be bouncing for a very long time now. Did they really
> sign off this code or is this intended to be an authorship credit?
I took over his patches from ~ a year ago and reworked them.
> > binding according to Rob Herrings feedback]
> > Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> > ---
> > drivers/video/backlight/Kconfig | 7 +
> > drivers/video/backlight/Makefile | 3 +
> > drivers/video/backlight/ti-lmu-backlight-core.c | 666 ++++++++++++++++++++++++
> > drivers/video/backlight/ti-lmu-backlight-data.c | 304 +++++++++++
> > drivers/video/backlight/ti-lmu-backlight-data.h | 95 ++++
> > 5 files changed, 1075 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 5d2d0d7e8100..27e6c5a0add8 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 19da71d518bf..a1132d3dfd4c 100644
> > --- a/drivers/video/backlight/Makefile
> > +++ b/drivers/video/backlight/Makefile
> > @@ -53,6 +53,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..a6099581edd7
> > --- /dev/null
> > +++ b/drivers/video/backlight/ti-lmu-backlight-core.c
> > @@ -0,0 +1,666 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright 2015 Texas Instruments
> > + * Copyright 2018 Sebastian Reichel
> > + *
> > + * TI LMU Backlight driver, based on previous work from
> > + * Milo Kim <milo.kim@ti.com>
> > + */
> > +
> > +#include <linux/backlight.h>
> > +#include <linux/bitops.h>
> > +#include <linux/device.h>
> > +#include <linux/delay.h>
> > +#include <linux/err.h>
> > +#include <linux/leds.h>
> > +#include <linux/mfd/ti-lmu.h>
> > +#include <linux/mfd/ti-lmu-register.h>
> > +#include <linux/module.h>
> > +#include <linux/notifier.h>
> > +#include <linux/of_device.h>
> > +#include <linux/platform_device.h>
> > +
> > +#include "ti-lmu-backlight-data.h"
> > +
> > +enum ti_lmu_bl_ctrl_mode {
> > + BL_REGISTER_BASED,
> > + BL_PWM_BASED,
> > +};
> > +
> > +enum ti_lmu_bl_type {
> > + TI_LMU_BL, /* backlight userspace interface */
> > + TI_LMU_LED, /* led userspace interface */
> > +};
> > +
> > +enum ti_lmu_bl_ramp_mode {
> > + BL_RAMP_UP,
> > + BL_RAMP_DOWN,
> > +};
> > +
> > +#define DEFAULT_PWM_NAME "lmu-backlight"
> > +#define NUM_DUAL_CHANNEL 2
> > +#define LMU_BACKLIGHT_DUAL_CHANNEL_USED (BIT(0) | BIT(1))
> > +#define LMU_BACKLIGHT_11BIT_LSB_MASK (BIT(0) | BIT(1) | BIT(2))
> > +#define LMU_BACKLIGHT_11BIT_MSB_SHIFT 3
> > +
> > +struct ti_lmu_bank {
> > + struct device *dev;
> > + int bank_id;
> > + const struct ti_lmu_bl_cfg *cfg;
> > + struct ti_lmu *lmu;
> > + const char *label;
> > + int leds;
> > + int current_brightness;
> > + u32 default_brightness;
> > + u32 ramp_up_msec;
> > + u32 ramp_down_msec;
> > + u32 pwm_period;
> > + enum ti_lmu_bl_ctrl_mode mode;
> > + enum ti_lmu_bl_type type;
> > +
> > + struct notifier_block nb;
> > +
> > + struct backlight_device *backlight;
> > + struct led_classdev *led;
> > +};
> > +
> > +static int ti_lmu_bl_enable(struct ti_lmu_bank *lmu_bank, bool enable)
> > +{
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + unsigned long enable_time = lmu_bank->cfg->reginfo->enable_usec;
> > + u8 *reg = lmu_bank->cfg->reginfo->enable;
> > + u8 mask = BIT(lmu_bank->bank_id);
> > + u8 val = (enable == true) ? mask : 0;
> > + int ret;
> > +
> > + if (!reg)
> > + return -EINVAL;
> > +
> > + ret = regmap_update_bits(regmap, *reg, mask, val);
> > + if (ret)
> > + return ret;
> > +
> > + if (enable_time > 0)
> > + usleep_range(enable_time, enable_time + 100);
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_update_brightness_register(struct ti_lmu_bank *lmu_bank,
> > + int brightness)
> > +{
> > + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> > + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + u8 reg, val;
> > + int ret;
> > +
> > + /*
> > + * Brightness register update
> > + *
> > + * 11 bit dimming: update LSB bits and write MSB byte.
> > + * MSB brightness should be shifted.
> > + * 8 bit dimming: write MSB byte.
> > + */
> > + if (cfg->max_brightness == MAX_BRIGHTNESS_11BIT) {
> > + reg = reginfo->brightness_lsb[lmu_bank->bank_id];
> > + ret = regmap_update_bits(regmap, reg,
> > + LMU_BACKLIGHT_11BIT_LSB_MASK,
> > + brightness);
> > + if (ret)
> > + return ret;
> > +
> > + val = brightness >> LMU_BACKLIGHT_11BIT_MSB_SHIFT;
> > + } else {
> > + val = brightness;
> > + }
> > +
> > + reg = reginfo->brightness_msb[lmu_bank->bank_id];
> > + return regmap_write(regmap, reg, val);
> > +}
> > +
> > +static int ti_lmu_bl_pwm_ctrl(struct ti_lmu_bank *lmu_bank, int brightness)
> > +{
> > + int max_brightness = lmu_bank->cfg->max_brightness;
> > + struct pwm_state state = { };
> > + int ret;
> > +
> > + if (!lmu_bank->lmu->pwm) {
> > + dev_err(lmu_bank->dev, "Missing PWM device!\n");
> > + return -ENODEV;
> > + }
> > +
> > + pwm_init_state(lmu_bank->lmu->pwm, &state);
> > + state.period = lmu_bank->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_bank->lmu->pwm, &state);
> > + if (ret)
> > + dev_err(lmu_bank->dev, "Failed to configure PWM: %d", ret);
> > +
> > + return ret;
> > +}
> > +
> > +static int ti_lmu_bl_set_brightness(struct ti_lmu_bank *lmu_bank,
> > + int brightness)
> > +{
> > + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> > + bool enable = brightness > 0;
> > + int ret;
> > +
> > + ret = ti_lmu_bl_enable(lmu_bank, enable);
> > + if (ret)
> > + return ret;
> > +
> > + if (lmu_bank->mode == BL_PWM_BASED) {
> > + ti_lmu_bl_pwm_ctrl(lmu_bank, 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.
> > + */
>
> This comment could do with a little expansion (I assume the bank's
> brightness register means something different when in PWM mode but the
> flow of the code is tricky to read).
I will consult the manual and see how exactly the PWM stuff
is supposed to work. The platform I need this driver for
(Motorola Droid 4) does not have/use the PWM feature.
> > + if (brightness > 0)
>
> Isn't this "enable"?
Yes, good catch!
> > + brightness = MAX_BRIGHTNESS_11BIT;
> > + else
> > + brightness = 0;
> > + break;
> > + default:
>
> case UPDATE_PWM_AND_BRT_REGISTER:
ok.
>
> > + break;
> > + }
> > + }
> > +
> > + lmu_bank->current_brightness = brightness;
> > +
> > + return ti_lmu_bl_update_brightness_register(lmu_bank, brightness);
> > +}
> > [...]
Thanks for the review.
-- Sebastian
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 62+ messages in thread* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
@ 2018-04-09 16:14 ` Sebastian Reichel
0 siblings, 0 replies; 62+ messages in thread
From: Sebastian Reichel @ 2018-04-09 16:14 UTC (permalink / raw)
To: Daniel Thompson
Cc: Milo Kim, Lee Jones, Rob Herring, Tony Lindgren, Jingoo Han,
Mark Rutland, linux-kernel, linux-fbdev, devicetree, linux-omap
[-- Attachment #1: Type: text/plain, Size: 9196 bytes --]
Hi Daniel,
On Wed, Apr 04, 2018 at 03:57:39PM +0100, Daniel Thompson wrote:
> On Fri, Mar 30, 2018 at 07:24:12PM +0200, Sebastian Reichel wrote:
> > This adds backlight support for the following TI LMU
> > chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
> >
> > Signed-off-by: Milo Kim <milo.kim@ti.com>
> > [add LED subsystem support for keyboard backlight and rework DT
>
> Milo's mail has be bouncing for a very long time now. Did they really
> sign off this code or is this intended to be an authorship credit?
I took over his patches from ~ a year ago and reworked them.
> > binding according to Rob Herrings feedback]
> > Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> > ---
> > drivers/video/backlight/Kconfig | 7 +
> > drivers/video/backlight/Makefile | 3 +
> > drivers/video/backlight/ti-lmu-backlight-core.c | 666 ++++++++++++++++++++++++
> > drivers/video/backlight/ti-lmu-backlight-data.c | 304 +++++++++++
> > drivers/video/backlight/ti-lmu-backlight-data.h | 95 ++++
> > 5 files changed, 1075 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 5d2d0d7e8100..27e6c5a0add8 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 19da71d518bf..a1132d3dfd4c 100644
> > --- a/drivers/video/backlight/Makefile
> > +++ b/drivers/video/backlight/Makefile
> > @@ -53,6 +53,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..a6099581edd7
> > --- /dev/null
> > +++ b/drivers/video/backlight/ti-lmu-backlight-core.c
> > @@ -0,0 +1,666 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright 2015 Texas Instruments
> > + * Copyright 2018 Sebastian Reichel
> > + *
> > + * TI LMU Backlight driver, based on previous work from
> > + * Milo Kim <milo.kim@ti.com>
> > + */
> > +
> > +#include <linux/backlight.h>
> > +#include <linux/bitops.h>
> > +#include <linux/device.h>
> > +#include <linux/delay.h>
> > +#include <linux/err.h>
> > +#include <linux/leds.h>
> > +#include <linux/mfd/ti-lmu.h>
> > +#include <linux/mfd/ti-lmu-register.h>
> > +#include <linux/module.h>
> > +#include <linux/notifier.h>
> > +#include <linux/of_device.h>
> > +#include <linux/platform_device.h>
> > +
> > +#include "ti-lmu-backlight-data.h"
> > +
> > +enum ti_lmu_bl_ctrl_mode {
> > + BL_REGISTER_BASED,
> > + BL_PWM_BASED,
> > +};
> > +
> > +enum ti_lmu_bl_type {
> > + TI_LMU_BL, /* backlight userspace interface */
> > + TI_LMU_LED, /* led userspace interface */
> > +};
> > +
> > +enum ti_lmu_bl_ramp_mode {
> > + BL_RAMP_UP,
> > + BL_RAMP_DOWN,
> > +};
> > +
> > +#define DEFAULT_PWM_NAME "lmu-backlight"
> > +#define NUM_DUAL_CHANNEL 2
> > +#define LMU_BACKLIGHT_DUAL_CHANNEL_USED (BIT(0) | BIT(1))
> > +#define LMU_BACKLIGHT_11BIT_LSB_MASK (BIT(0) | BIT(1) | BIT(2))
> > +#define LMU_BACKLIGHT_11BIT_MSB_SHIFT 3
> > +
> > +struct ti_lmu_bank {
> > + struct device *dev;
> > + int bank_id;
> > + const struct ti_lmu_bl_cfg *cfg;
> > + struct ti_lmu *lmu;
> > + const char *label;
> > + int leds;
> > + int current_brightness;
> > + u32 default_brightness;
> > + u32 ramp_up_msec;
> > + u32 ramp_down_msec;
> > + u32 pwm_period;
> > + enum ti_lmu_bl_ctrl_mode mode;
> > + enum ti_lmu_bl_type type;
> > +
> > + struct notifier_block nb;
> > +
> > + struct backlight_device *backlight;
> > + struct led_classdev *led;
> > +};
> > +
> > +static int ti_lmu_bl_enable(struct ti_lmu_bank *lmu_bank, bool enable)
> > +{
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + unsigned long enable_time = lmu_bank->cfg->reginfo->enable_usec;
> > + u8 *reg = lmu_bank->cfg->reginfo->enable;
> > + u8 mask = BIT(lmu_bank->bank_id);
> > + u8 val = (enable == true) ? mask : 0;
> > + int ret;
> > +
> > + if (!reg)
> > + return -EINVAL;
> > +
> > + ret = regmap_update_bits(regmap, *reg, mask, val);
> > + if (ret)
> > + return ret;
> > +
> > + if (enable_time > 0)
> > + usleep_range(enable_time, enable_time + 100);
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_update_brightness_register(struct ti_lmu_bank *lmu_bank,
> > + int brightness)
> > +{
> > + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> > + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + u8 reg, val;
> > + int ret;
> > +
> > + /*
> > + * Brightness register update
> > + *
> > + * 11 bit dimming: update LSB bits and write MSB byte.
> > + * MSB brightness should be shifted.
> > + * 8 bit dimming: write MSB byte.
> > + */
> > + if (cfg->max_brightness == MAX_BRIGHTNESS_11BIT) {
> > + reg = reginfo->brightness_lsb[lmu_bank->bank_id];
> > + ret = regmap_update_bits(regmap, reg,
> > + LMU_BACKLIGHT_11BIT_LSB_MASK,
> > + brightness);
> > + if (ret)
> > + return ret;
> > +
> > + val = brightness >> LMU_BACKLIGHT_11BIT_MSB_SHIFT;
> > + } else {
> > + val = brightness;
> > + }
> > +
> > + reg = reginfo->brightness_msb[lmu_bank->bank_id];
> > + return regmap_write(regmap, reg, val);
> > +}
> > +
> > +static int ti_lmu_bl_pwm_ctrl(struct ti_lmu_bank *lmu_bank, int brightness)
> > +{
> > + int max_brightness = lmu_bank->cfg->max_brightness;
> > + struct pwm_state state = { };
> > + int ret;
> > +
> > + if (!lmu_bank->lmu->pwm) {
> > + dev_err(lmu_bank->dev, "Missing PWM device!\n");
> > + return -ENODEV;
> > + }
> > +
> > + pwm_init_state(lmu_bank->lmu->pwm, &state);
> > + state.period = lmu_bank->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_bank->lmu->pwm, &state);
> > + if (ret)
> > + dev_err(lmu_bank->dev, "Failed to configure PWM: %d", ret);
> > +
> > + return ret;
> > +}
> > +
> > +static int ti_lmu_bl_set_brightness(struct ti_lmu_bank *lmu_bank,
> > + int brightness)
> > +{
> > + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> > + bool enable = brightness > 0;
> > + int ret;
> > +
> > + ret = ti_lmu_bl_enable(lmu_bank, enable);
> > + if (ret)
> > + return ret;
> > +
> > + if (lmu_bank->mode == BL_PWM_BASED) {
> > + ti_lmu_bl_pwm_ctrl(lmu_bank, 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.
> > + */
>
> This comment could do with a little expansion (I assume the bank's
> brightness register means something different when in PWM mode but the
> flow of the code is tricky to read).
I will consult the manual and see how exactly the PWM stuff
is supposed to work. The platform I need this driver for
(Motorola Droid 4) does not have/use the PWM feature.
> > + if (brightness > 0)
>
> Isn't this "enable"?
Yes, good catch!
> > + brightness = MAX_BRIGHTNESS_11BIT;
> > + else
> > + brightness = 0;
> > + break;
> > + default:
>
> case UPDATE_PWM_AND_BRT_REGISTER:
ok.
>
> > + break;
> > + }
> > + }
> > +
> > + lmu_bank->current_brightness = brightness;
> > +
> > + return ti_lmu_bl_update_brightness_register(lmu_bank, brightness);
> > +}
> > [...]
Thanks for the review.
-- Sebastian
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
2018-03-30 17:24 ` Sebastian Reichel
(?)
@ 2018-04-04 18:30 ` Dan Murphy
-1 siblings, 0 replies; 62+ messages in thread
From: Dan Murphy @ 2018-04-04 18:30 UTC (permalink / raw)
To: Sebastian Reichel, Sebastian Reichel, Lee Jones, Daniel Thompson,
Rob Herring, Tony Lindgren
Cc: Jingoo Han, Mark Rutland, linux-kernel, linux-fbdev, devicetree,
linux-omap
Sebastian
-Milo Kim email is not valid
On 03/30/2018 12:24 PM, Sebastian Reichel wrote:
> This adds backlight support for the following TI LMU
> chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
>
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> [add LED subsystem support for keyboard backlight and rework DT
> binding according to Rob Herrings feedback]
> Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> ---
> drivers/video/backlight/Kconfig | 7 +
> drivers/video/backlight/Makefile | 3 +
> drivers/video/backlight/ti-lmu-backlight-core.c | 666 ++++++++++++++++++++++++
> drivers/video/backlight/ti-lmu-backlight-data.c | 304 +++++++++++
> drivers/video/backlight/ti-lmu-backlight-data.h | 95 ++++
> 5 files changed, 1075 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 5d2d0d7e8100..27e6c5a0add8 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
select REGMAP?
> + 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 19da71d518bf..a1132d3dfd4c 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -53,6 +53,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..a6099581edd7
> --- /dev/null
> +++ b/drivers/video/backlight/ti-lmu-backlight-core.c
> @@ -0,0 +1,666 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2015 Texas Instruments
> + * Copyright 2018 Sebastian Reichel
> + *
> + * TI LMU Backlight driver, based on previous work from
> + * Milo Kim <milo.kim@ti.com>
> + */
> +
> +#include <linux/backlight.h>
> +#include <linux/bitops.h>
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/leds.h>
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +
> +#include "ti-lmu-backlight-data.h"
> +
> +enum ti_lmu_bl_ctrl_mode {
> + BL_REGISTER_BASED,
> + BL_PWM_BASED,
> +};
> +
> +enum ti_lmu_bl_type {
> + TI_LMU_BL, /* backlight userspace interface */
> + TI_LMU_LED, /* led userspace interface */
> +};
> +
> +enum ti_lmu_bl_ramp_mode {
> + BL_RAMP_UP,
> + BL_RAMP_DOWN,
> +};
> +
> +#define DEFAULT_PWM_NAME "lmu-backlight"
I don't see this used.
> +#define NUM_DUAL_CHANNEL 2
> +#define LMU_BACKLIGHT_DUAL_CHANNEL_USED (BIT(0) | BIT(1))
> +#define LMU_BACKLIGHT_11BIT_LSB_MASK (BIT(0) | BIT(1) | BIT(2))
> +#define LMU_BACKLIGHT_11BIT_MSB_SHIFT 3
> +
Struct kerneldoc?
> +struct ti_lmu_bank {
> + struct device *dev;
> + int bank_id;
> + const struct ti_lmu_bl_cfg *cfg;
> + struct ti_lmu *lmu;
> + const char *label;
> + int leds;
> + int current_brightness;
> + u32 default_brightness;
> + u32 ramp_up_msec;
> + u32 ramp_down_msec;
> + u32 pwm_period;
> + enum ti_lmu_bl_ctrl_mode mode;
> + enum ti_lmu_bl_type type;
> +
> + struct notifier_block nb;
> +
> + struct backlight_device *backlight;
> + struct led_classdev *led;
> +};
> +
> +static int ti_lmu_bl_enable(struct ti_lmu_bank *lmu_bank, bool enable)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + unsigned long enable_time = lmu_bank->cfg->reginfo->enable_usec;
> + u8 *reg = lmu_bank->cfg->reginfo->enable;
> + u8 mask = BIT(lmu_bank->bank_id);
> + u8 val = (enable = true) ? mask : 0;
> + int ret;
> +
> + if (!reg)
> + return -EINVAL;
> +
> + ret = regmap_update_bits(regmap, *reg, mask, val);
> + if (ret)
> + return ret;
> +
> + if (enable_time > 0)
> + usleep_range(enable_time, enable_time + 100);
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_update_brightness_register(struct ti_lmu_bank *lmu_bank,
> + int brightness)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + u8 reg, val;
> + int ret;
> +
> + /*
> + * Brightness register update
> + *
> + * 11 bit dimming: update LSB bits and write MSB byte.
> + * MSB brightness should be shifted.
> + * 8 bit dimming: write MSB byte.
> + */
> + if (cfg->max_brightness = MAX_BRIGHTNESS_11BIT) {
> + reg = reginfo->brightness_lsb[lmu_bank->bank_id];
> + ret = regmap_update_bits(regmap, reg,
> + LMU_BACKLIGHT_11BIT_LSB_MASK,
> + brightness);
> + if (ret)
> + return ret;
> +
> + val = brightness >> LMU_BACKLIGHT_11BIT_MSB_SHIFT;
> + } else {
> + val = brightness;
> + }
> +
> + reg = reginfo->brightness_msb[lmu_bank->bank_id];
> + return regmap_write(regmap, reg, val);
> +}
> +
> +static int ti_lmu_bl_pwm_ctrl(struct ti_lmu_bank *lmu_bank, int brightness)
> +{
> + int max_brightness = lmu_bank->cfg->max_brightness;
> + struct pwm_state state = { };
> + int ret;
> +
> + if (!lmu_bank->lmu->pwm) {
> + dev_err(lmu_bank->dev, "Missing PWM device!\n");
> + return -ENODEV;
> + }
> +
> + pwm_init_state(lmu_bank->lmu->pwm, &state);
> + state.period = lmu_bank->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_bank->lmu->pwm, &state);
> + if (ret)
> + dev_err(lmu_bank->dev, "Failed to configure PWM: %d", ret);
> +
> + return ret;
> +}
> +
> +static int ti_lmu_bl_set_brightness(struct ti_lmu_bank *lmu_bank,
> + int brightness)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + bool enable = brightness > 0;
> + int ret;
> +
Is there anyway that update_status and set_led_blocking can be called at the same time causing
a race condition in this api?
> + ret = ti_lmu_bl_enable(lmu_bank, enable);
> + if (ret)
> + return ret;
> +
> + if (lmu_bank->mode = BL_PWM_BASED) {
> + ti_lmu_bl_pwm_ctrl(lmu_bank, 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;
> + }
> + }
> +
> + lmu_bank->current_brightness = brightness;
> +
> + return ti_lmu_bl_update_brightness_register(lmu_bank, brightness);
> +}
> +
> +static int ti_lmu_bl_update_status(struct backlight_device *bl_dev)
> +{
> + struct ti_lmu_bank *lmu_bank = bl_get_data(bl_dev);
> + int brightness = bl_dev->props.brightness;
> +
> + if (bl_dev->props.state & BL_CORE_SUSPENDED)
> + brightness = 0;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> +}
> +
> +static const struct backlight_ops lmu_backlight_ops = {
> + .options = BL_CORE_SUSPENDRESUME,
> + .update_status = ti_lmu_bl_update_status,
> +};
> +
> +static int ti_lmu_bl_set_led_blocking(struct led_classdev *ledc,
> + enum led_brightness value)
> +{
> + struct ti_lmu_bank *lmu_bank = dev_get_drvdata(ledc->dev->parent);
> + int brightness = value;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> +}
> +
> +static int ti_lmu_bl_check_channel(struct ti_lmu_bank *lmu_bank)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> +
> + if (!reginfo->brightness_msb)
> + return -EINVAL;
> +
> + if (cfg->max_brightness > MAX_BRIGHTNESS_8BIT) {
> + if (!reginfo->brightness_lsb)
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_create_channel(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata = lmu_bank->cfg->reginfo->channel;
> + int num_channels = lmu_bank->cfg->num_channels;
> + unsigned long led_sources = lmu_bank->leds;
> + int i, ret;
> + u8 shift;
> +
> + /*
> + * How to create backlight output channels:
> + * Check 'led_sources' bit and update registers.
> + *
> + * 1) Dual channel configuration
> + * The 1st register data is used for single channel.
> + * The 2nd register data is used for dual channel.
> + *
> + * 2) Multiple channel configuration
> + * Each register data is mapped to bank ID.
> + * Bit shift operation is defined in channel registers.
> + *
> + * Channel register data consists of address, mask, value.
> + */
> +
> + if (num_channels = NUM_DUAL_CHANNEL) {
> + if (led_sources = LMU_BACKLIGHT_DUAL_CHANNEL_USED)
> + regdata++;
> +
> + return regmap_update_bits(regmap, regdata->reg, regdata->mask,
> + regdata->val);
> + }
> +
> + for (i = 0; regdata && i < num_channels; i++) {
> + /*
> + * Note that the result of regdata->val is shift bit.
> + * The bank_id should be shifted for the channel configuration.
> + */
> + if (test_bit(i, &led_sources)) {
> + shift = regdata->val;
> + ret = regmap_update_bits(regmap, regdata->reg,
> + regdata->mask,
> + lmu_bank->bank_id << shift);
> + if (ret)
> + return ret;
> + }
> +
> + regdata++;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_update_ctrl_mode(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata > + lmu_bank->cfg->reginfo->mode + lmu_bank->bank_id;
> + u8 val = regdata->val;
> +
> + if (!regdata)
> + return 0;
> +
> + /*
> + * Update PWM configuration register.
> + * If the mode is register based, then clear the bit.
> + */
> + if (lmu_bank->mode != BL_PWM_BASED)
> + val = 0;
> +
> + return regmap_update_bits(regmap, regdata->reg, regdata->mask, val);
> +}
> +
> +static int ti_lmu_bl_convert_ramp_to_index(struct ti_lmu_bank *lmu_bank,
> + enum ti_lmu_bl_ramp_mode mode)
> +{
> + const int *ramp_table = lmu_bank->cfg->ramp_table;
> + const int size = lmu_bank->cfg->size_ramp;
> + unsigned int msec;
> + int i;
> +
> + if (!ramp_table)
> + return -EINVAL;
> +
> + switch (mode) {
> + case BL_RAMP_UP:
> + msec = lmu_bank->ramp_up_msec;
> + break;
> + case BL_RAMP_DOWN:
> + msec = lmu_bank->ramp_down_msec;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (msec <= ramp_table[0])
> + return 0;
> +
> + if (msec > ramp_table[size - 1])
> + return size - 1;
> +
> + for (i = 1; i < size; i++) {
> + if (msec = ramp_table[i])
> + return i;
> +
> + /* Find an approximate index by looking up the table */
> + if (msec > ramp_table[i - 1] && msec < ramp_table[i]) {
> + if (msec - ramp_table[i - 1] < ramp_table[i] - msec)
> + return i - 1;
> + else
> + return i;
> + }
> + }
> +
> + return -EINVAL;
> +}
> +
> +
Extra new line
> +static int ti_lmu_bl_set_ramp(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct ti_lmu_bl_reg *reginfo = lmu_bank->cfg->reginfo;
> + int offset = reginfo->ramp_reg_offset;
> + int i, ret, index;
> + struct lmu_bl_reg_data regdata;
> +
> + for (i = BL_RAMP_UP; i <= BL_RAMP_DOWN; i++) {
> + index = ti_lmu_bl_convert_ramp_to_index(lmu_bank, i);
> + if (index > 0) {
> + if (!reginfo->ramp)
> + break;
> +
> + regdata = reginfo->ramp[i];
> + if (lmu_bank->bank_id != 0)
> + regdata.val += offset;
> +
> + /* regdata.val is shift bit */
> + ret = regmap_update_bits(regmap, regdata.reg,
> + regdata.mask,
> + index << regdata.val);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_configure(struct ti_lmu_bank *lmu_bank)
> +{
> + int ret;
> +
> + ret = ti_lmu_bl_check_channel(lmu_bank);
> + if (ret)
> + return ret;
> +
> + ret = ti_lmu_bl_create_channel(lmu_bank);
> + if (ret)
> + return ret;
> +
> + ret = ti_lmu_bl_update_ctrl_mode(lmu_bank);
> + if (ret)
> + return ret;
> +
> + return ti_lmu_bl_set_ramp(lmu_bank);
> +}
> +
> +static int ti_lmu_bl_register_backlight(struct ti_lmu_bank *lmu_bank)
> +{
> + struct backlight_device *bl_dev;
> + struct backlight_properties props;
> +
> + if (lmu_bank->type != TI_LMU_BL)
> + return -EINVAL;
> +
> + memset(&props, 0, sizeof(struct backlight_properties));
> + props.type = BACKLIGHT_PLATFORM;
> + props.brightness = lmu_bank->default_brightness;
> + props.max_brightness = lmu_bank->cfg->max_brightness;
> +
> + bl_dev = devm_backlight_device_register(lmu_bank->dev, lmu_bank->label,
> + lmu_bank->dev, lmu_bank,
> + &lmu_backlight_ops, &props);
> + if (IS_ERR(bl_dev))
> + return PTR_ERR(bl_dev);
> +
> + lmu_bank->backlight = bl_dev;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_register_led(struct ti_lmu_bank *lmu_bank)
> +{
> + int err;
> +
> + if (lmu_bank->type != TI_LMU_LED)
> + return -EINVAL;
> +
> + lmu_bank->led = devm_kzalloc(lmu_bank->dev, sizeof(*lmu_bank->led),
> + GFP_KERNEL);
> + if (!lmu_bank->led)
> + return -ENOMEM;
> +
> + lmu_bank->led->name = lmu_bank->label;
> + lmu_bank->led->max_brightness = lmu_bank->cfg->max_brightness;
> + lmu_bank->led->brightness_set_blocking > + ti_lmu_bl_set_led_blocking;
> +
> + err = devm_led_classdev_register(lmu_bank->dev, lmu_bank->led);
> + if (err)
> + return err;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
> +{
> + switch (lmu_bank->type) {
> + case TI_LMU_BL:
> + return ti_lmu_bl_register_backlight(lmu_bank);
> + case TI_LMU_LED:
> + return ti_lmu_bl_register_led(lmu_bank);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int setup_of_node(struct platform_device *pdev)
This seems a bit generic. Maybe keep with the naming convention
ti_lmu_setup_of_node
> +{
> + struct device_node *parent_node = pdev->dev.parent->of_node;
> + char *name;
> +
> + if (!parent_node)
> + return 0;
> +
> + name = kasprintf(GFP_KERNEL, "bank%d", pdev->id);
> + if (!name)
> + return -ENOMEM;
> +
> + pdev->dev.of_node = of_get_child_by_name(parent_node, name);
> + kfree(name);
> +
> + if (!pdev->dev.of_node)
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_parse_led_sources(struct device *dev)
> +{
> + unsigned long mask = 0;
> + int ret;
> + int size, i;
> + u32 *leds;
> +
> + size = device_property_read_u32_array(dev, "ti,led-sources", NULL, 0);
> + if (size <= 0) {
> + dev_err(dev, "Missing or malformed property led-sources: %d\n",
> + size);
> + return size < 0 ? size : -EINVAL;
Why the additional check? Why not just return -EINVAL?
> + }
> +
> + leds = kmalloc_array(size, sizeof(u32), GFP_KERNEL);
> + if (!leds)
> + return -ENOMEM;
> +
> + ret = device_property_read_u32_array(dev, "ti,led-sources", leds, size);
> + if (ret) {
> + dev_err(dev, "Failed to read led-sources property: %d\n", ret);
> + goto out;
> + }
> +
> + for (i = 0; i < size; i++)
> + set_bit(leds[i], &mask);
> +
> + ret = mask;
> +
> +out:
> + kfree(leds);
> + return ret;
> +}
> +
> +static int ti_lmu_bl_init(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata > + lmu_bank->cfg->reginfo->init;
> + int num_init = lmu_bank->cfg->reginfo->num_init;
> + int i, ret;
> +
> + if (lmu_bank->lmu->backlight_initialized)
> + return 0;
Add a new line here
> + lmu_bank->lmu->backlight_initialized = true;
> +
> + for (i = 0; regdata && i < num_init; i++) {
> + ret = regmap_update_bits(regmap, regdata->reg, regdata->mask,
> + regdata->val);
> + if (ret)
> + return ret;
> +
> + regdata++;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_reload(struct ti_lmu_bank *lmu_bank)
> +{
> + int err;
> +
> + ti_lmu_bl_init(lmu_bank);
> +
> + err = ti_lmu_bl_configure(lmu_bank);
> + if (err)
> + return err;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, lmu_bank->current_brightness);
> +}
> +
> +static int ti_lmu_bl_monitor_notifier(struct notifier_block *nb,
> + unsigned long action, void *unused)
> +{
> + struct ti_lmu_bank *lmu_bank = container_of(nb, struct ti_lmu_bank, nb);
> +
> + if (action = LMU_EVENT_MONITOR_DONE) {
> + if (ti_lmu_bl_reload(lmu_bank))
> + return NOTIFY_STOP;
> + }
> +
> + return NOTIFY_OK;
> +}
> +
> +static int ti_lmu_bl_probe(struct platform_device *pdev)
> +{
> + struct ti_lmu_bank *lmu_bank;
> + int err;
> +
> + err = setup_of_node(pdev);
> + if (err)
> + return err;
> +
> + lmu_bank = devm_kzalloc(&pdev->dev, sizeof(*lmu_bank), GFP_KERNEL);
> + if (!lmu_bank)
> + return -ENOMEM;
Add a new line here
> + lmu_bank->dev = &pdev->dev;
> + dev_set_drvdata(&pdev->dev, lmu_bank);
> +
> + err = device_property_read_string(&pdev->dev, "label",
> + &lmu_bank->label);
> + if (err)
> + return err;
> +
> + lmu_bank->type = TI_LMU_BL;
> + if (!strcmp(lmu_bank->label, "keyboard")) {
> + lmu_bank->type = TI_LMU_LED;
> + lmu_bank->label = "kbd_backlight";
What is the reason for changing the label? Why can't the label in the DT be kbd_backlight?
> + }
> +
> + lmu_bank->leds = ti_lmu_parse_led_sources(&pdev->dev);
> + if (lmu_bank->leds < 0)
> + return lmu_bank->leds;
> + else if (lmu_bank->leds = 0)
> + return -EINVAL;
> +
> + device_property_read_u32(&pdev->dev, "default-brightness-level",
> + &lmu_bank->default_brightness);
> + device_property_read_u32(&pdev->dev, "ti,ramp-up-ms",
> + &lmu_bank->ramp_up_msec);
> + device_property_read_u32(&pdev->dev, "ti,ramp-down-ms",
> + &lmu_bank->ramp_down_msec);
> + device_property_read_u32(&pdev->dev, "pwm-period",
> + &lmu_bank->pwm_period);
> +
> + if (lmu_bank->pwm_period > 0)
> + lmu_bank->mode = BL_PWM_BASED;
> + else
> + lmu_bank->mode = BL_REGISTER_BASED;
> +
> + lmu_bank->lmu = dev_get_drvdata(pdev->dev.parent);
> + lmu_bank->cfg = &lmu_bl_cfg[lmu_bank->lmu->id];
> + lmu_bank->bank_id = pdev->id;
> +
> + ti_lmu_bl_init(lmu_bank);
> +
> + err = ti_lmu_bl_configure(lmu_bank);
> + if (err)
> + return err;
> +
> + err = ti_lmu_bl_add_device(lmu_bank);
> + if (err)
> + return err;
> +
> + err = ti_lmu_bl_set_brightness(lmu_bank,
> + lmu_bank->default_brightness);
> + if (err)
> + return err;
> +
> + /*
> + * Notifier callback is required because backlight device needs
> + * reconfiguration after fault detection procedure is done by
> + * ti-lmu-fault-monitor driver.
> + */
> + if (lmu_bank->cfg->fault_monitor_used) {
> + lmu_bank->nb.notifier_call = ti_lmu_bl_monitor_notifier;
> + err = blocking_notifier_chain_register(&lmu_bank->lmu->notifier,
> + &lmu_bank->nb);
> + if (err)
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_remove(struct platform_device *pdev)
> +{
> + struct ti_lmu_bank *lmu_bank = platform_get_drvdata(pdev);
> +
> + if (lmu_bank->cfg->fault_monitor_used)
> + blocking_notifier_chain_unregister(&lmu_bank->lmu->notifier,
> + &lmu_bank->nb);
> +
> + ti_lmu_bl_set_brightness(lmu_bank, 0);
> +
> + return 0;
> +}
> +
> +static struct platform_driver ti_lmu_bl_driver = {
> + .probe = ti_lmu_bl_probe,
> + .remove = ti_lmu_bl_remove,
> + .driver = {
> + .name = "ti-lmu-led-backlight",
> + },
> +};
> +module_platform_driver(ti_lmu_bl_driver)
> +
> +MODULE_DESCRIPTION("TI LMU Backlight LED Driver");
> +MODULE_AUTHOR("Sebastian Reichel");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:ti-lmu-led-backlight");
> diff --git a/drivers/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 @@
> +/*
Inconsistent licensing here the core has
// SPDX-License-Identifier: GPL-2.0
> + * TI LMU (Lighting Management Unit) Backlight Device Data
> + *
> + * Copyright 2015 Texas Instruments
Copyright?
> + *
> + * Author: Milo Kim <milo.kim@ti.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include "ti-lmu-backlight-data.h"
> +
> +/* LM3532 */
> +static const struct lmu_bl_reg_data lm3532_init_data[] = {
> + { LM3532_REG_ZONE_CFG_A, LM3532_ZONE_MASK, LM3532_ZONE_0 },
> + { LM3532_REG_ZONE_CFG_B, LM3532_ZONE_MASK, LM3532_ZONE_1 },
> + { LM3532_REG_ZONE_CFG_C, LM3532_ZONE_MASK, LM3532_ZONE_2 },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_channel_data[] = {
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED1_CFG_MASK,
> + LM3532_ILED1_CFG_SHIFT },
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED2_CFG_MASK,
> + LM3532_ILED2_CFG_SHIFT },
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED3_CFG_MASK,
> + LM3532_ILED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_mode_data[] = {
> + { LM3532_REG_PWM_A_CFG, LM3532_PWM_A_MASK, LM3532_PWM_ZONE_0 },
> + { LM3532_REG_PWM_B_CFG, LM3532_PWM_B_MASK, LM3532_PWM_ZONE_1 },
> + { LM3532_REG_PWM_C_CFG, LM3532_PWM_C_MASK, LM3532_PWM_ZONE_2 },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_ramp_data[] = {
> + { LM3532_REG_RAMPUP, LM3532_RAMPUP_MASK, LM3532_RAMPUP_SHIFT },
> + { LM3532_REG_RAMPDN, LM3532_RAMPDN_MASK, LM3532_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3532_enable_reg = LM3532_REG_ENABLE;
> +
> +static u8 lm3532_brightness_regs[] = {
> + LM3532_REG_BRT_A,
> + LM3532_REG_BRT_B,
> + LM3532_REG_BRT_C,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3532_reg_info = {
> + .init = lm3532_init_data,
> + .num_init = ARRAY_SIZE(lm3532_init_data),
> + .channel = lm3532_channel_data,
> + .mode = lm3532_mode_data,
> + .ramp = lm3532_ramp_data,
> + .enable = &lm3532_enable_reg,
> + .brightness_msb = lm3532_brightness_regs,
> +};
> +
> +/* LM3631 */
> +static const struct lmu_bl_reg_data lm3631_init_data[] = {
> + { LM3631_REG_BRT_MODE, LM3631_MODE_MASK, LM3631_DEFAULT_MODE },
> + { LM3631_REG_BL_CFG, LM3631_MAP_MASK, LM3631_EXPONENTIAL_MAP },
> +};
> +
> +static const struct lmu_bl_reg_data lm3631_channel_data[] = {
> + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_SINGLE_CHANNEL },
> + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_DUAL_CHANNEL },
> +};
> +
> +static const struct lmu_bl_reg_data lm3631_ramp_data[] = {
> + { LM3631_REG_SLOPE, LM3631_SLOPE_MASK, LM3631_SLOPE_SHIFT },
> +};
> +
> +static u8 lm3631_enable_reg = LM3631_REG_DEVCTRL;
> +static u8 lm3631_brightness_msb_reg = LM3631_REG_BRT_MSB;
> +static u8 lm3631_brightness_lsb_reg = LM3631_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3631_reg_info = {
> + .init = lm3631_init_data,
> + .num_init = ARRAY_SIZE(lm3631_init_data),
> + .channel = lm3631_channel_data,
> + .ramp = lm3631_ramp_data,
> + .enable = &lm3631_enable_reg,
> + .brightness_msb = &lm3631_brightness_msb_reg,
> + .brightness_lsb = &lm3631_brightness_lsb_reg,
> +};
> +
> +/* LM3632 */
> +static const struct lmu_bl_reg_data lm3632_init_data[] = {
> + { LM3632_REG_CONFIG1, LM3632_OVP_MASK, LM3632_OVP_25V },
> + { LM3632_REG_CONFIG2, LM3632_SWFREQ_MASK, LM3632_SWFREQ_1MHZ },
> +};
> +
> +static const struct lmu_bl_reg_data lm3632_channel_data[] = {
> + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_SINGLE_CHANNEL },
> + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_DUAL_CHANNEL },
> +};
> +
> +static const struct lmu_bl_reg_data lm3632_mode_data[] = {
> + { LM3632_REG_IO_CTRL, LM3632_PWM_MASK, LM3632_PWM_MODE },
> +};
> +
> +static u8 lm3632_enable_reg = LM3632_REG_ENABLE;
> +static u8 lm3632_brightness_msb_reg = LM3632_REG_BRT_MSB;
> +static u8 lm3632_brightness_lsb_reg = LM3632_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3632_reg_info = {
> + .init = lm3632_init_data,
> + .num_init = ARRAY_SIZE(lm3632_init_data),
> + .channel = lm3632_channel_data,
> + .mode = lm3632_mode_data,
> + .enable = &lm3632_enable_reg,
> + .brightness_msb = &lm3632_brightness_msb_reg,
> + .brightness_lsb = &lm3632_brightness_lsb_reg,
> +};
> +
> +/* LM3633 */
> +static const struct lmu_bl_reg_data lm3633_init_data[] = {
> + { LM3633_REG_BOOST_CFG, LM3633_OVP_MASK, LM3633_OVP_40V },
> + { LM3633_REG_BL_RAMP_CONF, LM3633_BL_RAMP_MASK, LM3633_BL_RAMP_EACH },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_channel_data[] = {
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED1_CFG_MASK,
> + LM3633_HVLED1_CFG_SHIFT },
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED2_CFG_MASK,
> + LM3633_HVLED2_CFG_SHIFT },
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED3_CFG_MASK,
> + LM3633_HVLED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_mode_data[] = {
> + { LM3633_REG_PWM_CFG, LM3633_PWM_A_MASK, LM3633_PWM_A_MASK },
> + { LM3633_REG_PWM_CFG, LM3633_PWM_B_MASK, LM3633_PWM_B_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_ramp_data[] = {
> + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPUP_MASK, LM3633_BL_RAMPUP_SHIFT },
> + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPDN_MASK, LM3633_BL_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3633_enable_reg = LM3633_REG_ENABLE;
> +
> +static u8 lm3633_brightness_msb_regs[] = {
> + LM3633_REG_BRT_HVLED_A_MSB,
> + LM3633_REG_BRT_HVLED_B_MSB,
> +};
> +
> +static u8 lm3633_brightness_lsb_regs[] = {
> + LM3633_REG_BRT_HVLED_A_LSB,
> + LM3633_REG_BRT_HVLED_B_LSB,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3633_reg_info = {
> + .init = lm3633_init_data,
> + .num_init = ARRAY_SIZE(lm3633_init_data),
> + .channel = lm3633_channel_data,
> + .mode = lm3633_mode_data,
> + .ramp = lm3633_ramp_data,
> + .ramp_reg_offset = 1, /* For LM3633_REG_BL1_RAMPUP/DN */
> + .enable = &lm3633_enable_reg,
> + .brightness_msb = lm3633_brightness_msb_regs,
> + .brightness_lsb = lm3633_brightness_lsb_regs,
> +};
> +
> +/* LM3695 */
> +static const struct lmu_bl_reg_data lm3695_init_data[] = {
> + { LM3695_REG_GP, LM3695_BRT_RW_MASK, LM3695_BRT_RW_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3695_channel_data[] = {
> + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_SINGLE_CHANNEL },
> + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_DUAL_CHANNEL },
> +};
> +
> +static u8 lm3695_enable_reg = LM3695_REG_GP;
> +static u8 lm3695_brightness_msb_reg = LM3695_REG_BRT_MSB;
> +static u8 lm3695_brightness_lsb_reg = LM3695_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3695_reg_info = {
> + .init = lm3695_init_data,
> + .num_init = ARRAY_SIZE(lm3695_init_data),
> + .channel = lm3695_channel_data,
> + .enable = &lm3695_enable_reg,
> + .enable_usec = 600,
> + .brightness_msb = &lm3695_brightness_msb_reg,
> + .brightness_lsb = &lm3695_brightness_lsb_reg,
> +};
> +
> +/* LM3697 */
> +static const struct lmu_bl_reg_data lm3697_init_data[] = {
> + { LM3697_REG_RAMP_CONF, LM3697_RAMP_MASK, LM3697_RAMP_EACH },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_channel_data[] = {
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED1_CFG_MASK,
> + LM3697_HVLED1_CFG_SHIFT },
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED2_CFG_MASK,
> + LM3697_HVLED2_CFG_SHIFT },
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED3_CFG_MASK,
> + LM3697_HVLED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_mode_data[] = {
> + { LM3697_REG_PWM_CFG, LM3697_PWM_A_MASK, LM3697_PWM_A_MASK },
> + { LM3697_REG_PWM_CFG, LM3697_PWM_B_MASK, LM3697_PWM_B_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_ramp_data[] = {
> + { LM3697_REG_BL0_RAMP, LM3697_RAMPUP_MASK, LM3697_RAMPUP_SHIFT },
> + { LM3697_REG_BL0_RAMP, LM3697_RAMPDN_MASK, LM3697_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3697_enable_reg = LM3697_REG_ENABLE;
> +
> +static u8 lm3697_brightness_msb_regs[] = {
> + LM3697_REG_BRT_A_MSB,
> + LM3697_REG_BRT_B_MSB,
> +};
> +
> +static u8 lm3697_brightness_lsb_regs[] = {
> + LM3697_REG_BRT_A_LSB,
> + LM3697_REG_BRT_B_LSB,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3697_reg_info = {
> + .init = lm3697_init_data,
> + .num_init = ARRAY_SIZE(lm3697_init_data),
> + .channel = lm3697_channel_data,
> + .mode = lm3697_mode_data,
> + .ramp = lm3697_ramp_data,
> + .ramp_reg_offset = 1, /* For LM3697_REG_BL1_RAMPUP/DN */
> + .enable = &lm3697_enable_reg,
> + .brightness_msb = lm3697_brightness_msb_regs,
> + .brightness_lsb = lm3697_brightness_lsb_regs,
> +};
> +
> +static int lm3532_ramp_table[] = { 0, 1, 2, 4, 8, 16, 32, 65 };
> +
> +static int lm3631_ramp_table[] = {
> + 0, 1, 2, 5, 10, 20, 50, 100,
> + 250, 500, 750, 1000, 1500, 2000, 3000, 4000,
> +};
> +
> +static int common_ramp_table[] = {
> + 2, 250, 500, 1000, 2000, 4000, 8000, 16000,
> +};
> +
> +#define LM3532_MAX_CHANNELS 3
> +#define LM3631_MAX_CHANNELS 2
> +#define LM3632_MAX_CHANNELS 2
> +#define LM3633_MAX_CHANNELS 3
> +#define LM3695_MAX_CHANNELS 2
> +#define LM3697_MAX_CHANNELS 3
> +
> +const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID] = {
> + {
> + .reginfo = &lm3532_reg_info,
> + .num_channels = LM3532_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_8BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + .ramp_table = lm3532_ramp_table,
> + .size_ramp = ARRAY_SIZE(lm3532_ramp_table),
> + },
> + {
> + .reginfo = &lm3631_reg_info,
> + .num_channels = LM3631_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_ONLY,
> + .ramp_table = lm3631_ramp_table,
> + .size_ramp = ARRAY_SIZE(lm3631_ramp_table),
> + },
> + {
> + .reginfo = &lm3632_reg_info,
> + .num_channels = LM3632_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_ONLY,
> + },
> + {
> + .reginfo = &lm3633_reg_info,
> + .num_channels = LM3633_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_MAX_BRT,
> + .ramp_table = common_ramp_table,
> + .size_ramp = ARRAY_SIZE(common_ramp_table),
> + .fault_monitor_used = true,
> + },
> + {
> + .reginfo = &lm3695_reg_info,
> + .num_channels = LM3695_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + },
> + {
> + .reginfo = &lm3697_reg_info,
> + .num_channels = LM3697_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + .ramp_table = common_ramp_table,
> + .size_ramp = ARRAY_SIZE(common_ramp_table),
> + .fault_monitor_used = true,
> + },
> +};
> diff --git a/drivers/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 @@
> +/*
Inconsistent licensing here the core has
// SPDX-License-Identifier: GPL-2.0
> + * TI LMU (Lighting Management Unit) Backlight Device Data Definitions
> + *
> + * Copyright 2015 Texas Instruments
> + *
> + * Author: Milo Kim <milo.kim@ti.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#ifndef __TI_LMU_BACKLIGHT_H__
> +#define __TI_LMU_BACKLIGHT_H__
> +
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +
> +#define MAX_BRIGHTNESS_8BIT 255
> +#define MAX_BRIGHTNESS_11BIT 2047
> +
> +enum ti_lmu_bl_pwm_action {
> + /* Update PWM duty, no brightness register update is required */
> + UPDATE_PWM_ONLY,
> + /* Update not only duty but also brightness register */
> + UPDATE_PWM_AND_BRT_REGISTER,
> + /* Update max value in brightness registers */
> + UPDATE_MAX_BRT,
> +};
> +
> +struct lmu_bl_reg_data {
> + u8 reg;
> + u8 mask;
> + u8 val;
> +};
> +
> +/**
> + * struct ti_lmu_bl_reg
> + *
> + * @init: Device initialization registers
> + * @num_init: Numbers of initialization registers
> + * @channel: Backlight channel configuration registers
> + * @mode: Brightness control mode registers
> + * @ramp: Ramp registers for lighting effect
> + * @ramp_reg_offset: Ramp register offset.
> + * Only used for multiple ramp registers.
> + * @enable: Enable control register address
> + * @enable_usec: Delay time for updating enable register.
> + * Unit is microsecond.
> + * @brightness_msb: Brightness MSB(Upper 8 bits) registers.
> + * Concatenated with LSB in 11 bit dimming mode.
> + * In 8 bit dimming, only MSB is used.
> + * @brightness_lsb: Brightness LSB(Lower 3 bits) registers.
> + * Only valid in 11 bit dimming mode.
> + */
> +struct ti_lmu_bl_reg {
> + const struct lmu_bl_reg_data *init;
> + int num_init;
> + const struct lmu_bl_reg_data *channel;
> + const struct lmu_bl_reg_data *mode;
> + const struct lmu_bl_reg_data *ramp;
> + int ramp_reg_offset;
> + u8 *enable;
> + unsigned long enable_usec;
> + u8 *brightness_msb;
> + u8 *brightness_lsb;
> +};
> +
> +/**
> + * struct ti_lmu_bl_cfg
> + *
> + * @reginfo: Device register configuration
> + * @num_channels: Number of backlight channels
> + * @max_brightness: Max brightness value of backlight device
> + * @pwm_action: How to control brightness registers in PWM mode
> + * @ramp_table: [Optional] Ramp time table for lighting effect.
> + * It's used for searching approximate register index.
> + * @size_ramp: [Optional] Size of ramp table
> + * @fault_monitor_used: [Optional] Set true if the device needs to handle
> + * LMU fault monitor event.
> + *
> + * This structure is used for device specific data configuration.
> + */
> +struct ti_lmu_bl_cfg {
> + const struct ti_lmu_bl_reg *reginfo;
> + int num_channels;
> + int max_brightness;
> + enum ti_lmu_bl_pwm_action pwm_action;
> + int *ramp_table;
> + int size_ramp;
> + bool fault_monitor_used;
> +};
> +
> +extern const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID];
> +#endif
>
--
------------------
Dan Murphy
^ permalink raw reply [flat|nested] 62+ messages in thread* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
@ 2018-04-04 18:30 ` Dan Murphy
0 siblings, 0 replies; 62+ messages in thread
From: Dan Murphy @ 2018-04-04 18:30 UTC (permalink / raw)
To: Sebastian Reichel, Sebastian Reichel, Lee Jones, Daniel Thompson,
Rob Herring, Tony Lindgren
Cc: Jingoo Han, Mark Rutland, linux-kernel, linux-fbdev, devicetree,
linux-omap
Sebastian
-Milo Kim email is not valid
On 03/30/2018 12:24 PM, Sebastian Reichel wrote:
> This adds backlight support for the following TI LMU
> chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
>
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> [add LED subsystem support for keyboard backlight and rework DT
> binding according to Rob Herrings feedback]
> Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> ---
> drivers/video/backlight/Kconfig | 7 +
> drivers/video/backlight/Makefile | 3 +
> drivers/video/backlight/ti-lmu-backlight-core.c | 666 ++++++++++++++++++++++++
> drivers/video/backlight/ti-lmu-backlight-data.c | 304 +++++++++++
> drivers/video/backlight/ti-lmu-backlight-data.h | 95 ++++
> 5 files changed, 1075 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 5d2d0d7e8100..27e6c5a0add8 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
select REGMAP?
> + 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 19da71d518bf..a1132d3dfd4c 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -53,6 +53,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..a6099581edd7
> --- /dev/null
> +++ b/drivers/video/backlight/ti-lmu-backlight-core.c
> @@ -0,0 +1,666 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2015 Texas Instruments
> + * Copyright 2018 Sebastian Reichel
> + *
> + * TI LMU Backlight driver, based on previous work from
> + * Milo Kim <milo.kim@ti.com>
> + */
> +
> +#include <linux/backlight.h>
> +#include <linux/bitops.h>
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/leds.h>
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +
> +#include "ti-lmu-backlight-data.h"
> +
> +enum ti_lmu_bl_ctrl_mode {
> + BL_REGISTER_BASED,
> + BL_PWM_BASED,
> +};
> +
> +enum ti_lmu_bl_type {
> + TI_LMU_BL, /* backlight userspace interface */
> + TI_LMU_LED, /* led userspace interface */
> +};
> +
> +enum ti_lmu_bl_ramp_mode {
> + BL_RAMP_UP,
> + BL_RAMP_DOWN,
> +};
> +
> +#define DEFAULT_PWM_NAME "lmu-backlight"
I don't see this used.
> +#define NUM_DUAL_CHANNEL 2
> +#define LMU_BACKLIGHT_DUAL_CHANNEL_USED (BIT(0) | BIT(1))
> +#define LMU_BACKLIGHT_11BIT_LSB_MASK (BIT(0) | BIT(1) | BIT(2))
> +#define LMU_BACKLIGHT_11BIT_MSB_SHIFT 3
> +
Struct kerneldoc?
> +struct ti_lmu_bank {
> + struct device *dev;
> + int bank_id;
> + const struct ti_lmu_bl_cfg *cfg;
> + struct ti_lmu *lmu;
> + const char *label;
> + int leds;
> + int current_brightness;
> + u32 default_brightness;
> + u32 ramp_up_msec;
> + u32 ramp_down_msec;
> + u32 pwm_period;
> + enum ti_lmu_bl_ctrl_mode mode;
> + enum ti_lmu_bl_type type;
> +
> + struct notifier_block nb;
> +
> + struct backlight_device *backlight;
> + struct led_classdev *led;
> +};
> +
> +static int ti_lmu_bl_enable(struct ti_lmu_bank *lmu_bank, bool enable)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + unsigned long enable_time = lmu_bank->cfg->reginfo->enable_usec;
> + u8 *reg = lmu_bank->cfg->reginfo->enable;
> + u8 mask = BIT(lmu_bank->bank_id);
> + u8 val = (enable == true) ? mask : 0;
> + int ret;
> +
> + if (!reg)
> + return -EINVAL;
> +
> + ret = regmap_update_bits(regmap, *reg, mask, val);
> + if (ret)
> + return ret;
> +
> + if (enable_time > 0)
> + usleep_range(enable_time, enable_time + 100);
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_update_brightness_register(struct ti_lmu_bank *lmu_bank,
> + int brightness)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + u8 reg, val;
> + int ret;
> +
> + /*
> + * Brightness register update
> + *
> + * 11 bit dimming: update LSB bits and write MSB byte.
> + * MSB brightness should be shifted.
> + * 8 bit dimming: write MSB byte.
> + */
> + if (cfg->max_brightness == MAX_BRIGHTNESS_11BIT) {
> + reg = reginfo->brightness_lsb[lmu_bank->bank_id];
> + ret = regmap_update_bits(regmap, reg,
> + LMU_BACKLIGHT_11BIT_LSB_MASK,
> + brightness);
> + if (ret)
> + return ret;
> +
> + val = brightness >> LMU_BACKLIGHT_11BIT_MSB_SHIFT;
> + } else {
> + val = brightness;
> + }
> +
> + reg = reginfo->brightness_msb[lmu_bank->bank_id];
> + return regmap_write(regmap, reg, val);
> +}
> +
> +static int ti_lmu_bl_pwm_ctrl(struct ti_lmu_bank *lmu_bank, int brightness)
> +{
> + int max_brightness = lmu_bank->cfg->max_brightness;
> + struct pwm_state state = { };
> + int ret;
> +
> + if (!lmu_bank->lmu->pwm) {
> + dev_err(lmu_bank->dev, "Missing PWM device!\n");
> + return -ENODEV;
> + }
> +
> + pwm_init_state(lmu_bank->lmu->pwm, &state);
> + state.period = lmu_bank->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_bank->lmu->pwm, &state);
> + if (ret)
> + dev_err(lmu_bank->dev, "Failed to configure PWM: %d", ret);
> +
> + return ret;
> +}
> +
> +static int ti_lmu_bl_set_brightness(struct ti_lmu_bank *lmu_bank,
> + int brightness)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + bool enable = brightness > 0;
> + int ret;
> +
Is there anyway that update_status and set_led_blocking can be called at the same time causing
a race condition in this api?
> + ret = ti_lmu_bl_enable(lmu_bank, enable);
> + if (ret)
> + return ret;
> +
> + if (lmu_bank->mode == BL_PWM_BASED) {
> + ti_lmu_bl_pwm_ctrl(lmu_bank, 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;
> + }
> + }
> +
> + lmu_bank->current_brightness = brightness;
> +
> + return ti_lmu_bl_update_brightness_register(lmu_bank, brightness);
> +}
> +
> +static int ti_lmu_bl_update_status(struct backlight_device *bl_dev)
> +{
> + struct ti_lmu_bank *lmu_bank = bl_get_data(bl_dev);
> + int brightness = bl_dev->props.brightness;
> +
> + if (bl_dev->props.state & BL_CORE_SUSPENDED)
> + brightness = 0;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> +}
> +
> +static const struct backlight_ops lmu_backlight_ops = {
> + .options = BL_CORE_SUSPENDRESUME,
> + .update_status = ti_lmu_bl_update_status,
> +};
> +
> +static int ti_lmu_bl_set_led_blocking(struct led_classdev *ledc,
> + enum led_brightness value)
> +{
> + struct ti_lmu_bank *lmu_bank = dev_get_drvdata(ledc->dev->parent);
> + int brightness = value;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> +}
> +
> +static int ti_lmu_bl_check_channel(struct ti_lmu_bank *lmu_bank)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> +
> + if (!reginfo->brightness_msb)
> + return -EINVAL;
> +
> + if (cfg->max_brightness > MAX_BRIGHTNESS_8BIT) {
> + if (!reginfo->brightness_lsb)
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_create_channel(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata = lmu_bank->cfg->reginfo->channel;
> + int num_channels = lmu_bank->cfg->num_channels;
> + unsigned long led_sources = lmu_bank->leds;
> + int i, ret;
> + u8 shift;
> +
> + /*
> + * How to create backlight output channels:
> + * Check 'led_sources' bit and update registers.
> + *
> + * 1) Dual channel configuration
> + * The 1st register data is used for single channel.
> + * The 2nd register data is used for dual channel.
> + *
> + * 2) Multiple channel configuration
> + * Each register data is mapped to bank ID.
> + * Bit shift operation is defined in channel registers.
> + *
> + * Channel register data consists of address, mask, value.
> + */
> +
> + if (num_channels == NUM_DUAL_CHANNEL) {
> + if (led_sources == LMU_BACKLIGHT_DUAL_CHANNEL_USED)
> + regdata++;
> +
> + return regmap_update_bits(regmap, regdata->reg, regdata->mask,
> + regdata->val);
> + }
> +
> + for (i = 0; regdata && i < num_channels; i++) {
> + /*
> + * Note that the result of regdata->val is shift bit.
> + * The bank_id should be shifted for the channel configuration.
> + */
> + if (test_bit(i, &led_sources)) {
> + shift = regdata->val;
> + ret = regmap_update_bits(regmap, regdata->reg,
> + regdata->mask,
> + lmu_bank->bank_id << shift);
> + if (ret)
> + return ret;
> + }
> +
> + regdata++;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_update_ctrl_mode(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata =
> + lmu_bank->cfg->reginfo->mode + lmu_bank->bank_id;
> + u8 val = regdata->val;
> +
> + if (!regdata)
> + return 0;
> +
> + /*
> + * Update PWM configuration register.
> + * If the mode is register based, then clear the bit.
> + */
> + if (lmu_bank->mode != BL_PWM_BASED)
> + val = 0;
> +
> + return regmap_update_bits(regmap, regdata->reg, regdata->mask, val);
> +}
> +
> +static int ti_lmu_bl_convert_ramp_to_index(struct ti_lmu_bank *lmu_bank,
> + enum ti_lmu_bl_ramp_mode mode)
> +{
> + const int *ramp_table = lmu_bank->cfg->ramp_table;
> + const int size = lmu_bank->cfg->size_ramp;
> + unsigned int msec;
> + int i;
> +
> + if (!ramp_table)
> + return -EINVAL;
> +
> + switch (mode) {
> + case BL_RAMP_UP:
> + msec = lmu_bank->ramp_up_msec;
> + break;
> + case BL_RAMP_DOWN:
> + msec = lmu_bank->ramp_down_msec;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (msec <= ramp_table[0])
> + return 0;
> +
> + if (msec > ramp_table[size - 1])
> + return size - 1;
> +
> + for (i = 1; i < size; i++) {
> + if (msec == ramp_table[i])
> + return i;
> +
> + /* Find an approximate index by looking up the table */
> + if (msec > ramp_table[i - 1] && msec < ramp_table[i]) {
> + if (msec - ramp_table[i - 1] < ramp_table[i] - msec)
> + return i - 1;
> + else
> + return i;
> + }
> + }
> +
> + return -EINVAL;
> +}
> +
> +
Extra new line
> +static int ti_lmu_bl_set_ramp(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct ti_lmu_bl_reg *reginfo = lmu_bank->cfg->reginfo;
> + int offset = reginfo->ramp_reg_offset;
> + int i, ret, index;
> + struct lmu_bl_reg_data regdata;
> +
> + for (i = BL_RAMP_UP; i <= BL_RAMP_DOWN; i++) {
> + index = ti_lmu_bl_convert_ramp_to_index(lmu_bank, i);
> + if (index > 0) {
> + if (!reginfo->ramp)
> + break;
> +
> + regdata = reginfo->ramp[i];
> + if (lmu_bank->bank_id != 0)
> + regdata.val += offset;
> +
> + /* regdata.val is shift bit */
> + ret = regmap_update_bits(regmap, regdata.reg,
> + regdata.mask,
> + index << regdata.val);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_configure(struct ti_lmu_bank *lmu_bank)
> +{
> + int ret;
> +
> + ret = ti_lmu_bl_check_channel(lmu_bank);
> + if (ret)
> + return ret;
> +
> + ret = ti_lmu_bl_create_channel(lmu_bank);
> + if (ret)
> + return ret;
> +
> + ret = ti_lmu_bl_update_ctrl_mode(lmu_bank);
> + if (ret)
> + return ret;
> +
> + return ti_lmu_bl_set_ramp(lmu_bank);
> +}
> +
> +static int ti_lmu_bl_register_backlight(struct ti_lmu_bank *lmu_bank)
> +{
> + struct backlight_device *bl_dev;
> + struct backlight_properties props;
> +
> + if (lmu_bank->type != TI_LMU_BL)
> + return -EINVAL;
> +
> + memset(&props, 0, sizeof(struct backlight_properties));
> + props.type = BACKLIGHT_PLATFORM;
> + props.brightness = lmu_bank->default_brightness;
> + props.max_brightness = lmu_bank->cfg->max_brightness;
> +
> + bl_dev = devm_backlight_device_register(lmu_bank->dev, lmu_bank->label,
> + lmu_bank->dev, lmu_bank,
> + &lmu_backlight_ops, &props);
> + if (IS_ERR(bl_dev))
> + return PTR_ERR(bl_dev);
> +
> + lmu_bank->backlight = bl_dev;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_register_led(struct ti_lmu_bank *lmu_bank)
> +{
> + int err;
> +
> + if (lmu_bank->type != TI_LMU_LED)
> + return -EINVAL;
> +
> + lmu_bank->led = devm_kzalloc(lmu_bank->dev, sizeof(*lmu_bank->led),
> + GFP_KERNEL);
> + if (!lmu_bank->led)
> + return -ENOMEM;
> +
> + lmu_bank->led->name = lmu_bank->label;
> + lmu_bank->led->max_brightness = lmu_bank->cfg->max_brightness;
> + lmu_bank->led->brightness_set_blocking =
> + ti_lmu_bl_set_led_blocking;
> +
> + err = devm_led_classdev_register(lmu_bank->dev, lmu_bank->led);
> + if (err)
> + return err;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
> +{
> + switch (lmu_bank->type) {
> + case TI_LMU_BL:
> + return ti_lmu_bl_register_backlight(lmu_bank);
> + case TI_LMU_LED:
> + return ti_lmu_bl_register_led(lmu_bank);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int setup_of_node(struct platform_device *pdev)
This seems a bit generic. Maybe keep with the naming convention
ti_lmu_setup_of_node
> +{
> + struct device_node *parent_node = pdev->dev.parent->of_node;
> + char *name;
> +
> + if (!parent_node)
> + return 0;
> +
> + name = kasprintf(GFP_KERNEL, "bank%d", pdev->id);
> + if (!name)
> + return -ENOMEM;
> +
> + pdev->dev.of_node = of_get_child_by_name(parent_node, name);
> + kfree(name);
> +
> + if (!pdev->dev.of_node)
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_parse_led_sources(struct device *dev)
> +{
> + unsigned long mask = 0;
> + int ret;
> + int size, i;
> + u32 *leds;
> +
> + size = device_property_read_u32_array(dev, "ti,led-sources", NULL, 0);
> + if (size <= 0) {
> + dev_err(dev, "Missing or malformed property led-sources: %d\n",
> + size);
> + return size < 0 ? size : -EINVAL;
Why the additional check? Why not just return -EINVAL?
> + }
> +
> + leds = kmalloc_array(size, sizeof(u32), GFP_KERNEL);
> + if (!leds)
> + return -ENOMEM;
> +
> + ret = device_property_read_u32_array(dev, "ti,led-sources", leds, size);
> + if (ret) {
> + dev_err(dev, "Failed to read led-sources property: %d\n", ret);
> + goto out;
> + }
> +
> + for (i = 0; i < size; i++)
> + set_bit(leds[i], &mask);
> +
> + ret = mask;
> +
> +out:
> + kfree(leds);
> + return ret;
> +}
> +
> +static int ti_lmu_bl_init(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata =
> + lmu_bank->cfg->reginfo->init;
> + int num_init = lmu_bank->cfg->reginfo->num_init;
> + int i, ret;
> +
> + if (lmu_bank->lmu->backlight_initialized)
> + return 0;
Add a new line here
> + lmu_bank->lmu->backlight_initialized = true;
> +
> + for (i = 0; regdata && i < num_init; i++) {
> + ret = regmap_update_bits(regmap, regdata->reg, regdata->mask,
> + regdata->val);
> + if (ret)
> + return ret;
> +
> + regdata++;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_reload(struct ti_lmu_bank *lmu_bank)
> +{
> + int err;
> +
> + ti_lmu_bl_init(lmu_bank);
> +
> + err = ti_lmu_bl_configure(lmu_bank);
> + if (err)
> + return err;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, lmu_bank->current_brightness);
> +}
> +
> +static int ti_lmu_bl_monitor_notifier(struct notifier_block *nb,
> + unsigned long action, void *unused)
> +{
> + struct ti_lmu_bank *lmu_bank = container_of(nb, struct ti_lmu_bank, nb);
> +
> + if (action == LMU_EVENT_MONITOR_DONE) {
> + if (ti_lmu_bl_reload(lmu_bank))
> + return NOTIFY_STOP;
> + }
> +
> + return NOTIFY_OK;
> +}
> +
> +static int ti_lmu_bl_probe(struct platform_device *pdev)
> +{
> + struct ti_lmu_bank *lmu_bank;
> + int err;
> +
> + err = setup_of_node(pdev);
> + if (err)
> + return err;
> +
> + lmu_bank = devm_kzalloc(&pdev->dev, sizeof(*lmu_bank), GFP_KERNEL);
> + if (!lmu_bank)
> + return -ENOMEM;
Add a new line here
> + lmu_bank->dev = &pdev->dev;
> + dev_set_drvdata(&pdev->dev, lmu_bank);
> +
> + err = device_property_read_string(&pdev->dev, "label",
> + &lmu_bank->label);
> + if (err)
> + return err;
> +
> + lmu_bank->type = TI_LMU_BL;
> + if (!strcmp(lmu_bank->label, "keyboard")) {
> + lmu_bank->type = TI_LMU_LED;
> + lmu_bank->label = "kbd_backlight";
What is the reason for changing the label? Why can't the label in the DT be kbd_backlight?
> + }
> +
> + lmu_bank->leds = ti_lmu_parse_led_sources(&pdev->dev);
> + if (lmu_bank->leds < 0)
> + return lmu_bank->leds;
> + else if (lmu_bank->leds == 0)
> + return -EINVAL;
> +
> + device_property_read_u32(&pdev->dev, "default-brightness-level",
> + &lmu_bank->default_brightness);
> + device_property_read_u32(&pdev->dev, "ti,ramp-up-ms",
> + &lmu_bank->ramp_up_msec);
> + device_property_read_u32(&pdev->dev, "ti,ramp-down-ms",
> + &lmu_bank->ramp_down_msec);
> + device_property_read_u32(&pdev->dev, "pwm-period",
> + &lmu_bank->pwm_period);
> +
> + if (lmu_bank->pwm_period > 0)
> + lmu_bank->mode = BL_PWM_BASED;
> + else
> + lmu_bank->mode = BL_REGISTER_BASED;
> +
> + lmu_bank->lmu = dev_get_drvdata(pdev->dev.parent);
> + lmu_bank->cfg = &lmu_bl_cfg[lmu_bank->lmu->id];
> + lmu_bank->bank_id = pdev->id;
> +
> + ti_lmu_bl_init(lmu_bank);
> +
> + err = ti_lmu_bl_configure(lmu_bank);
> + if (err)
> + return err;
> +
> + err = ti_lmu_bl_add_device(lmu_bank);
> + if (err)
> + return err;
> +
> + err = ti_lmu_bl_set_brightness(lmu_bank,
> + lmu_bank->default_brightness);
> + if (err)
> + return err;
> +
> + /*
> + * Notifier callback is required because backlight device needs
> + * reconfiguration after fault detection procedure is done by
> + * ti-lmu-fault-monitor driver.
> + */
> + if (lmu_bank->cfg->fault_monitor_used) {
> + lmu_bank->nb.notifier_call = ti_lmu_bl_monitor_notifier;
> + err = blocking_notifier_chain_register(&lmu_bank->lmu->notifier,
> + &lmu_bank->nb);
> + if (err)
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_remove(struct platform_device *pdev)
> +{
> + struct ti_lmu_bank *lmu_bank = platform_get_drvdata(pdev);
> +
> + if (lmu_bank->cfg->fault_monitor_used)
> + blocking_notifier_chain_unregister(&lmu_bank->lmu->notifier,
> + &lmu_bank->nb);
> +
> + ti_lmu_bl_set_brightness(lmu_bank, 0);
> +
> + return 0;
> +}
> +
> +static struct platform_driver ti_lmu_bl_driver = {
> + .probe = ti_lmu_bl_probe,
> + .remove = ti_lmu_bl_remove,
> + .driver = {
> + .name = "ti-lmu-led-backlight",
> + },
> +};
> +module_platform_driver(ti_lmu_bl_driver)
> +
> +MODULE_DESCRIPTION("TI LMU Backlight LED Driver");
> +MODULE_AUTHOR("Sebastian Reichel");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:ti-lmu-led-backlight");
> diff --git a/drivers/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 @@
> +/*
Inconsistent licensing here the core has
// SPDX-License-Identifier: GPL-2.0
> + * TI LMU (Lighting Management Unit) Backlight Device Data
> + *
> + * Copyright 2015 Texas Instruments
Copyright?
> + *
> + * Author: Milo Kim <milo.kim@ti.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include "ti-lmu-backlight-data.h"
> +
> +/* LM3532 */
> +static const struct lmu_bl_reg_data lm3532_init_data[] = {
> + { LM3532_REG_ZONE_CFG_A, LM3532_ZONE_MASK, LM3532_ZONE_0 },
> + { LM3532_REG_ZONE_CFG_B, LM3532_ZONE_MASK, LM3532_ZONE_1 },
> + { LM3532_REG_ZONE_CFG_C, LM3532_ZONE_MASK, LM3532_ZONE_2 },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_channel_data[] = {
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED1_CFG_MASK,
> + LM3532_ILED1_CFG_SHIFT },
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED2_CFG_MASK,
> + LM3532_ILED2_CFG_SHIFT },
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED3_CFG_MASK,
> + LM3532_ILED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_mode_data[] = {
> + { LM3532_REG_PWM_A_CFG, LM3532_PWM_A_MASK, LM3532_PWM_ZONE_0 },
> + { LM3532_REG_PWM_B_CFG, LM3532_PWM_B_MASK, LM3532_PWM_ZONE_1 },
> + { LM3532_REG_PWM_C_CFG, LM3532_PWM_C_MASK, LM3532_PWM_ZONE_2 },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_ramp_data[] = {
> + { LM3532_REG_RAMPUP, LM3532_RAMPUP_MASK, LM3532_RAMPUP_SHIFT },
> + { LM3532_REG_RAMPDN, LM3532_RAMPDN_MASK, LM3532_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3532_enable_reg = LM3532_REG_ENABLE;
> +
> +static u8 lm3532_brightness_regs[] = {
> + LM3532_REG_BRT_A,
> + LM3532_REG_BRT_B,
> + LM3532_REG_BRT_C,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3532_reg_info = {
> + .init = lm3532_init_data,
> + .num_init = ARRAY_SIZE(lm3532_init_data),
> + .channel = lm3532_channel_data,
> + .mode = lm3532_mode_data,
> + .ramp = lm3532_ramp_data,
> + .enable = &lm3532_enable_reg,
> + .brightness_msb = lm3532_brightness_regs,
> +};
> +
> +/* LM3631 */
> +static const struct lmu_bl_reg_data lm3631_init_data[] = {
> + { LM3631_REG_BRT_MODE, LM3631_MODE_MASK, LM3631_DEFAULT_MODE },
> + { LM3631_REG_BL_CFG, LM3631_MAP_MASK, LM3631_EXPONENTIAL_MAP },
> +};
> +
> +static const struct lmu_bl_reg_data lm3631_channel_data[] = {
> + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_SINGLE_CHANNEL },
> + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_DUAL_CHANNEL },
> +};
> +
> +static const struct lmu_bl_reg_data lm3631_ramp_data[] = {
> + { LM3631_REG_SLOPE, LM3631_SLOPE_MASK, LM3631_SLOPE_SHIFT },
> +};
> +
> +static u8 lm3631_enable_reg = LM3631_REG_DEVCTRL;
> +static u8 lm3631_brightness_msb_reg = LM3631_REG_BRT_MSB;
> +static u8 lm3631_brightness_lsb_reg = LM3631_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3631_reg_info = {
> + .init = lm3631_init_data,
> + .num_init = ARRAY_SIZE(lm3631_init_data),
> + .channel = lm3631_channel_data,
> + .ramp = lm3631_ramp_data,
> + .enable = &lm3631_enable_reg,
> + .brightness_msb = &lm3631_brightness_msb_reg,
> + .brightness_lsb = &lm3631_brightness_lsb_reg,
> +};
> +
> +/* LM3632 */
> +static const struct lmu_bl_reg_data lm3632_init_data[] = {
> + { LM3632_REG_CONFIG1, LM3632_OVP_MASK, LM3632_OVP_25V },
> + { LM3632_REG_CONFIG2, LM3632_SWFREQ_MASK, LM3632_SWFREQ_1MHZ },
> +};
> +
> +static const struct lmu_bl_reg_data lm3632_channel_data[] = {
> + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_SINGLE_CHANNEL },
> + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_DUAL_CHANNEL },
> +};
> +
> +static const struct lmu_bl_reg_data lm3632_mode_data[] = {
> + { LM3632_REG_IO_CTRL, LM3632_PWM_MASK, LM3632_PWM_MODE },
> +};
> +
> +static u8 lm3632_enable_reg = LM3632_REG_ENABLE;
> +static u8 lm3632_brightness_msb_reg = LM3632_REG_BRT_MSB;
> +static u8 lm3632_brightness_lsb_reg = LM3632_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3632_reg_info = {
> + .init = lm3632_init_data,
> + .num_init = ARRAY_SIZE(lm3632_init_data),
> + .channel = lm3632_channel_data,
> + .mode = lm3632_mode_data,
> + .enable = &lm3632_enable_reg,
> + .brightness_msb = &lm3632_brightness_msb_reg,
> + .brightness_lsb = &lm3632_brightness_lsb_reg,
> +};
> +
> +/* LM3633 */
> +static const struct lmu_bl_reg_data lm3633_init_data[] = {
> + { LM3633_REG_BOOST_CFG, LM3633_OVP_MASK, LM3633_OVP_40V },
> + { LM3633_REG_BL_RAMP_CONF, LM3633_BL_RAMP_MASK, LM3633_BL_RAMP_EACH },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_channel_data[] = {
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED1_CFG_MASK,
> + LM3633_HVLED1_CFG_SHIFT },
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED2_CFG_MASK,
> + LM3633_HVLED2_CFG_SHIFT },
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED3_CFG_MASK,
> + LM3633_HVLED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_mode_data[] = {
> + { LM3633_REG_PWM_CFG, LM3633_PWM_A_MASK, LM3633_PWM_A_MASK },
> + { LM3633_REG_PWM_CFG, LM3633_PWM_B_MASK, LM3633_PWM_B_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_ramp_data[] = {
> + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPUP_MASK, LM3633_BL_RAMPUP_SHIFT },
> + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPDN_MASK, LM3633_BL_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3633_enable_reg = LM3633_REG_ENABLE;
> +
> +static u8 lm3633_brightness_msb_regs[] = {
> + LM3633_REG_BRT_HVLED_A_MSB,
> + LM3633_REG_BRT_HVLED_B_MSB,
> +};
> +
> +static u8 lm3633_brightness_lsb_regs[] = {
> + LM3633_REG_BRT_HVLED_A_LSB,
> + LM3633_REG_BRT_HVLED_B_LSB,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3633_reg_info = {
> + .init = lm3633_init_data,
> + .num_init = ARRAY_SIZE(lm3633_init_data),
> + .channel = lm3633_channel_data,
> + .mode = lm3633_mode_data,
> + .ramp = lm3633_ramp_data,
> + .ramp_reg_offset = 1, /* For LM3633_REG_BL1_RAMPUP/DN */
> + .enable = &lm3633_enable_reg,
> + .brightness_msb = lm3633_brightness_msb_regs,
> + .brightness_lsb = lm3633_brightness_lsb_regs,
> +};
> +
> +/* LM3695 */
> +static const struct lmu_bl_reg_data lm3695_init_data[] = {
> + { LM3695_REG_GP, LM3695_BRT_RW_MASK, LM3695_BRT_RW_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3695_channel_data[] = {
> + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_SINGLE_CHANNEL },
> + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_DUAL_CHANNEL },
> +};
> +
> +static u8 lm3695_enable_reg = LM3695_REG_GP;
> +static u8 lm3695_brightness_msb_reg = LM3695_REG_BRT_MSB;
> +static u8 lm3695_brightness_lsb_reg = LM3695_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3695_reg_info = {
> + .init = lm3695_init_data,
> + .num_init = ARRAY_SIZE(lm3695_init_data),
> + .channel = lm3695_channel_data,
> + .enable = &lm3695_enable_reg,
> + .enable_usec = 600,
> + .brightness_msb = &lm3695_brightness_msb_reg,
> + .brightness_lsb = &lm3695_brightness_lsb_reg,
> +};
> +
> +/* LM3697 */
> +static const struct lmu_bl_reg_data lm3697_init_data[] = {
> + { LM3697_REG_RAMP_CONF, LM3697_RAMP_MASK, LM3697_RAMP_EACH },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_channel_data[] = {
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED1_CFG_MASK,
> + LM3697_HVLED1_CFG_SHIFT },
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED2_CFG_MASK,
> + LM3697_HVLED2_CFG_SHIFT },
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED3_CFG_MASK,
> + LM3697_HVLED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_mode_data[] = {
> + { LM3697_REG_PWM_CFG, LM3697_PWM_A_MASK, LM3697_PWM_A_MASK },
> + { LM3697_REG_PWM_CFG, LM3697_PWM_B_MASK, LM3697_PWM_B_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_ramp_data[] = {
> + { LM3697_REG_BL0_RAMP, LM3697_RAMPUP_MASK, LM3697_RAMPUP_SHIFT },
> + { LM3697_REG_BL0_RAMP, LM3697_RAMPDN_MASK, LM3697_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3697_enable_reg = LM3697_REG_ENABLE;
> +
> +static u8 lm3697_brightness_msb_regs[] = {
> + LM3697_REG_BRT_A_MSB,
> + LM3697_REG_BRT_B_MSB,
> +};
> +
> +static u8 lm3697_brightness_lsb_regs[] = {
> + LM3697_REG_BRT_A_LSB,
> + LM3697_REG_BRT_B_LSB,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3697_reg_info = {
> + .init = lm3697_init_data,
> + .num_init = ARRAY_SIZE(lm3697_init_data),
> + .channel = lm3697_channel_data,
> + .mode = lm3697_mode_data,
> + .ramp = lm3697_ramp_data,
> + .ramp_reg_offset = 1, /* For LM3697_REG_BL1_RAMPUP/DN */
> + .enable = &lm3697_enable_reg,
> + .brightness_msb = lm3697_brightness_msb_regs,
> + .brightness_lsb = lm3697_brightness_lsb_regs,
> +};
> +
> +static int lm3532_ramp_table[] = { 0, 1, 2, 4, 8, 16, 32, 65 };
> +
> +static int lm3631_ramp_table[] = {
> + 0, 1, 2, 5, 10, 20, 50, 100,
> + 250, 500, 750, 1000, 1500, 2000, 3000, 4000,
> +};
> +
> +static int common_ramp_table[] = {
> + 2, 250, 500, 1000, 2000, 4000, 8000, 16000,
> +};
> +
> +#define LM3532_MAX_CHANNELS 3
> +#define LM3631_MAX_CHANNELS 2
> +#define LM3632_MAX_CHANNELS 2
> +#define LM3633_MAX_CHANNELS 3
> +#define LM3695_MAX_CHANNELS 2
> +#define LM3697_MAX_CHANNELS 3
> +
> +const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID] = {
> + {
> + .reginfo = &lm3532_reg_info,
> + .num_channels = LM3532_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_8BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + .ramp_table = lm3532_ramp_table,
> + .size_ramp = ARRAY_SIZE(lm3532_ramp_table),
> + },
> + {
> + .reginfo = &lm3631_reg_info,
> + .num_channels = LM3631_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_ONLY,
> + .ramp_table = lm3631_ramp_table,
> + .size_ramp = ARRAY_SIZE(lm3631_ramp_table),
> + },
> + {
> + .reginfo = &lm3632_reg_info,
> + .num_channels = LM3632_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_ONLY,
> + },
> + {
> + .reginfo = &lm3633_reg_info,
> + .num_channels = LM3633_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_MAX_BRT,
> + .ramp_table = common_ramp_table,
> + .size_ramp = ARRAY_SIZE(common_ramp_table),
> + .fault_monitor_used = true,
> + },
> + {
> + .reginfo = &lm3695_reg_info,
> + .num_channels = LM3695_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + },
> + {
> + .reginfo = &lm3697_reg_info,
> + .num_channels = LM3697_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + .ramp_table = common_ramp_table,
> + .size_ramp = ARRAY_SIZE(common_ramp_table),
> + .fault_monitor_used = true,
> + },
> +};
> diff --git a/drivers/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 @@
> +/*
Inconsistent licensing here the core has
// SPDX-License-Identifier: GPL-2.0
> + * TI LMU (Lighting Management Unit) Backlight Device Data Definitions
> + *
> + * Copyright 2015 Texas Instruments
> + *
> + * Author: Milo Kim <milo.kim@ti.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#ifndef __TI_LMU_BACKLIGHT_H__
> +#define __TI_LMU_BACKLIGHT_H__
> +
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +
> +#define MAX_BRIGHTNESS_8BIT 255
> +#define MAX_BRIGHTNESS_11BIT 2047
> +
> +enum ti_lmu_bl_pwm_action {
> + /* Update PWM duty, no brightness register update is required */
> + UPDATE_PWM_ONLY,
> + /* Update not only duty but also brightness register */
> + UPDATE_PWM_AND_BRT_REGISTER,
> + /* Update max value in brightness registers */
> + UPDATE_MAX_BRT,
> +};
> +
> +struct lmu_bl_reg_data {
> + u8 reg;
> + u8 mask;
> + u8 val;
> +};
> +
> +/**
> + * struct ti_lmu_bl_reg
> + *
> + * @init: Device initialization registers
> + * @num_init: Numbers of initialization registers
> + * @channel: Backlight channel configuration registers
> + * @mode: Brightness control mode registers
> + * @ramp: Ramp registers for lighting effect
> + * @ramp_reg_offset: Ramp register offset.
> + * Only used for multiple ramp registers.
> + * @enable: Enable control register address
> + * @enable_usec: Delay time for updating enable register.
> + * Unit is microsecond.
> + * @brightness_msb: Brightness MSB(Upper 8 bits) registers.
> + * Concatenated with LSB in 11 bit dimming mode.
> + * In 8 bit dimming, only MSB is used.
> + * @brightness_lsb: Brightness LSB(Lower 3 bits) registers.
> + * Only valid in 11 bit dimming mode.
> + */
> +struct ti_lmu_bl_reg {
> + const struct lmu_bl_reg_data *init;
> + int num_init;
> + const struct lmu_bl_reg_data *channel;
> + const struct lmu_bl_reg_data *mode;
> + const struct lmu_bl_reg_data *ramp;
> + int ramp_reg_offset;
> + u8 *enable;
> + unsigned long enable_usec;
> + u8 *brightness_msb;
> + u8 *brightness_lsb;
> +};
> +
> +/**
> + * struct ti_lmu_bl_cfg
> + *
> + * @reginfo: Device register configuration
> + * @num_channels: Number of backlight channels
> + * @max_brightness: Max brightness value of backlight device
> + * @pwm_action: How to control brightness registers in PWM mode
> + * @ramp_table: [Optional] Ramp time table for lighting effect.
> + * It's used for searching approximate register index.
> + * @size_ramp: [Optional] Size of ramp table
> + * @fault_monitor_used: [Optional] Set true if the device needs to handle
> + * LMU fault monitor event.
> + *
> + * This structure is used for device specific data configuration.
> + */
> +struct ti_lmu_bl_cfg {
> + const struct ti_lmu_bl_reg *reginfo;
> + int num_channels;
> + int max_brightness;
> + enum ti_lmu_bl_pwm_action pwm_action;
> + int *ramp_table;
> + int size_ramp;
> + bool fault_monitor_used;
> +};
> +
> +extern const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID];
> +#endif
>
--
------------------
Dan Murphy
^ permalink raw reply [flat|nested] 62+ messages in thread* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
@ 2018-04-04 18:30 ` Dan Murphy
0 siblings, 0 replies; 62+ messages in thread
From: Dan Murphy @ 2018-04-04 18:30 UTC (permalink / raw)
To: Sebastian Reichel, Sebastian Reichel, Lee Jones, Daniel Thompson,
Rob Herring, Tony Lindgren
Cc: Jingoo Han, Mark Rutland, linux-kernel, linux-fbdev, devicetree,
linux-omap
Sebastian
-Milo Kim email is not valid
On 03/30/2018 12:24 PM, Sebastian Reichel wrote:
> This adds backlight support for the following TI LMU
> chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
>
> Signed-off-by: Milo Kim <milo.kim@ti.com>
> [add LED subsystem support for keyboard backlight and rework DT
> binding according to Rob Herrings feedback]
> Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> ---
> drivers/video/backlight/Kconfig | 7 +
> drivers/video/backlight/Makefile | 3 +
> drivers/video/backlight/ti-lmu-backlight-core.c | 666 ++++++++++++++++++++++++
> drivers/video/backlight/ti-lmu-backlight-data.c | 304 +++++++++++
> drivers/video/backlight/ti-lmu-backlight-data.h | 95 ++++
> 5 files changed, 1075 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 5d2d0d7e8100..27e6c5a0add8 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
select REGMAP?
> + 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 19da71d518bf..a1132d3dfd4c 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -53,6 +53,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..a6099581edd7
> --- /dev/null
> +++ b/drivers/video/backlight/ti-lmu-backlight-core.c
> @@ -0,0 +1,666 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2015 Texas Instruments
> + * Copyright 2018 Sebastian Reichel
> + *
> + * TI LMU Backlight driver, based on previous work from
> + * Milo Kim <milo.kim@ti.com>
> + */
> +
> +#include <linux/backlight.h>
> +#include <linux/bitops.h>
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/leds.h>
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +#include <linux/module.h>
> +#include <linux/notifier.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +
> +#include "ti-lmu-backlight-data.h"
> +
> +enum ti_lmu_bl_ctrl_mode {
> + BL_REGISTER_BASED,
> + BL_PWM_BASED,
> +};
> +
> +enum ti_lmu_bl_type {
> + TI_LMU_BL, /* backlight userspace interface */
> + TI_LMU_LED, /* led userspace interface */
> +};
> +
> +enum ti_lmu_bl_ramp_mode {
> + BL_RAMP_UP,
> + BL_RAMP_DOWN,
> +};
> +
> +#define DEFAULT_PWM_NAME "lmu-backlight"
I don't see this used.
> +#define NUM_DUAL_CHANNEL 2
> +#define LMU_BACKLIGHT_DUAL_CHANNEL_USED (BIT(0) | BIT(1))
> +#define LMU_BACKLIGHT_11BIT_LSB_MASK (BIT(0) | BIT(1) | BIT(2))
> +#define LMU_BACKLIGHT_11BIT_MSB_SHIFT 3
> +
Struct kerneldoc?
> +struct ti_lmu_bank {
> + struct device *dev;
> + int bank_id;
> + const struct ti_lmu_bl_cfg *cfg;
> + struct ti_lmu *lmu;
> + const char *label;
> + int leds;
> + int current_brightness;
> + u32 default_brightness;
> + u32 ramp_up_msec;
> + u32 ramp_down_msec;
> + u32 pwm_period;
> + enum ti_lmu_bl_ctrl_mode mode;
> + enum ti_lmu_bl_type type;
> +
> + struct notifier_block nb;
> +
> + struct backlight_device *backlight;
> + struct led_classdev *led;
> +};
> +
> +static int ti_lmu_bl_enable(struct ti_lmu_bank *lmu_bank, bool enable)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + unsigned long enable_time = lmu_bank->cfg->reginfo->enable_usec;
> + u8 *reg = lmu_bank->cfg->reginfo->enable;
> + u8 mask = BIT(lmu_bank->bank_id);
> + u8 val = (enable == true) ? mask : 0;
> + int ret;
> +
> + if (!reg)
> + return -EINVAL;
> +
> + ret = regmap_update_bits(regmap, *reg, mask, val);
> + if (ret)
> + return ret;
> +
> + if (enable_time > 0)
> + usleep_range(enable_time, enable_time + 100);
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_update_brightness_register(struct ti_lmu_bank *lmu_bank,
> + int brightness)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + u8 reg, val;
> + int ret;
> +
> + /*
> + * Brightness register update
> + *
> + * 11 bit dimming: update LSB bits and write MSB byte.
> + * MSB brightness should be shifted.
> + * 8 bit dimming: write MSB byte.
> + */
> + if (cfg->max_brightness == MAX_BRIGHTNESS_11BIT) {
> + reg = reginfo->brightness_lsb[lmu_bank->bank_id];
> + ret = regmap_update_bits(regmap, reg,
> + LMU_BACKLIGHT_11BIT_LSB_MASK,
> + brightness);
> + if (ret)
> + return ret;
> +
> + val = brightness >> LMU_BACKLIGHT_11BIT_MSB_SHIFT;
> + } else {
> + val = brightness;
> + }
> +
> + reg = reginfo->brightness_msb[lmu_bank->bank_id];
> + return regmap_write(regmap, reg, val);
> +}
> +
> +static int ti_lmu_bl_pwm_ctrl(struct ti_lmu_bank *lmu_bank, int brightness)
> +{
> + int max_brightness = lmu_bank->cfg->max_brightness;
> + struct pwm_state state = { };
> + int ret;
> +
> + if (!lmu_bank->lmu->pwm) {
> + dev_err(lmu_bank->dev, "Missing PWM device!\n");
> + return -ENODEV;
> + }
> +
> + pwm_init_state(lmu_bank->lmu->pwm, &state);
> + state.period = lmu_bank->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_bank->lmu->pwm, &state);
> + if (ret)
> + dev_err(lmu_bank->dev, "Failed to configure PWM: %d", ret);
> +
> + return ret;
> +}
> +
> +static int ti_lmu_bl_set_brightness(struct ti_lmu_bank *lmu_bank,
> + int brightness)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + bool enable = brightness > 0;
> + int ret;
> +
Is there anyway that update_status and set_led_blocking can be called at the same time causing
a race condition in this api?
> + ret = ti_lmu_bl_enable(lmu_bank, enable);
> + if (ret)
> + return ret;
> +
> + if (lmu_bank->mode == BL_PWM_BASED) {
> + ti_lmu_bl_pwm_ctrl(lmu_bank, 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;
> + }
> + }
> +
> + lmu_bank->current_brightness = brightness;
> +
> + return ti_lmu_bl_update_brightness_register(lmu_bank, brightness);
> +}
> +
> +static int ti_lmu_bl_update_status(struct backlight_device *bl_dev)
> +{
> + struct ti_lmu_bank *lmu_bank = bl_get_data(bl_dev);
> + int brightness = bl_dev->props.brightness;
> +
> + if (bl_dev->props.state & BL_CORE_SUSPENDED)
> + brightness = 0;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> +}
> +
> +static const struct backlight_ops lmu_backlight_ops = {
> + .options = BL_CORE_SUSPENDRESUME,
> + .update_status = ti_lmu_bl_update_status,
> +};
> +
> +static int ti_lmu_bl_set_led_blocking(struct led_classdev *ledc,
> + enum led_brightness value)
> +{
> + struct ti_lmu_bank *lmu_bank = dev_get_drvdata(ledc->dev->parent);
> + int brightness = value;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> +}
> +
> +static int ti_lmu_bl_check_channel(struct ti_lmu_bank *lmu_bank)
> +{
> + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> +
> + if (!reginfo->brightness_msb)
> + return -EINVAL;
> +
> + if (cfg->max_brightness > MAX_BRIGHTNESS_8BIT) {
> + if (!reginfo->brightness_lsb)
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_create_channel(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata = lmu_bank->cfg->reginfo->channel;
> + int num_channels = lmu_bank->cfg->num_channels;
> + unsigned long led_sources = lmu_bank->leds;
> + int i, ret;
> + u8 shift;
> +
> + /*
> + * How to create backlight output channels:
> + * Check 'led_sources' bit and update registers.
> + *
> + * 1) Dual channel configuration
> + * The 1st register data is used for single channel.
> + * The 2nd register data is used for dual channel.
> + *
> + * 2) Multiple channel configuration
> + * Each register data is mapped to bank ID.
> + * Bit shift operation is defined in channel registers.
> + *
> + * Channel register data consists of address, mask, value.
> + */
> +
> + if (num_channels == NUM_DUAL_CHANNEL) {
> + if (led_sources == LMU_BACKLIGHT_DUAL_CHANNEL_USED)
> + regdata++;
> +
> + return regmap_update_bits(regmap, regdata->reg, regdata->mask,
> + regdata->val);
> + }
> +
> + for (i = 0; regdata && i < num_channels; i++) {
> + /*
> + * Note that the result of regdata->val is shift bit.
> + * The bank_id should be shifted for the channel configuration.
> + */
> + if (test_bit(i, &led_sources)) {
> + shift = regdata->val;
> + ret = regmap_update_bits(regmap, regdata->reg,
> + regdata->mask,
> + lmu_bank->bank_id << shift);
> + if (ret)
> + return ret;
> + }
> +
> + regdata++;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_update_ctrl_mode(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata =
> + lmu_bank->cfg->reginfo->mode + lmu_bank->bank_id;
> + u8 val = regdata->val;
> +
> + if (!regdata)
> + return 0;
> +
> + /*
> + * Update PWM configuration register.
> + * If the mode is register based, then clear the bit.
> + */
> + if (lmu_bank->mode != BL_PWM_BASED)
> + val = 0;
> +
> + return regmap_update_bits(regmap, regdata->reg, regdata->mask, val);
> +}
> +
> +static int ti_lmu_bl_convert_ramp_to_index(struct ti_lmu_bank *lmu_bank,
> + enum ti_lmu_bl_ramp_mode mode)
> +{
> + const int *ramp_table = lmu_bank->cfg->ramp_table;
> + const int size = lmu_bank->cfg->size_ramp;
> + unsigned int msec;
> + int i;
> +
> + if (!ramp_table)
> + return -EINVAL;
> +
> + switch (mode) {
> + case BL_RAMP_UP:
> + msec = lmu_bank->ramp_up_msec;
> + break;
> + case BL_RAMP_DOWN:
> + msec = lmu_bank->ramp_down_msec;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (msec <= ramp_table[0])
> + return 0;
> +
> + if (msec > ramp_table[size - 1])
> + return size - 1;
> +
> + for (i = 1; i < size; i++) {
> + if (msec == ramp_table[i])
> + return i;
> +
> + /* Find an approximate index by looking up the table */
> + if (msec > ramp_table[i - 1] && msec < ramp_table[i]) {
> + if (msec - ramp_table[i - 1] < ramp_table[i] - msec)
> + return i - 1;
> + else
> + return i;
> + }
> + }
> +
> + return -EINVAL;
> +}
> +
> +
Extra new line
> +static int ti_lmu_bl_set_ramp(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct ti_lmu_bl_reg *reginfo = lmu_bank->cfg->reginfo;
> + int offset = reginfo->ramp_reg_offset;
> + int i, ret, index;
> + struct lmu_bl_reg_data regdata;
> +
> + for (i = BL_RAMP_UP; i <= BL_RAMP_DOWN; i++) {
> + index = ti_lmu_bl_convert_ramp_to_index(lmu_bank, i);
> + if (index > 0) {
> + if (!reginfo->ramp)
> + break;
> +
> + regdata = reginfo->ramp[i];
> + if (lmu_bank->bank_id != 0)
> + regdata.val += offset;
> +
> + /* regdata.val is shift bit */
> + ret = regmap_update_bits(regmap, regdata.reg,
> + regdata.mask,
> + index << regdata.val);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_configure(struct ti_lmu_bank *lmu_bank)
> +{
> + int ret;
> +
> + ret = ti_lmu_bl_check_channel(lmu_bank);
> + if (ret)
> + return ret;
> +
> + ret = ti_lmu_bl_create_channel(lmu_bank);
> + if (ret)
> + return ret;
> +
> + ret = ti_lmu_bl_update_ctrl_mode(lmu_bank);
> + if (ret)
> + return ret;
> +
> + return ti_lmu_bl_set_ramp(lmu_bank);
> +}
> +
> +static int ti_lmu_bl_register_backlight(struct ti_lmu_bank *lmu_bank)
> +{
> + struct backlight_device *bl_dev;
> + struct backlight_properties props;
> +
> + if (lmu_bank->type != TI_LMU_BL)
> + return -EINVAL;
> +
> + memset(&props, 0, sizeof(struct backlight_properties));
> + props.type = BACKLIGHT_PLATFORM;
> + props.brightness = lmu_bank->default_brightness;
> + props.max_brightness = lmu_bank->cfg->max_brightness;
> +
> + bl_dev = devm_backlight_device_register(lmu_bank->dev, lmu_bank->label,
> + lmu_bank->dev, lmu_bank,
> + &lmu_backlight_ops, &props);
> + if (IS_ERR(bl_dev))
> + return PTR_ERR(bl_dev);
> +
> + lmu_bank->backlight = bl_dev;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_register_led(struct ti_lmu_bank *lmu_bank)
> +{
> + int err;
> +
> + if (lmu_bank->type != TI_LMU_LED)
> + return -EINVAL;
> +
> + lmu_bank->led = devm_kzalloc(lmu_bank->dev, sizeof(*lmu_bank->led),
> + GFP_KERNEL);
> + if (!lmu_bank->led)
> + return -ENOMEM;
> +
> + lmu_bank->led->name = lmu_bank->label;
> + lmu_bank->led->max_brightness = lmu_bank->cfg->max_brightness;
> + lmu_bank->led->brightness_set_blocking =
> + ti_lmu_bl_set_led_blocking;
> +
> + err = devm_led_classdev_register(lmu_bank->dev, lmu_bank->led);
> + if (err)
> + return err;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
> +{
> + switch (lmu_bank->type) {
> + case TI_LMU_BL:
> + return ti_lmu_bl_register_backlight(lmu_bank);
> + case TI_LMU_LED:
> + return ti_lmu_bl_register_led(lmu_bank);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int setup_of_node(struct platform_device *pdev)
This seems a bit generic. Maybe keep with the naming convention
ti_lmu_setup_of_node
> +{
> + struct device_node *parent_node = pdev->dev.parent->of_node;
> + char *name;
> +
> + if (!parent_node)
> + return 0;
> +
> + name = kasprintf(GFP_KERNEL, "bank%d", pdev->id);
> + if (!name)
> + return -ENOMEM;
> +
> + pdev->dev.of_node = of_get_child_by_name(parent_node, name);
> + kfree(name);
> +
> + if (!pdev->dev.of_node)
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +static int ti_lmu_parse_led_sources(struct device *dev)
> +{
> + unsigned long mask = 0;
> + int ret;
> + int size, i;
> + u32 *leds;
> +
> + size = device_property_read_u32_array(dev, "ti,led-sources", NULL, 0);
> + if (size <= 0) {
> + dev_err(dev, "Missing or malformed property led-sources: %d\n",
> + size);
> + return size < 0 ? size : -EINVAL;
Why the additional check? Why not just return -EINVAL?
> + }
> +
> + leds = kmalloc_array(size, sizeof(u32), GFP_KERNEL);
> + if (!leds)
> + return -ENOMEM;
> +
> + ret = device_property_read_u32_array(dev, "ti,led-sources", leds, size);
> + if (ret) {
> + dev_err(dev, "Failed to read led-sources property: %d\n", ret);
> + goto out;
> + }
> +
> + for (i = 0; i < size; i++)
> + set_bit(leds[i], &mask);
> +
> + ret = mask;
> +
> +out:
> + kfree(leds);
> + return ret;
> +}
> +
> +static int ti_lmu_bl_init(struct ti_lmu_bank *lmu_bank)
> +{
> + struct regmap *regmap = lmu_bank->lmu->regmap;
> + const struct lmu_bl_reg_data *regdata =
> + lmu_bank->cfg->reginfo->init;
> + int num_init = lmu_bank->cfg->reginfo->num_init;
> + int i, ret;
> +
> + if (lmu_bank->lmu->backlight_initialized)
> + return 0;
Add a new line here
> + lmu_bank->lmu->backlight_initialized = true;
> +
> + for (i = 0; regdata && i < num_init; i++) {
> + ret = regmap_update_bits(regmap, regdata->reg, regdata->mask,
> + regdata->val);
> + if (ret)
> + return ret;
> +
> + regdata++;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_reload(struct ti_lmu_bank *lmu_bank)
> +{
> + int err;
> +
> + ti_lmu_bl_init(lmu_bank);
> +
> + err = ti_lmu_bl_configure(lmu_bank);
> + if (err)
> + return err;
> +
> + return ti_lmu_bl_set_brightness(lmu_bank, lmu_bank->current_brightness);
> +}
> +
> +static int ti_lmu_bl_monitor_notifier(struct notifier_block *nb,
> + unsigned long action, void *unused)
> +{
> + struct ti_lmu_bank *lmu_bank = container_of(nb, struct ti_lmu_bank, nb);
> +
> + if (action == LMU_EVENT_MONITOR_DONE) {
> + if (ti_lmu_bl_reload(lmu_bank))
> + return NOTIFY_STOP;
> + }
> +
> + return NOTIFY_OK;
> +}
> +
> +static int ti_lmu_bl_probe(struct platform_device *pdev)
> +{
> + struct ti_lmu_bank *lmu_bank;
> + int err;
> +
> + err = setup_of_node(pdev);
> + if (err)
> + return err;
> +
> + lmu_bank = devm_kzalloc(&pdev->dev, sizeof(*lmu_bank), GFP_KERNEL);
> + if (!lmu_bank)
> + return -ENOMEM;
Add a new line here
> + lmu_bank->dev = &pdev->dev;
> + dev_set_drvdata(&pdev->dev, lmu_bank);
> +
> + err = device_property_read_string(&pdev->dev, "label",
> + &lmu_bank->label);
> + if (err)
> + return err;
> +
> + lmu_bank->type = TI_LMU_BL;
> + if (!strcmp(lmu_bank->label, "keyboard")) {
> + lmu_bank->type = TI_LMU_LED;
> + lmu_bank->label = "kbd_backlight";
What is the reason for changing the label? Why can't the label in the DT be kbd_backlight?
> + }
> +
> + lmu_bank->leds = ti_lmu_parse_led_sources(&pdev->dev);
> + if (lmu_bank->leds < 0)
> + return lmu_bank->leds;
> + else if (lmu_bank->leds == 0)
> + return -EINVAL;
> +
> + device_property_read_u32(&pdev->dev, "default-brightness-level",
> + &lmu_bank->default_brightness);
> + device_property_read_u32(&pdev->dev, "ti,ramp-up-ms",
> + &lmu_bank->ramp_up_msec);
> + device_property_read_u32(&pdev->dev, "ti,ramp-down-ms",
> + &lmu_bank->ramp_down_msec);
> + device_property_read_u32(&pdev->dev, "pwm-period",
> + &lmu_bank->pwm_period);
> +
> + if (lmu_bank->pwm_period > 0)
> + lmu_bank->mode = BL_PWM_BASED;
> + else
> + lmu_bank->mode = BL_REGISTER_BASED;
> +
> + lmu_bank->lmu = dev_get_drvdata(pdev->dev.parent);
> + lmu_bank->cfg = &lmu_bl_cfg[lmu_bank->lmu->id];
> + lmu_bank->bank_id = pdev->id;
> +
> + ti_lmu_bl_init(lmu_bank);
> +
> + err = ti_lmu_bl_configure(lmu_bank);
> + if (err)
> + return err;
> +
> + err = ti_lmu_bl_add_device(lmu_bank);
> + if (err)
> + return err;
> +
> + err = ti_lmu_bl_set_brightness(lmu_bank,
> + lmu_bank->default_brightness);
> + if (err)
> + return err;
> +
> + /*
> + * Notifier callback is required because backlight device needs
> + * reconfiguration after fault detection procedure is done by
> + * ti-lmu-fault-monitor driver.
> + */
> + if (lmu_bank->cfg->fault_monitor_used) {
> + lmu_bank->nb.notifier_call = ti_lmu_bl_monitor_notifier;
> + err = blocking_notifier_chain_register(&lmu_bank->lmu->notifier,
> + &lmu_bank->nb);
> + if (err)
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int ti_lmu_bl_remove(struct platform_device *pdev)
> +{
> + struct ti_lmu_bank *lmu_bank = platform_get_drvdata(pdev);
> +
> + if (lmu_bank->cfg->fault_monitor_used)
> + blocking_notifier_chain_unregister(&lmu_bank->lmu->notifier,
> + &lmu_bank->nb);
> +
> + ti_lmu_bl_set_brightness(lmu_bank, 0);
> +
> + return 0;
> +}
> +
> +static struct platform_driver ti_lmu_bl_driver = {
> + .probe = ti_lmu_bl_probe,
> + .remove = ti_lmu_bl_remove,
> + .driver = {
> + .name = "ti-lmu-led-backlight",
> + },
> +};
> +module_platform_driver(ti_lmu_bl_driver)
> +
> +MODULE_DESCRIPTION("TI LMU Backlight LED Driver");
> +MODULE_AUTHOR("Sebastian Reichel");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:ti-lmu-led-backlight");
> diff --git a/drivers/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 @@
> +/*
Inconsistent licensing here the core has
// SPDX-License-Identifier: GPL-2.0
> + * TI LMU (Lighting Management Unit) Backlight Device Data
> + *
> + * Copyright 2015 Texas Instruments
Copyright?
> + *
> + * Author: Milo Kim <milo.kim@ti.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include "ti-lmu-backlight-data.h"
> +
> +/* LM3532 */
> +static const struct lmu_bl_reg_data lm3532_init_data[] = {
> + { LM3532_REG_ZONE_CFG_A, LM3532_ZONE_MASK, LM3532_ZONE_0 },
> + { LM3532_REG_ZONE_CFG_B, LM3532_ZONE_MASK, LM3532_ZONE_1 },
> + { LM3532_REG_ZONE_CFG_C, LM3532_ZONE_MASK, LM3532_ZONE_2 },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_channel_data[] = {
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED1_CFG_MASK,
> + LM3532_ILED1_CFG_SHIFT },
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED2_CFG_MASK,
> + LM3532_ILED2_CFG_SHIFT },
> + { LM3532_REG_OUTPUT_CFG, LM3532_ILED3_CFG_MASK,
> + LM3532_ILED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_mode_data[] = {
> + { LM3532_REG_PWM_A_CFG, LM3532_PWM_A_MASK, LM3532_PWM_ZONE_0 },
> + { LM3532_REG_PWM_B_CFG, LM3532_PWM_B_MASK, LM3532_PWM_ZONE_1 },
> + { LM3532_REG_PWM_C_CFG, LM3532_PWM_C_MASK, LM3532_PWM_ZONE_2 },
> +};
> +
> +static const struct lmu_bl_reg_data lm3532_ramp_data[] = {
> + { LM3532_REG_RAMPUP, LM3532_RAMPUP_MASK, LM3532_RAMPUP_SHIFT },
> + { LM3532_REG_RAMPDN, LM3532_RAMPDN_MASK, LM3532_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3532_enable_reg = LM3532_REG_ENABLE;
> +
> +static u8 lm3532_brightness_regs[] = {
> + LM3532_REG_BRT_A,
> + LM3532_REG_BRT_B,
> + LM3532_REG_BRT_C,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3532_reg_info = {
> + .init = lm3532_init_data,
> + .num_init = ARRAY_SIZE(lm3532_init_data),
> + .channel = lm3532_channel_data,
> + .mode = lm3532_mode_data,
> + .ramp = lm3532_ramp_data,
> + .enable = &lm3532_enable_reg,
> + .brightness_msb = lm3532_brightness_regs,
> +};
> +
> +/* LM3631 */
> +static const struct lmu_bl_reg_data lm3631_init_data[] = {
> + { LM3631_REG_BRT_MODE, LM3631_MODE_MASK, LM3631_DEFAULT_MODE },
> + { LM3631_REG_BL_CFG, LM3631_MAP_MASK, LM3631_EXPONENTIAL_MAP },
> +};
> +
> +static const struct lmu_bl_reg_data lm3631_channel_data[] = {
> + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_SINGLE_CHANNEL },
> + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_DUAL_CHANNEL },
> +};
> +
> +static const struct lmu_bl_reg_data lm3631_ramp_data[] = {
> + { LM3631_REG_SLOPE, LM3631_SLOPE_MASK, LM3631_SLOPE_SHIFT },
> +};
> +
> +static u8 lm3631_enable_reg = LM3631_REG_DEVCTRL;
> +static u8 lm3631_brightness_msb_reg = LM3631_REG_BRT_MSB;
> +static u8 lm3631_brightness_lsb_reg = LM3631_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3631_reg_info = {
> + .init = lm3631_init_data,
> + .num_init = ARRAY_SIZE(lm3631_init_data),
> + .channel = lm3631_channel_data,
> + .ramp = lm3631_ramp_data,
> + .enable = &lm3631_enable_reg,
> + .brightness_msb = &lm3631_brightness_msb_reg,
> + .brightness_lsb = &lm3631_brightness_lsb_reg,
> +};
> +
> +/* LM3632 */
> +static const struct lmu_bl_reg_data lm3632_init_data[] = {
> + { LM3632_REG_CONFIG1, LM3632_OVP_MASK, LM3632_OVP_25V },
> + { LM3632_REG_CONFIG2, LM3632_SWFREQ_MASK, LM3632_SWFREQ_1MHZ },
> +};
> +
> +static const struct lmu_bl_reg_data lm3632_channel_data[] = {
> + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_SINGLE_CHANNEL },
> + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_DUAL_CHANNEL },
> +};
> +
> +static const struct lmu_bl_reg_data lm3632_mode_data[] = {
> + { LM3632_REG_IO_CTRL, LM3632_PWM_MASK, LM3632_PWM_MODE },
> +};
> +
> +static u8 lm3632_enable_reg = LM3632_REG_ENABLE;
> +static u8 lm3632_brightness_msb_reg = LM3632_REG_BRT_MSB;
> +static u8 lm3632_brightness_lsb_reg = LM3632_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3632_reg_info = {
> + .init = lm3632_init_data,
> + .num_init = ARRAY_SIZE(lm3632_init_data),
> + .channel = lm3632_channel_data,
> + .mode = lm3632_mode_data,
> + .enable = &lm3632_enable_reg,
> + .brightness_msb = &lm3632_brightness_msb_reg,
> + .brightness_lsb = &lm3632_brightness_lsb_reg,
> +};
> +
> +/* LM3633 */
> +static const struct lmu_bl_reg_data lm3633_init_data[] = {
> + { LM3633_REG_BOOST_CFG, LM3633_OVP_MASK, LM3633_OVP_40V },
> + { LM3633_REG_BL_RAMP_CONF, LM3633_BL_RAMP_MASK, LM3633_BL_RAMP_EACH },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_channel_data[] = {
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED1_CFG_MASK,
> + LM3633_HVLED1_CFG_SHIFT },
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED2_CFG_MASK,
> + LM3633_HVLED2_CFG_SHIFT },
> + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED3_CFG_MASK,
> + LM3633_HVLED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_mode_data[] = {
> + { LM3633_REG_PWM_CFG, LM3633_PWM_A_MASK, LM3633_PWM_A_MASK },
> + { LM3633_REG_PWM_CFG, LM3633_PWM_B_MASK, LM3633_PWM_B_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3633_ramp_data[] = {
> + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPUP_MASK, LM3633_BL_RAMPUP_SHIFT },
> + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPDN_MASK, LM3633_BL_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3633_enable_reg = LM3633_REG_ENABLE;
> +
> +static u8 lm3633_brightness_msb_regs[] = {
> + LM3633_REG_BRT_HVLED_A_MSB,
> + LM3633_REG_BRT_HVLED_B_MSB,
> +};
> +
> +static u8 lm3633_brightness_lsb_regs[] = {
> + LM3633_REG_BRT_HVLED_A_LSB,
> + LM3633_REG_BRT_HVLED_B_LSB,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3633_reg_info = {
> + .init = lm3633_init_data,
> + .num_init = ARRAY_SIZE(lm3633_init_data),
> + .channel = lm3633_channel_data,
> + .mode = lm3633_mode_data,
> + .ramp = lm3633_ramp_data,
> + .ramp_reg_offset = 1, /* For LM3633_REG_BL1_RAMPUP/DN */
> + .enable = &lm3633_enable_reg,
> + .brightness_msb = lm3633_brightness_msb_regs,
> + .brightness_lsb = lm3633_brightness_lsb_regs,
> +};
> +
> +/* LM3695 */
> +static const struct lmu_bl_reg_data lm3695_init_data[] = {
> + { LM3695_REG_GP, LM3695_BRT_RW_MASK, LM3695_BRT_RW_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3695_channel_data[] = {
> + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_SINGLE_CHANNEL },
> + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_DUAL_CHANNEL },
> +};
> +
> +static u8 lm3695_enable_reg = LM3695_REG_GP;
> +static u8 lm3695_brightness_msb_reg = LM3695_REG_BRT_MSB;
> +static u8 lm3695_brightness_lsb_reg = LM3695_REG_BRT_LSB;
> +
> +static const struct ti_lmu_bl_reg lm3695_reg_info = {
> + .init = lm3695_init_data,
> + .num_init = ARRAY_SIZE(lm3695_init_data),
> + .channel = lm3695_channel_data,
> + .enable = &lm3695_enable_reg,
> + .enable_usec = 600,
> + .brightness_msb = &lm3695_brightness_msb_reg,
> + .brightness_lsb = &lm3695_brightness_lsb_reg,
> +};
> +
> +/* LM3697 */
> +static const struct lmu_bl_reg_data lm3697_init_data[] = {
> + { LM3697_REG_RAMP_CONF, LM3697_RAMP_MASK, LM3697_RAMP_EACH },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_channel_data[] = {
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED1_CFG_MASK,
> + LM3697_HVLED1_CFG_SHIFT },
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED2_CFG_MASK,
> + LM3697_HVLED2_CFG_SHIFT },
> + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED3_CFG_MASK,
> + LM3697_HVLED3_CFG_SHIFT },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_mode_data[] = {
> + { LM3697_REG_PWM_CFG, LM3697_PWM_A_MASK, LM3697_PWM_A_MASK },
> + { LM3697_REG_PWM_CFG, LM3697_PWM_B_MASK, LM3697_PWM_B_MASK },
> +};
> +
> +static const struct lmu_bl_reg_data lm3697_ramp_data[] = {
> + { LM3697_REG_BL0_RAMP, LM3697_RAMPUP_MASK, LM3697_RAMPUP_SHIFT },
> + { LM3697_REG_BL0_RAMP, LM3697_RAMPDN_MASK, LM3697_RAMPDN_SHIFT },
> +};
> +
> +static u8 lm3697_enable_reg = LM3697_REG_ENABLE;
> +
> +static u8 lm3697_brightness_msb_regs[] = {
> + LM3697_REG_BRT_A_MSB,
> + LM3697_REG_BRT_B_MSB,
> +};
> +
> +static u8 lm3697_brightness_lsb_regs[] = {
> + LM3697_REG_BRT_A_LSB,
> + LM3697_REG_BRT_B_LSB,
> +};
> +
> +static const struct ti_lmu_bl_reg lm3697_reg_info = {
> + .init = lm3697_init_data,
> + .num_init = ARRAY_SIZE(lm3697_init_data),
> + .channel = lm3697_channel_data,
> + .mode = lm3697_mode_data,
> + .ramp = lm3697_ramp_data,
> + .ramp_reg_offset = 1, /* For LM3697_REG_BL1_RAMPUP/DN */
> + .enable = &lm3697_enable_reg,
> + .brightness_msb = lm3697_brightness_msb_regs,
> + .brightness_lsb = lm3697_brightness_lsb_regs,
> +};
> +
> +static int lm3532_ramp_table[] = { 0, 1, 2, 4, 8, 16, 32, 65 };
> +
> +static int lm3631_ramp_table[] = {
> + 0, 1, 2, 5, 10, 20, 50, 100,
> + 250, 500, 750, 1000, 1500, 2000, 3000, 4000,
> +};
> +
> +static int common_ramp_table[] = {
> + 2, 250, 500, 1000, 2000, 4000, 8000, 16000,
> +};
> +
> +#define LM3532_MAX_CHANNELS 3
> +#define LM3631_MAX_CHANNELS 2
> +#define LM3632_MAX_CHANNELS 2
> +#define LM3633_MAX_CHANNELS 3
> +#define LM3695_MAX_CHANNELS 2
> +#define LM3697_MAX_CHANNELS 3
> +
> +const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID] = {
> + {
> + .reginfo = &lm3532_reg_info,
> + .num_channels = LM3532_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_8BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + .ramp_table = lm3532_ramp_table,
> + .size_ramp = ARRAY_SIZE(lm3532_ramp_table),
> + },
> + {
> + .reginfo = &lm3631_reg_info,
> + .num_channels = LM3631_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_ONLY,
> + .ramp_table = lm3631_ramp_table,
> + .size_ramp = ARRAY_SIZE(lm3631_ramp_table),
> + },
> + {
> + .reginfo = &lm3632_reg_info,
> + .num_channels = LM3632_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_ONLY,
> + },
> + {
> + .reginfo = &lm3633_reg_info,
> + .num_channels = LM3633_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_MAX_BRT,
> + .ramp_table = common_ramp_table,
> + .size_ramp = ARRAY_SIZE(common_ramp_table),
> + .fault_monitor_used = true,
> + },
> + {
> + .reginfo = &lm3695_reg_info,
> + .num_channels = LM3695_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + },
> + {
> + .reginfo = &lm3697_reg_info,
> + .num_channels = LM3697_MAX_CHANNELS,
> + .max_brightness = MAX_BRIGHTNESS_11BIT,
> + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> + .ramp_table = common_ramp_table,
> + .size_ramp = ARRAY_SIZE(common_ramp_table),
> + .fault_monitor_used = true,
> + },
> +};
> diff --git a/drivers/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 @@
> +/*
Inconsistent licensing here the core has
// SPDX-License-Identifier: GPL-2.0
> + * TI LMU (Lighting Management Unit) Backlight Device Data Definitions
> + *
> + * Copyright 2015 Texas Instruments
> + *
> + * Author: Milo Kim <milo.kim@ti.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#ifndef __TI_LMU_BACKLIGHT_H__
> +#define __TI_LMU_BACKLIGHT_H__
> +
> +#include <linux/mfd/ti-lmu.h>
> +#include <linux/mfd/ti-lmu-register.h>
> +
> +#define MAX_BRIGHTNESS_8BIT 255
> +#define MAX_BRIGHTNESS_11BIT 2047
> +
> +enum ti_lmu_bl_pwm_action {
> + /* Update PWM duty, no brightness register update is required */
> + UPDATE_PWM_ONLY,
> + /* Update not only duty but also brightness register */
> + UPDATE_PWM_AND_BRT_REGISTER,
> + /* Update max value in brightness registers */
> + UPDATE_MAX_BRT,
> +};
> +
> +struct lmu_bl_reg_data {
> + u8 reg;
> + u8 mask;
> + u8 val;
> +};
> +
> +/**
> + * struct ti_lmu_bl_reg
> + *
> + * @init: Device initialization registers
> + * @num_init: Numbers of initialization registers
> + * @channel: Backlight channel configuration registers
> + * @mode: Brightness control mode registers
> + * @ramp: Ramp registers for lighting effect
> + * @ramp_reg_offset: Ramp register offset.
> + * Only used for multiple ramp registers.
> + * @enable: Enable control register address
> + * @enable_usec: Delay time for updating enable register.
> + * Unit is microsecond.
> + * @brightness_msb: Brightness MSB(Upper 8 bits) registers.
> + * Concatenated with LSB in 11 bit dimming mode.
> + * In 8 bit dimming, only MSB is used.
> + * @brightness_lsb: Brightness LSB(Lower 3 bits) registers.
> + * Only valid in 11 bit dimming mode.
> + */
> +struct ti_lmu_bl_reg {
> + const struct lmu_bl_reg_data *init;
> + int num_init;
> + const struct lmu_bl_reg_data *channel;
> + const struct lmu_bl_reg_data *mode;
> + const struct lmu_bl_reg_data *ramp;
> + int ramp_reg_offset;
> + u8 *enable;
> + unsigned long enable_usec;
> + u8 *brightness_msb;
> + u8 *brightness_lsb;
> +};
> +
> +/**
> + * struct ti_lmu_bl_cfg
> + *
> + * @reginfo: Device register configuration
> + * @num_channels: Number of backlight channels
> + * @max_brightness: Max brightness value of backlight device
> + * @pwm_action: How to control brightness registers in PWM mode
> + * @ramp_table: [Optional] Ramp time table for lighting effect.
> + * It's used for searching approximate register index.
> + * @size_ramp: [Optional] Size of ramp table
> + * @fault_monitor_used: [Optional] Set true if the device needs to handle
> + * LMU fault monitor event.
> + *
> + * This structure is used for device specific data configuration.
> + */
> +struct ti_lmu_bl_cfg {
> + const struct ti_lmu_bl_reg *reginfo;
> + int num_channels;
> + int max_brightness;
> + enum ti_lmu_bl_pwm_action pwm_action;
> + int *ramp_table;
> + int size_ramp;
> + bool fault_monitor_used;
> +};
> +
> +extern const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID];
> +#endif
>
--
------------------
Dan Murphy
^ permalink raw reply [flat|nested] 62+ messages in thread* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
2018-04-04 18:30 ` Dan Murphy
@ 2018-04-09 16:11 ` Sebastian Reichel
-1 siblings, 0 replies; 62+ messages in thread
From: Sebastian Reichel @ 2018-04-09 16:11 UTC (permalink / raw)
To: Dan Murphy
Cc: Lee Jones, Daniel Thompson, Rob Herring, Tony Lindgren,
Jingoo Han, Mark Rutland, linux-kernel, linux-fbdev, devicetree,
linux-omap
[-- Attachment #1: Type: text/plain, Size: 39324 bytes --]
Hi,
On Wed, Apr 04, 2018 at 01:30:37PM -0500, Dan Murphy wrote:
> Sebastian
>
> -Milo Kim email is not valid
Thanks for your review! Milo was CC'd, since git took over the
SoB. I will make sure to avoid it next time.
> On 03/30/2018 12:24 PM, Sebastian Reichel wrote:
> > This adds backlight support for the following TI LMU
> > chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
> >
> > Signed-off-by: Milo Kim <milo.kim@ti.com>
> > [add LED subsystem support for keyboard backlight and rework DT
> > binding according to Rob Herrings feedback]
> > Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> > ---
> > drivers/video/backlight/Kconfig | 7 +
> > drivers/video/backlight/Makefile | 3 +
> > drivers/video/backlight/ti-lmu-backlight-core.c | 666 ++++++++++++++++++++++++
> > drivers/video/backlight/ti-lmu-backlight-data.c | 304 +++++++++++
> > drivers/video/backlight/ti-lmu-backlight-data.h | 95 ++++
> > 5 files changed, 1075 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 5d2d0d7e8100..27e6c5a0add8 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
>
> select REGMAP?
Right.
> > + 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 19da71d518bf..a1132d3dfd4c 100644
> > --- a/drivers/video/backlight/Makefile
> > +++ b/drivers/video/backlight/Makefile
> > @@ -53,6 +53,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..a6099581edd7
> > --- /dev/null
> > +++ b/drivers/video/backlight/ti-lmu-backlight-core.c
> > @@ -0,0 +1,666 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright 2015 Texas Instruments
> > + * Copyright 2018 Sebastian Reichel
> > + *
> > + * TI LMU Backlight driver, based on previous work from
> > + * Milo Kim <milo.kim@ti.com>
> > + */
> > +
> > +#include <linux/backlight.h>
> > +#include <linux/bitops.h>
> > +#include <linux/device.h>
> > +#include <linux/delay.h>
> > +#include <linux/err.h>
> > +#include <linux/leds.h>
> > +#include <linux/mfd/ti-lmu.h>
> > +#include <linux/mfd/ti-lmu-register.h>
> > +#include <linux/module.h>
> > +#include <linux/notifier.h>
> > +#include <linux/of_device.h>
> > +#include <linux/platform_device.h>
> > +
> > +#include "ti-lmu-backlight-data.h"
> > +
> > +enum ti_lmu_bl_ctrl_mode {
> > + BL_REGISTER_BASED,
> > + BL_PWM_BASED,
> > +};
> > +
> > +enum ti_lmu_bl_type {
> > + TI_LMU_BL, /* backlight userspace interface */
> > + TI_LMU_LED, /* led userspace interface */
> > +};
> > +
> > +enum ti_lmu_bl_ramp_mode {
> > + BL_RAMP_UP,
> > + BL_RAMP_DOWN,
> > +};
> > +
> > +#define DEFAULT_PWM_NAME "lmu-backlight"
>
> I don't see this used.
I guess I can remove this :)
> > +#define NUM_DUAL_CHANNEL 2
> > +#define LMU_BACKLIGHT_DUAL_CHANNEL_USED (BIT(0) | BIT(1))
> > +#define LMU_BACKLIGHT_11BIT_LSB_MASK (BIT(0) | BIT(1) | BIT(2))
> > +#define LMU_BACKLIGHT_11BIT_MSB_SHIFT 3
> > +
>
> Struct kerneldoc?
Ok.
> > +struct ti_lmu_bank {
> > + struct device *dev;
> > + int bank_id;
> > + const struct ti_lmu_bl_cfg *cfg;
> > + struct ti_lmu *lmu;
> > + const char *label;
> > + int leds;
> > + int current_brightness;
> > + u32 default_brightness;
> > + u32 ramp_up_msec;
> > + u32 ramp_down_msec;
> > + u32 pwm_period;
> > + enum ti_lmu_bl_ctrl_mode mode;
> > + enum ti_lmu_bl_type type;
> > +
> > + struct notifier_block nb;
> > +
> > + struct backlight_device *backlight;
> > + struct led_classdev *led;
> > +};
> > +
> > +static int ti_lmu_bl_enable(struct ti_lmu_bank *lmu_bank, bool enable)
> > +{
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + unsigned long enable_time = lmu_bank->cfg->reginfo->enable_usec;
> > + u8 *reg = lmu_bank->cfg->reginfo->enable;
> > + u8 mask = BIT(lmu_bank->bank_id);
> > + u8 val = (enable == true) ? mask : 0;
> > + int ret;
> > +
> > + if (!reg)
> > + return -EINVAL;
> > +
> > + ret = regmap_update_bits(regmap, *reg, mask, val);
> > + if (ret)
> > + return ret;
> > +
> > + if (enable_time > 0)
> > + usleep_range(enable_time, enable_time + 100);
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_update_brightness_register(struct ti_lmu_bank *lmu_bank,
> > + int brightness)
> > +{
> > + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> > + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + u8 reg, val;
> > + int ret;
> > +
> > + /*
> > + * Brightness register update
> > + *
> > + * 11 bit dimming: update LSB bits and write MSB byte.
> > + * MSB brightness should be shifted.
> > + * 8 bit dimming: write MSB byte.
> > + */
> > + if (cfg->max_brightness == MAX_BRIGHTNESS_11BIT) {
> > + reg = reginfo->brightness_lsb[lmu_bank->bank_id];
> > + ret = regmap_update_bits(regmap, reg,
> > + LMU_BACKLIGHT_11BIT_LSB_MASK,
> > + brightness);
> > + if (ret)
> > + return ret;
> > +
> > + val = brightness >> LMU_BACKLIGHT_11BIT_MSB_SHIFT;
> > + } else {
> > + val = brightness;
> > + }
> > +
> > + reg = reginfo->brightness_msb[lmu_bank->bank_id];
> > + return regmap_write(regmap, reg, val);
> > +}
> > +
> > +static int ti_lmu_bl_pwm_ctrl(struct ti_lmu_bank *lmu_bank, int brightness)
> > +{
> > + int max_brightness = lmu_bank->cfg->max_brightness;
> > + struct pwm_state state = { };
> > + int ret;
> > +
> > + if (!lmu_bank->lmu->pwm) {
> > + dev_err(lmu_bank->dev, "Missing PWM device!\n");
> > + return -ENODEV;
> > + }
> > +
> > + pwm_init_state(lmu_bank->lmu->pwm, &state);
> > + state.period = lmu_bank->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_bank->lmu->pwm, &state);
> > + if (ret)
> > + dev_err(lmu_bank->dev, "Failed to configure PWM: %d", ret);
> > +
> > + return ret;
> > +}
> > +
> > +static int ti_lmu_bl_set_brightness(struct ti_lmu_bank *lmu_bank,
> > + int brightness)
> > +{
> > + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> > + bool enable = brightness > 0;
> > + int ret;
> > +
>
> Is there anyway that update_status and set_led_blocking can be
> called at the same time causing a race condition in this api?
Each bank is either exclusively exposed via LED or via backlight
subsystem.
> > + ret = ti_lmu_bl_enable(lmu_bank, enable);
> > + if (ret)
> > + return ret;
> > +
> > + if (lmu_bank->mode == BL_PWM_BASED) {
> > + ti_lmu_bl_pwm_ctrl(lmu_bank, 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;
> > + }
> > + }
> > +
> > + lmu_bank->current_brightness = brightness;
> > +
> > + return ti_lmu_bl_update_brightness_register(lmu_bank, brightness);
> > +}
> > +
> > +static int ti_lmu_bl_update_status(struct backlight_device *bl_dev)
> > +{
> > + struct ti_lmu_bank *lmu_bank = bl_get_data(bl_dev);
> > + int brightness = bl_dev->props.brightness;
> > +
> > + if (bl_dev->props.state & BL_CORE_SUSPENDED)
> > + brightness = 0;
> > +
> > + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> > +}
> > +
> > +static const struct backlight_ops lmu_backlight_ops = {
> > + .options = BL_CORE_SUSPENDRESUME,
> > + .update_status = ti_lmu_bl_update_status,
> > +};
> > +
> > +static int ti_lmu_bl_set_led_blocking(struct led_classdev *ledc,
> > + enum led_brightness value)
> > +{
> > + struct ti_lmu_bank *lmu_bank = dev_get_drvdata(ledc->dev->parent);
> > + int brightness = value;
> > +
> > + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> > +}
> > +
> > +static int ti_lmu_bl_check_channel(struct ti_lmu_bank *lmu_bank)
> > +{
> > + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> > + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> > +
> > + if (!reginfo->brightness_msb)
> > + return -EINVAL;
> > +
> > + if (cfg->max_brightness > MAX_BRIGHTNESS_8BIT) {
> > + if (!reginfo->brightness_lsb)
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_create_channel(struct ti_lmu_bank *lmu_bank)
> > +{
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + const struct lmu_bl_reg_data *regdata = lmu_bank->cfg->reginfo->channel;
> > + int num_channels = lmu_bank->cfg->num_channels;
> > + unsigned long led_sources = lmu_bank->leds;
> > + int i, ret;
> > + u8 shift;
> > +
> > + /*
> > + * How to create backlight output channels:
> > + * Check 'led_sources' bit and update registers.
> > + *
> > + * 1) Dual channel configuration
> > + * The 1st register data is used for single channel.
> > + * The 2nd register data is used for dual channel.
> > + *
> > + * 2) Multiple channel configuration
> > + * Each register data is mapped to bank ID.
> > + * Bit shift operation is defined in channel registers.
> > + *
> > + * Channel register data consists of address, mask, value.
> > + */
> > +
> > + if (num_channels == NUM_DUAL_CHANNEL) {
> > + if (led_sources == LMU_BACKLIGHT_DUAL_CHANNEL_USED)
> > + regdata++;
> > +
> > + return regmap_update_bits(regmap, regdata->reg, regdata->mask,
> > + regdata->val);
> > + }
> > +
> > + for (i = 0; regdata && i < num_channels; i++) {
> > + /*
> > + * Note that the result of regdata->val is shift bit.
> > + * The bank_id should be shifted for the channel configuration.
> > + */
> > + if (test_bit(i, &led_sources)) {
> > + shift = regdata->val;
> > + ret = regmap_update_bits(regmap, regdata->reg,
> > + regdata->mask,
> > + lmu_bank->bank_id << shift);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + regdata++;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_update_ctrl_mode(struct ti_lmu_bank *lmu_bank)
> > +{
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + const struct lmu_bl_reg_data *regdata =
> > + lmu_bank->cfg->reginfo->mode + lmu_bank->bank_id;
> > + u8 val = regdata->val;
> > +
> > + if (!regdata)
> > + return 0;
> > +
> > + /*
> > + * Update PWM configuration register.
> > + * If the mode is register based, then clear the bit.
> > + */
> > + if (lmu_bank->mode != BL_PWM_BASED)
> > + val = 0;
> > +
> > + return regmap_update_bits(regmap, regdata->reg, regdata->mask, val);
> > +}
> > +
> > +static int ti_lmu_bl_convert_ramp_to_index(struct ti_lmu_bank *lmu_bank,
> > + enum ti_lmu_bl_ramp_mode mode)
> > +{
> > + const int *ramp_table = lmu_bank->cfg->ramp_table;
> > + const int size = lmu_bank->cfg->size_ramp;
> > + unsigned int msec;
> > + int i;
> > +
> > + if (!ramp_table)
> > + return -EINVAL;
> > +
> > + switch (mode) {
> > + case BL_RAMP_UP:
> > + msec = lmu_bank->ramp_up_msec;
> > + break;
> > + case BL_RAMP_DOWN:
> > + msec = lmu_bank->ramp_down_msec;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + if (msec <= ramp_table[0])
> > + return 0;
> > +
> > + if (msec > ramp_table[size - 1])
> > + return size - 1;
> > +
> > + for (i = 1; i < size; i++) {
> > + if (msec == ramp_table[i])
> > + return i;
> > +
> > + /* Find an approximate index by looking up the table */
> > + if (msec > ramp_table[i - 1] && msec < ramp_table[i]) {
> > + if (msec - ramp_table[i - 1] < ramp_table[i] - msec)
> > + return i - 1;
> > + else
> > + return i;
> > + }
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +
>
> Extra new line
oops.
>
> > +static int ti_lmu_bl_set_ramp(struct ti_lmu_bank *lmu_bank)
> > +{
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + const struct ti_lmu_bl_reg *reginfo = lmu_bank->cfg->reginfo;
> > + int offset = reginfo->ramp_reg_offset;
> > + int i, ret, index;
> > + struct lmu_bl_reg_data regdata;
> > +
> > + for (i = BL_RAMP_UP; i <= BL_RAMP_DOWN; i++) {
> > + index = ti_lmu_bl_convert_ramp_to_index(lmu_bank, i);
> > + if (index > 0) {
> > + if (!reginfo->ramp)
> > + break;
> > +
> > + regdata = reginfo->ramp[i];
> > + if (lmu_bank->bank_id != 0)
> > + regdata.val += offset;
> > +
> > + /* regdata.val is shift bit */
> > + ret = regmap_update_bits(regmap, regdata.reg,
> > + regdata.mask,
> > + index << regdata.val);
> > + if (ret)
> > + return ret;
> > + }
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_configure(struct ti_lmu_bank *lmu_bank)
> > +{
> > + int ret;
> > +
> > + ret = ti_lmu_bl_check_channel(lmu_bank);
> > + if (ret)
> > + return ret;
> > +
> > + ret = ti_lmu_bl_create_channel(lmu_bank);
> > + if (ret)
> > + return ret;
> > +
> > + ret = ti_lmu_bl_update_ctrl_mode(lmu_bank);
> > + if (ret)
> > + return ret;
> > +
> > + return ti_lmu_bl_set_ramp(lmu_bank);
> > +}
> > +
> > +static int ti_lmu_bl_register_backlight(struct ti_lmu_bank *lmu_bank)
> > +{
> > + struct backlight_device *bl_dev;
> > + struct backlight_properties props;
> > +
> > + if (lmu_bank->type != TI_LMU_BL)
> > + return -EINVAL;
> > +
> > + memset(&props, 0, sizeof(struct backlight_properties));
> > + props.type = BACKLIGHT_PLATFORM;
> > + props.brightness = lmu_bank->default_brightness;
> > + props.max_brightness = lmu_bank->cfg->max_brightness;
> > +
> > + bl_dev = devm_backlight_device_register(lmu_bank->dev, lmu_bank->label,
> > + lmu_bank->dev, lmu_bank,
> > + &lmu_backlight_ops, &props);
> > + if (IS_ERR(bl_dev))
> > + return PTR_ERR(bl_dev);
> > +
> > + lmu_bank->backlight = bl_dev;
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_register_led(struct ti_lmu_bank *lmu_bank)
> > +{
> > + int err;
> > +
> > + if (lmu_bank->type != TI_LMU_LED)
> > + return -EINVAL;
> > +
> > + lmu_bank->led = devm_kzalloc(lmu_bank->dev, sizeof(*lmu_bank->led),
> > + GFP_KERNEL);
> > + if (!lmu_bank->led)
> > + return -ENOMEM;
> > +
> > + lmu_bank->led->name = lmu_bank->label;
> > + lmu_bank->led->max_brightness = lmu_bank->cfg->max_brightness;
> > + lmu_bank->led->brightness_set_blocking =
> > + ti_lmu_bl_set_led_blocking;
> > +
> > + err = devm_led_classdev_register(lmu_bank->dev, lmu_bank->led);
> > + if (err)
> > + return err;
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
> > +{
> > + switch (lmu_bank->type) {
> > + case TI_LMU_BL:
> > + return ti_lmu_bl_register_backlight(lmu_bank);
> > + case TI_LMU_LED:
> > + return ti_lmu_bl_register_led(lmu_bank);
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int setup_of_node(struct platform_device *pdev)
>
> This seems a bit generic. Maybe keep with the naming convention
>
> ti_lmu_setup_of_node
right.
> > +{
> > + struct device_node *parent_node = pdev->dev.parent->of_node;
> > + char *name;
> > +
> > + if (!parent_node)
> > + return 0;
> > +
> > + name = kasprintf(GFP_KERNEL, "bank%d", pdev->id);
> > + if (!name)
> > + return -ENOMEM;
> > +
> > + pdev->dev.of_node = of_get_child_by_name(parent_node, name);
> > + kfree(name);
> > +
> > + if (!pdev->dev.of_node)
> > + return -ENODEV;
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_parse_led_sources(struct device *dev)
> > +{
> > + unsigned long mask = 0;
> > + int ret;
> > + int size, i;
> > + u32 *leds;
> > +
> > + size = device_property_read_u32_array(dev, "ti,led-sources", NULL, 0);
> > + if (size <= 0) {
> > + dev_err(dev, "Missing or malformed property led-sources: %d\n",
> > + size);
> > + return size < 0 ? size : -EINVAL;
>
> Why the additional check? Why not just return -EINVAL?
I think it makes sense to pass the actual error code, but I can
simplify it when you insist.
> > + }
> > +
> > + leds = kmalloc_array(size, sizeof(u32), GFP_KERNEL);
> > + if (!leds)
> > + return -ENOMEM;
> > +
> > + ret = device_property_read_u32_array(dev, "ti,led-sources", leds, size);
> > + if (ret) {
> > + dev_err(dev, "Failed to read led-sources property: %d\n", ret);
> > + goto out;
> > + }
> > +
> > + for (i = 0; i < size; i++)
> > + set_bit(leds[i], &mask);
> > +
> > + ret = mask;
> > +
> > +out:
> > + kfree(leds);
> > + return ret;
> > +}
> > +
> > +static int ti_lmu_bl_init(struct ti_lmu_bank *lmu_bank)
> > +{
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + const struct lmu_bl_reg_data *regdata =
> > + lmu_bank->cfg->reginfo->init;
> > + int num_init = lmu_bank->cfg->reginfo->num_init;
> > + int i, ret;
> > +
> > + if (lmu_bank->lmu->backlight_initialized)
> > + return 0;
>
> Add a new line here
ok
> > + lmu_bank->lmu->backlight_initialized = true;
> > +
> > + for (i = 0; regdata && i < num_init; i++) {
> > + ret = regmap_update_bits(regmap, regdata->reg, regdata->mask,
> > + regdata->val);
> > + if (ret)
> > + return ret;
> > +
> > + regdata++;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_reload(struct ti_lmu_bank *lmu_bank)
> > +{
> > + int err;
> > +
> > + ti_lmu_bl_init(lmu_bank);
> > +
> > + err = ti_lmu_bl_configure(lmu_bank);
> > + if (err)
> > + return err;
> > +
> > + return ti_lmu_bl_set_brightness(lmu_bank, lmu_bank->current_brightness);
> > +}
> > +
> > +static int ti_lmu_bl_monitor_notifier(struct notifier_block *nb,
> > + unsigned long action, void *unused)
> > +{
> > + struct ti_lmu_bank *lmu_bank = container_of(nb, struct ti_lmu_bank, nb);
> > +
> > + if (action == LMU_EVENT_MONITOR_DONE) {
> > + if (ti_lmu_bl_reload(lmu_bank))
> > + return NOTIFY_STOP;
> > + }
> > +
> > + return NOTIFY_OK;
> > +}
> > +
> > +static int ti_lmu_bl_probe(struct platform_device *pdev)
> > +{
> > + struct ti_lmu_bank *lmu_bank;
> > + int err;
> > +
> > + err = setup_of_node(pdev);
> > + if (err)
> > + return err;
> > +
> > + lmu_bank = devm_kzalloc(&pdev->dev, sizeof(*lmu_bank), GFP_KERNEL);
> > + if (!lmu_bank)
> > + return -ENOMEM;
>
> Add a new line here
ok
> > + lmu_bank->dev = &pdev->dev;
> > + dev_set_drvdata(&pdev->dev, lmu_bank);
> > +
> > + err = device_property_read_string(&pdev->dev, "label",
> > + &lmu_bank->label);
> > + if (err)
> > + return err;
> > +
> > + lmu_bank->type = TI_LMU_BL;
> > + if (!strcmp(lmu_bank->label, "keyboard")) {
> > + lmu_bank->type = TI_LMU_LED;
> > + lmu_bank->label = "kbd_backlight";
>
> What is the reason for changing the label? Why can't the label in
> the DT be kbd_backlight?
It can, I tried to avoid Linuxism in DT.
> > + }
> > +
> > + lmu_bank->leds = ti_lmu_parse_led_sources(&pdev->dev);
> > + if (lmu_bank->leds < 0)
> > + return lmu_bank->leds;
> > + else if (lmu_bank->leds == 0)
> > + return -EINVAL;
> > +
> > + device_property_read_u32(&pdev->dev, "default-brightness-level",
> > + &lmu_bank->default_brightness);
> > + device_property_read_u32(&pdev->dev, "ti,ramp-up-ms",
> > + &lmu_bank->ramp_up_msec);
> > + device_property_read_u32(&pdev->dev, "ti,ramp-down-ms",
> > + &lmu_bank->ramp_down_msec);
> > + device_property_read_u32(&pdev->dev, "pwm-period",
> > + &lmu_bank->pwm_period);
> > +
> > + if (lmu_bank->pwm_period > 0)
> > + lmu_bank->mode = BL_PWM_BASED;
> > + else
> > + lmu_bank->mode = BL_REGISTER_BASED;
> > +
> > + lmu_bank->lmu = dev_get_drvdata(pdev->dev.parent);
> > + lmu_bank->cfg = &lmu_bl_cfg[lmu_bank->lmu->id];
> > + lmu_bank->bank_id = pdev->id;
> > +
> > + ti_lmu_bl_init(lmu_bank);
> > +
> > + err = ti_lmu_bl_configure(lmu_bank);
> > + if (err)
> > + return err;
> > +
> > + err = ti_lmu_bl_add_device(lmu_bank);
> > + if (err)
> > + return err;
> > +
> > + err = ti_lmu_bl_set_brightness(lmu_bank,
> > + lmu_bank->default_brightness);
> > + if (err)
> > + return err;
> > +
> > + /*
> > + * Notifier callback is required because backlight device needs
> > + * reconfiguration after fault detection procedure is done by
> > + * ti-lmu-fault-monitor driver.
> > + */
> > + if (lmu_bank->cfg->fault_monitor_used) {
> > + lmu_bank->nb.notifier_call = ti_lmu_bl_monitor_notifier;
> > + err = blocking_notifier_chain_register(&lmu_bank->lmu->notifier,
> > + &lmu_bank->nb);
> > + if (err)
> > + return err;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_remove(struct platform_device *pdev)
> > +{
> > + struct ti_lmu_bank *lmu_bank = platform_get_drvdata(pdev);
> > +
> > + if (lmu_bank->cfg->fault_monitor_used)
> > + blocking_notifier_chain_unregister(&lmu_bank->lmu->notifier,
> > + &lmu_bank->nb);
> > +
> > + ti_lmu_bl_set_brightness(lmu_bank, 0);
> > +
> > + return 0;
> > +}
> > +
> > +static struct platform_driver ti_lmu_bl_driver = {
> > + .probe = ti_lmu_bl_probe,
> > + .remove = ti_lmu_bl_remove,
> > + .driver = {
> > + .name = "ti-lmu-led-backlight",
> > + },
> > +};
> > +module_platform_driver(ti_lmu_bl_driver)
> > +
> > +MODULE_DESCRIPTION("TI LMU Backlight LED Driver");
> > +MODULE_AUTHOR("Sebastian Reichel");
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_ALIAS("platform:ti-lmu-led-backlight");
> > diff --git a/drivers/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 @@
> > +/*
>
> Inconsistent licensing here the core has
> // SPDX-License-Identifier: GPL-2.0
>
> > + * TI LMU (Lighting Management Unit) Backlight Device Data
> > + *
> > + * Copyright 2015 Texas Instruments
>
> Copyright?
The original driver is from Milo Kim / TI. I changed almost all of
the core driver, so I took the liberty to update the copyright and
license header. The data part has been taken over almost unmodified,
so I kept the original header. I suppose TI is fine with using the
abbreviated header?
> > + * Author: Milo Kim <milo.kim@ti.com>
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > + */
> > +
> > +#include "ti-lmu-backlight-data.h"
> > +
> > +/* LM3532 */
> > +static const struct lmu_bl_reg_data lm3532_init_data[] = {
> > + { LM3532_REG_ZONE_CFG_A, LM3532_ZONE_MASK, LM3532_ZONE_0 },
> > + { LM3532_REG_ZONE_CFG_B, LM3532_ZONE_MASK, LM3532_ZONE_1 },
> > + { LM3532_REG_ZONE_CFG_C, LM3532_ZONE_MASK, LM3532_ZONE_2 },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3532_channel_data[] = {
> > + { LM3532_REG_OUTPUT_CFG, LM3532_ILED1_CFG_MASK,
> > + LM3532_ILED1_CFG_SHIFT },
> > + { LM3532_REG_OUTPUT_CFG, LM3532_ILED2_CFG_MASK,
> > + LM3532_ILED2_CFG_SHIFT },
> > + { LM3532_REG_OUTPUT_CFG, LM3532_ILED3_CFG_MASK,
> > + LM3532_ILED3_CFG_SHIFT },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3532_mode_data[] = {
> > + { LM3532_REG_PWM_A_CFG, LM3532_PWM_A_MASK, LM3532_PWM_ZONE_0 },
> > + { LM3532_REG_PWM_B_CFG, LM3532_PWM_B_MASK, LM3532_PWM_ZONE_1 },
> > + { LM3532_REG_PWM_C_CFG, LM3532_PWM_C_MASK, LM3532_PWM_ZONE_2 },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3532_ramp_data[] = {
> > + { LM3532_REG_RAMPUP, LM3532_RAMPUP_MASK, LM3532_RAMPUP_SHIFT },
> > + { LM3532_REG_RAMPDN, LM3532_RAMPDN_MASK, LM3532_RAMPDN_SHIFT },
> > +};
> > +
> > +static u8 lm3532_enable_reg = LM3532_REG_ENABLE;
> > +
> > +static u8 lm3532_brightness_regs[] = {
> > + LM3532_REG_BRT_A,
> > + LM3532_REG_BRT_B,
> > + LM3532_REG_BRT_C,
> > +};
> > +
> > +static const struct ti_lmu_bl_reg lm3532_reg_info = {
> > + .init = lm3532_init_data,
> > + .num_init = ARRAY_SIZE(lm3532_init_data),
> > + .channel = lm3532_channel_data,
> > + .mode = lm3532_mode_data,
> > + .ramp = lm3532_ramp_data,
> > + .enable = &lm3532_enable_reg,
> > + .brightness_msb = lm3532_brightness_regs,
> > +};
> > +
> > +/* LM3631 */
> > +static const struct lmu_bl_reg_data lm3631_init_data[] = {
> > + { LM3631_REG_BRT_MODE, LM3631_MODE_MASK, LM3631_DEFAULT_MODE },
> > + { LM3631_REG_BL_CFG, LM3631_MAP_MASK, LM3631_EXPONENTIAL_MAP },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3631_channel_data[] = {
> > + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_SINGLE_CHANNEL },
> > + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_DUAL_CHANNEL },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3631_ramp_data[] = {
> > + { LM3631_REG_SLOPE, LM3631_SLOPE_MASK, LM3631_SLOPE_SHIFT },
> > +};
> > +
> > +static u8 lm3631_enable_reg = LM3631_REG_DEVCTRL;
> > +static u8 lm3631_brightness_msb_reg = LM3631_REG_BRT_MSB;
> > +static u8 lm3631_brightness_lsb_reg = LM3631_REG_BRT_LSB;
> > +
> > +static const struct ti_lmu_bl_reg lm3631_reg_info = {
> > + .init = lm3631_init_data,
> > + .num_init = ARRAY_SIZE(lm3631_init_data),
> > + .channel = lm3631_channel_data,
> > + .ramp = lm3631_ramp_data,
> > + .enable = &lm3631_enable_reg,
> > + .brightness_msb = &lm3631_brightness_msb_reg,
> > + .brightness_lsb = &lm3631_brightness_lsb_reg,
> > +};
> > +
> > +/* LM3632 */
> > +static const struct lmu_bl_reg_data lm3632_init_data[] = {
> > + { LM3632_REG_CONFIG1, LM3632_OVP_MASK, LM3632_OVP_25V },
> > + { LM3632_REG_CONFIG2, LM3632_SWFREQ_MASK, LM3632_SWFREQ_1MHZ },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3632_channel_data[] = {
> > + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_SINGLE_CHANNEL },
> > + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_DUAL_CHANNEL },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3632_mode_data[] = {
> > + { LM3632_REG_IO_CTRL, LM3632_PWM_MASK, LM3632_PWM_MODE },
> > +};
> > +
> > +static u8 lm3632_enable_reg = LM3632_REG_ENABLE;
> > +static u8 lm3632_brightness_msb_reg = LM3632_REG_BRT_MSB;
> > +static u8 lm3632_brightness_lsb_reg = LM3632_REG_BRT_LSB;
> > +
> > +static const struct ti_lmu_bl_reg lm3632_reg_info = {
> > + .init = lm3632_init_data,
> > + .num_init = ARRAY_SIZE(lm3632_init_data),
> > + .channel = lm3632_channel_data,
> > + .mode = lm3632_mode_data,
> > + .enable = &lm3632_enable_reg,
> > + .brightness_msb = &lm3632_brightness_msb_reg,
> > + .brightness_lsb = &lm3632_brightness_lsb_reg,
> > +};
> > +
> > +/* LM3633 */
> > +static const struct lmu_bl_reg_data lm3633_init_data[] = {
> > + { LM3633_REG_BOOST_CFG, LM3633_OVP_MASK, LM3633_OVP_40V },
> > + { LM3633_REG_BL_RAMP_CONF, LM3633_BL_RAMP_MASK, LM3633_BL_RAMP_EACH },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3633_channel_data[] = {
> > + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED1_CFG_MASK,
> > + LM3633_HVLED1_CFG_SHIFT },
> > + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED2_CFG_MASK,
> > + LM3633_HVLED2_CFG_SHIFT },
> > + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED3_CFG_MASK,
> > + LM3633_HVLED3_CFG_SHIFT },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3633_mode_data[] = {
> > + { LM3633_REG_PWM_CFG, LM3633_PWM_A_MASK, LM3633_PWM_A_MASK },
> > + { LM3633_REG_PWM_CFG, LM3633_PWM_B_MASK, LM3633_PWM_B_MASK },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3633_ramp_data[] = {
> > + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPUP_MASK, LM3633_BL_RAMPUP_SHIFT },
> > + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPDN_MASK, LM3633_BL_RAMPDN_SHIFT },
> > +};
> > +
> > +static u8 lm3633_enable_reg = LM3633_REG_ENABLE;
> > +
> > +static u8 lm3633_brightness_msb_regs[] = {
> > + LM3633_REG_BRT_HVLED_A_MSB,
> > + LM3633_REG_BRT_HVLED_B_MSB,
> > +};
> > +
> > +static u8 lm3633_brightness_lsb_regs[] = {
> > + LM3633_REG_BRT_HVLED_A_LSB,
> > + LM3633_REG_BRT_HVLED_B_LSB,
> > +};
> > +
> > +static const struct ti_lmu_bl_reg lm3633_reg_info = {
> > + .init = lm3633_init_data,
> > + .num_init = ARRAY_SIZE(lm3633_init_data),
> > + .channel = lm3633_channel_data,
> > + .mode = lm3633_mode_data,
> > + .ramp = lm3633_ramp_data,
> > + .ramp_reg_offset = 1, /* For LM3633_REG_BL1_RAMPUP/DN */
> > + .enable = &lm3633_enable_reg,
> > + .brightness_msb = lm3633_brightness_msb_regs,
> > + .brightness_lsb = lm3633_brightness_lsb_regs,
> > +};
> > +
> > +/* LM3695 */
> > +static const struct lmu_bl_reg_data lm3695_init_data[] = {
> > + { LM3695_REG_GP, LM3695_BRT_RW_MASK, LM3695_BRT_RW_MASK },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3695_channel_data[] = {
> > + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_SINGLE_CHANNEL },
> > + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_DUAL_CHANNEL },
> > +};
> > +
> > +static u8 lm3695_enable_reg = LM3695_REG_GP;
> > +static u8 lm3695_brightness_msb_reg = LM3695_REG_BRT_MSB;
> > +static u8 lm3695_brightness_lsb_reg = LM3695_REG_BRT_LSB;
> > +
> > +static const struct ti_lmu_bl_reg lm3695_reg_info = {
> > + .init = lm3695_init_data,
> > + .num_init = ARRAY_SIZE(lm3695_init_data),
> > + .channel = lm3695_channel_data,
> > + .enable = &lm3695_enable_reg,
> > + .enable_usec = 600,
> > + .brightness_msb = &lm3695_brightness_msb_reg,
> > + .brightness_lsb = &lm3695_brightness_lsb_reg,
> > +};
> > +
> > +/* LM3697 */
> > +static const struct lmu_bl_reg_data lm3697_init_data[] = {
> > + { LM3697_REG_RAMP_CONF, LM3697_RAMP_MASK, LM3697_RAMP_EACH },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3697_channel_data[] = {
> > + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED1_CFG_MASK,
> > + LM3697_HVLED1_CFG_SHIFT },
> > + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED2_CFG_MASK,
> > + LM3697_HVLED2_CFG_SHIFT },
> > + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED3_CFG_MASK,
> > + LM3697_HVLED3_CFG_SHIFT },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3697_mode_data[] = {
> > + { LM3697_REG_PWM_CFG, LM3697_PWM_A_MASK, LM3697_PWM_A_MASK },
> > + { LM3697_REG_PWM_CFG, LM3697_PWM_B_MASK, LM3697_PWM_B_MASK },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3697_ramp_data[] = {
> > + { LM3697_REG_BL0_RAMP, LM3697_RAMPUP_MASK, LM3697_RAMPUP_SHIFT },
> > + { LM3697_REG_BL0_RAMP, LM3697_RAMPDN_MASK, LM3697_RAMPDN_SHIFT },
> > +};
> > +
> > +static u8 lm3697_enable_reg = LM3697_REG_ENABLE;
> > +
> > +static u8 lm3697_brightness_msb_regs[] = {
> > + LM3697_REG_BRT_A_MSB,
> > + LM3697_REG_BRT_B_MSB,
> > +};
> > +
> > +static u8 lm3697_brightness_lsb_regs[] = {
> > + LM3697_REG_BRT_A_LSB,
> > + LM3697_REG_BRT_B_LSB,
> > +};
> > +
> > +static const struct ti_lmu_bl_reg lm3697_reg_info = {
> > + .init = lm3697_init_data,
> > + .num_init = ARRAY_SIZE(lm3697_init_data),
> > + .channel = lm3697_channel_data,
> > + .mode = lm3697_mode_data,
> > + .ramp = lm3697_ramp_data,
> > + .ramp_reg_offset = 1, /* For LM3697_REG_BL1_RAMPUP/DN */
> > + .enable = &lm3697_enable_reg,
> > + .brightness_msb = lm3697_brightness_msb_regs,
> > + .brightness_lsb = lm3697_brightness_lsb_regs,
> > +};
> > +
> > +static int lm3532_ramp_table[] = { 0, 1, 2, 4, 8, 16, 32, 65 };
> > +
> > +static int lm3631_ramp_table[] = {
> > + 0, 1, 2, 5, 10, 20, 50, 100,
> > + 250, 500, 750, 1000, 1500, 2000, 3000, 4000,
> > +};
> > +
> > +static int common_ramp_table[] = {
> > + 2, 250, 500, 1000, 2000, 4000, 8000, 16000,
> > +};
> > +
> > +#define LM3532_MAX_CHANNELS 3
> > +#define LM3631_MAX_CHANNELS 2
> > +#define LM3632_MAX_CHANNELS 2
> > +#define LM3633_MAX_CHANNELS 3
> > +#define LM3695_MAX_CHANNELS 2
> > +#define LM3697_MAX_CHANNELS 3
> > +
> > +const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID] = {
> > + {
> > + .reginfo = &lm3532_reg_info,
> > + .num_channels = LM3532_MAX_CHANNELS,
> > + .max_brightness = MAX_BRIGHTNESS_8BIT,
> > + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> > + .ramp_table = lm3532_ramp_table,
> > + .size_ramp = ARRAY_SIZE(lm3532_ramp_table),
> > + },
> > + {
> > + .reginfo = &lm3631_reg_info,
> > + .num_channels = LM3631_MAX_CHANNELS,
> > + .max_brightness = MAX_BRIGHTNESS_11BIT,
> > + .pwm_action = UPDATE_PWM_ONLY,
> > + .ramp_table = lm3631_ramp_table,
> > + .size_ramp = ARRAY_SIZE(lm3631_ramp_table),
> > + },
> > + {
> > + .reginfo = &lm3632_reg_info,
> > + .num_channels = LM3632_MAX_CHANNELS,
> > + .max_brightness = MAX_BRIGHTNESS_11BIT,
> > + .pwm_action = UPDATE_PWM_ONLY,
> > + },
> > + {
> > + .reginfo = &lm3633_reg_info,
> > + .num_channels = LM3633_MAX_CHANNELS,
> > + .max_brightness = MAX_BRIGHTNESS_11BIT,
> > + .pwm_action = UPDATE_MAX_BRT,
> > + .ramp_table = common_ramp_table,
> > + .size_ramp = ARRAY_SIZE(common_ramp_table),
> > + .fault_monitor_used = true,
> > + },
> > + {
> > + .reginfo = &lm3695_reg_info,
> > + .num_channels = LM3695_MAX_CHANNELS,
> > + .max_brightness = MAX_BRIGHTNESS_11BIT,
> > + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> > + },
> > + {
> > + .reginfo = &lm3697_reg_info,
> > + .num_channels = LM3697_MAX_CHANNELS,
> > + .max_brightness = MAX_BRIGHTNESS_11BIT,
> > + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> > + .ramp_table = common_ramp_table,
> > + .size_ramp = ARRAY_SIZE(common_ramp_table),
> > + .fault_monitor_used = true,
> > + },
> > +};
> > diff --git a/drivers/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 @@
> > +/*
>
> Inconsistent licensing here the core has
> // SPDX-License-Identifier: GPL-2.0
See above.
> > + * TI LMU (Lighting Management Unit) Backlight Device Data Definitions
> > + *
> > + * Copyright 2015 Texas Instruments
> > + *
> > + * Author: Milo Kim <milo.kim@ti.com>
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > + */
> > +
> > +#ifndef __TI_LMU_BACKLIGHT_H__
> > +#define __TI_LMU_BACKLIGHT_H__
> > +
> > +#include <linux/mfd/ti-lmu.h>
> > +#include <linux/mfd/ti-lmu-register.h>
> > +
> > +#define MAX_BRIGHTNESS_8BIT 255
> > +#define MAX_BRIGHTNESS_11BIT 2047
> > +
> > +enum ti_lmu_bl_pwm_action {
> > + /* Update PWM duty, no brightness register update is required */
> > + UPDATE_PWM_ONLY,
> > + /* Update not only duty but also brightness register */
> > + UPDATE_PWM_AND_BRT_REGISTER,
> > + /* Update max value in brightness registers */
> > + UPDATE_MAX_BRT,
> > +};
> > +
> > +struct lmu_bl_reg_data {
> > + u8 reg;
> > + u8 mask;
> > + u8 val;
> > +};
> > +
> > +/**
> > + * struct ti_lmu_bl_reg
> > + *
> > + * @init: Device initialization registers
> > + * @num_init: Numbers of initialization registers
> > + * @channel: Backlight channel configuration registers
> > + * @mode: Brightness control mode registers
> > + * @ramp: Ramp registers for lighting effect
> > + * @ramp_reg_offset: Ramp register offset.
> > + * Only used for multiple ramp registers.
> > + * @enable: Enable control register address
> > + * @enable_usec: Delay time for updating enable register.
> > + * Unit is microsecond.
> > + * @brightness_msb: Brightness MSB(Upper 8 bits) registers.
> > + * Concatenated with LSB in 11 bit dimming mode.
> > + * In 8 bit dimming, only MSB is used.
> > + * @brightness_lsb: Brightness LSB(Lower 3 bits) registers.
> > + * Only valid in 11 bit dimming mode.
> > + */
> > +struct ti_lmu_bl_reg {
> > + const struct lmu_bl_reg_data *init;
> > + int num_init;
> > + const struct lmu_bl_reg_data *channel;
> > + const struct lmu_bl_reg_data *mode;
> > + const struct lmu_bl_reg_data *ramp;
> > + int ramp_reg_offset;
> > + u8 *enable;
> > + unsigned long enable_usec;
> > + u8 *brightness_msb;
> > + u8 *brightness_lsb;
> > +};
> > +
> > +/**
> > + * struct ti_lmu_bl_cfg
> > + *
> > + * @reginfo: Device register configuration
> > + * @num_channels: Number of backlight channels
> > + * @max_brightness: Max brightness value of backlight device
> > + * @pwm_action: How to control brightness registers in PWM mode
> > + * @ramp_table: [Optional] Ramp time table for lighting effect.
> > + * It's used for searching approximate register index.
> > + * @size_ramp: [Optional] Size of ramp table
> > + * @fault_monitor_used: [Optional] Set true if the device needs to handle
> > + * LMU fault monitor event.
> > + *
> > + * This structure is used for device specific data configuration.
> > + */
> > +struct ti_lmu_bl_cfg {
> > + const struct ti_lmu_bl_reg *reginfo;
> > + int num_channels;
> > + int max_brightness;
> > + enum ti_lmu_bl_pwm_action pwm_action;
> > + int *ramp_table;
> > + int size_ramp;
> > + bool fault_monitor_used;
> > +};
> > +
> > +extern const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID];
> > +#endif
> >
-- Sebastian
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 62+ messages in thread* Re: [PATCHv4 08/10] backlight: add TI LMU backlight driver
@ 2018-04-09 16:11 ` Sebastian Reichel
0 siblings, 0 replies; 62+ messages in thread
From: Sebastian Reichel @ 2018-04-09 16:11 UTC (permalink / raw)
To: Dan Murphy
Cc: Lee Jones, Daniel Thompson, Rob Herring, Tony Lindgren,
Jingoo Han, Mark Rutland, linux-kernel, linux-fbdev, devicetree,
linux-omap
[-- Attachment #1: Type: text/plain, Size: 39324 bytes --]
Hi,
On Wed, Apr 04, 2018 at 01:30:37PM -0500, Dan Murphy wrote:
> Sebastian
>
> -Milo Kim email is not valid
Thanks for your review! Milo was CC'd, since git took over the
SoB. I will make sure to avoid it next time.
> On 03/30/2018 12:24 PM, Sebastian Reichel wrote:
> > This adds backlight support for the following TI LMU
> > chips: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697.
> >
> > Signed-off-by: Milo Kim <milo.kim@ti.com>
> > [add LED subsystem support for keyboard backlight and rework DT
> > binding according to Rob Herrings feedback]
> > Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> > ---
> > drivers/video/backlight/Kconfig | 7 +
> > drivers/video/backlight/Makefile | 3 +
> > drivers/video/backlight/ti-lmu-backlight-core.c | 666 ++++++++++++++++++++++++
> > drivers/video/backlight/ti-lmu-backlight-data.c | 304 +++++++++++
> > drivers/video/backlight/ti-lmu-backlight-data.h | 95 ++++
> > 5 files changed, 1075 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 5d2d0d7e8100..27e6c5a0add8 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
>
> select REGMAP?
Right.
> > + 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 19da71d518bf..a1132d3dfd4c 100644
> > --- a/drivers/video/backlight/Makefile
> > +++ b/drivers/video/backlight/Makefile
> > @@ -53,6 +53,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..a6099581edd7
> > --- /dev/null
> > +++ b/drivers/video/backlight/ti-lmu-backlight-core.c
> > @@ -0,0 +1,666 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright 2015 Texas Instruments
> > + * Copyright 2018 Sebastian Reichel
> > + *
> > + * TI LMU Backlight driver, based on previous work from
> > + * Milo Kim <milo.kim@ti.com>
> > + */
> > +
> > +#include <linux/backlight.h>
> > +#include <linux/bitops.h>
> > +#include <linux/device.h>
> > +#include <linux/delay.h>
> > +#include <linux/err.h>
> > +#include <linux/leds.h>
> > +#include <linux/mfd/ti-lmu.h>
> > +#include <linux/mfd/ti-lmu-register.h>
> > +#include <linux/module.h>
> > +#include <linux/notifier.h>
> > +#include <linux/of_device.h>
> > +#include <linux/platform_device.h>
> > +
> > +#include "ti-lmu-backlight-data.h"
> > +
> > +enum ti_lmu_bl_ctrl_mode {
> > + BL_REGISTER_BASED,
> > + BL_PWM_BASED,
> > +};
> > +
> > +enum ti_lmu_bl_type {
> > + TI_LMU_BL, /* backlight userspace interface */
> > + TI_LMU_LED, /* led userspace interface */
> > +};
> > +
> > +enum ti_lmu_bl_ramp_mode {
> > + BL_RAMP_UP,
> > + BL_RAMP_DOWN,
> > +};
> > +
> > +#define DEFAULT_PWM_NAME "lmu-backlight"
>
> I don't see this used.
I guess I can remove this :)
> > +#define NUM_DUAL_CHANNEL 2
> > +#define LMU_BACKLIGHT_DUAL_CHANNEL_USED (BIT(0) | BIT(1))
> > +#define LMU_BACKLIGHT_11BIT_LSB_MASK (BIT(0) | BIT(1) | BIT(2))
> > +#define LMU_BACKLIGHT_11BIT_MSB_SHIFT 3
> > +
>
> Struct kerneldoc?
Ok.
> > +struct ti_lmu_bank {
> > + struct device *dev;
> > + int bank_id;
> > + const struct ti_lmu_bl_cfg *cfg;
> > + struct ti_lmu *lmu;
> > + const char *label;
> > + int leds;
> > + int current_brightness;
> > + u32 default_brightness;
> > + u32 ramp_up_msec;
> > + u32 ramp_down_msec;
> > + u32 pwm_period;
> > + enum ti_lmu_bl_ctrl_mode mode;
> > + enum ti_lmu_bl_type type;
> > +
> > + struct notifier_block nb;
> > +
> > + struct backlight_device *backlight;
> > + struct led_classdev *led;
> > +};
> > +
> > +static int ti_lmu_bl_enable(struct ti_lmu_bank *lmu_bank, bool enable)
> > +{
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + unsigned long enable_time = lmu_bank->cfg->reginfo->enable_usec;
> > + u8 *reg = lmu_bank->cfg->reginfo->enable;
> > + u8 mask = BIT(lmu_bank->bank_id);
> > + u8 val = (enable == true) ? mask : 0;
> > + int ret;
> > +
> > + if (!reg)
> > + return -EINVAL;
> > +
> > + ret = regmap_update_bits(regmap, *reg, mask, val);
> > + if (ret)
> > + return ret;
> > +
> > + if (enable_time > 0)
> > + usleep_range(enable_time, enable_time + 100);
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_update_brightness_register(struct ti_lmu_bank *lmu_bank,
> > + int brightness)
> > +{
> > + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> > + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + u8 reg, val;
> > + int ret;
> > +
> > + /*
> > + * Brightness register update
> > + *
> > + * 11 bit dimming: update LSB bits and write MSB byte.
> > + * MSB brightness should be shifted.
> > + * 8 bit dimming: write MSB byte.
> > + */
> > + if (cfg->max_brightness == MAX_BRIGHTNESS_11BIT) {
> > + reg = reginfo->brightness_lsb[lmu_bank->bank_id];
> > + ret = regmap_update_bits(regmap, reg,
> > + LMU_BACKLIGHT_11BIT_LSB_MASK,
> > + brightness);
> > + if (ret)
> > + return ret;
> > +
> > + val = brightness >> LMU_BACKLIGHT_11BIT_MSB_SHIFT;
> > + } else {
> > + val = brightness;
> > + }
> > +
> > + reg = reginfo->brightness_msb[lmu_bank->bank_id];
> > + return regmap_write(regmap, reg, val);
> > +}
> > +
> > +static int ti_lmu_bl_pwm_ctrl(struct ti_lmu_bank *lmu_bank, int brightness)
> > +{
> > + int max_brightness = lmu_bank->cfg->max_brightness;
> > + struct pwm_state state = { };
> > + int ret;
> > +
> > + if (!lmu_bank->lmu->pwm) {
> > + dev_err(lmu_bank->dev, "Missing PWM device!\n");
> > + return -ENODEV;
> > + }
> > +
> > + pwm_init_state(lmu_bank->lmu->pwm, &state);
> > + state.period = lmu_bank->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_bank->lmu->pwm, &state);
> > + if (ret)
> > + dev_err(lmu_bank->dev, "Failed to configure PWM: %d", ret);
> > +
> > + return ret;
> > +}
> > +
> > +static int ti_lmu_bl_set_brightness(struct ti_lmu_bank *lmu_bank,
> > + int brightness)
> > +{
> > + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> > + bool enable = brightness > 0;
> > + int ret;
> > +
>
> Is there anyway that update_status and set_led_blocking can be
> called at the same time causing a race condition in this api?
Each bank is either exclusively exposed via LED or via backlight
subsystem.
> > + ret = ti_lmu_bl_enable(lmu_bank, enable);
> > + if (ret)
> > + return ret;
> > +
> > + if (lmu_bank->mode == BL_PWM_BASED) {
> > + ti_lmu_bl_pwm_ctrl(lmu_bank, 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;
> > + }
> > + }
> > +
> > + lmu_bank->current_brightness = brightness;
> > +
> > + return ti_lmu_bl_update_brightness_register(lmu_bank, brightness);
> > +}
> > +
> > +static int ti_lmu_bl_update_status(struct backlight_device *bl_dev)
> > +{
> > + struct ti_lmu_bank *lmu_bank = bl_get_data(bl_dev);
> > + int brightness = bl_dev->props.brightness;
> > +
> > + if (bl_dev->props.state & BL_CORE_SUSPENDED)
> > + brightness = 0;
> > +
> > + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> > +}
> > +
> > +static const struct backlight_ops lmu_backlight_ops = {
> > + .options = BL_CORE_SUSPENDRESUME,
> > + .update_status = ti_lmu_bl_update_status,
> > +};
> > +
> > +static int ti_lmu_bl_set_led_blocking(struct led_classdev *ledc,
> > + enum led_brightness value)
> > +{
> > + struct ti_lmu_bank *lmu_bank = dev_get_drvdata(ledc->dev->parent);
> > + int brightness = value;
> > +
> > + return ti_lmu_bl_set_brightness(lmu_bank, brightness);
> > +}
> > +
> > +static int ti_lmu_bl_check_channel(struct ti_lmu_bank *lmu_bank)
> > +{
> > + const struct ti_lmu_bl_cfg *cfg = lmu_bank->cfg;
> > + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo;
> > +
> > + if (!reginfo->brightness_msb)
> > + return -EINVAL;
> > +
> > + if (cfg->max_brightness > MAX_BRIGHTNESS_8BIT) {
> > + if (!reginfo->brightness_lsb)
> > + return -EINVAL;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_create_channel(struct ti_lmu_bank *lmu_bank)
> > +{
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + const struct lmu_bl_reg_data *regdata = lmu_bank->cfg->reginfo->channel;
> > + int num_channels = lmu_bank->cfg->num_channels;
> > + unsigned long led_sources = lmu_bank->leds;
> > + int i, ret;
> > + u8 shift;
> > +
> > + /*
> > + * How to create backlight output channels:
> > + * Check 'led_sources' bit and update registers.
> > + *
> > + * 1) Dual channel configuration
> > + * The 1st register data is used for single channel.
> > + * The 2nd register data is used for dual channel.
> > + *
> > + * 2) Multiple channel configuration
> > + * Each register data is mapped to bank ID.
> > + * Bit shift operation is defined in channel registers.
> > + *
> > + * Channel register data consists of address, mask, value.
> > + */
> > +
> > + if (num_channels == NUM_DUAL_CHANNEL) {
> > + if (led_sources == LMU_BACKLIGHT_DUAL_CHANNEL_USED)
> > + regdata++;
> > +
> > + return regmap_update_bits(regmap, regdata->reg, regdata->mask,
> > + regdata->val);
> > + }
> > +
> > + for (i = 0; regdata && i < num_channels; i++) {
> > + /*
> > + * Note that the result of regdata->val is shift bit.
> > + * The bank_id should be shifted for the channel configuration.
> > + */
> > + if (test_bit(i, &led_sources)) {
> > + shift = regdata->val;
> > + ret = regmap_update_bits(regmap, regdata->reg,
> > + regdata->mask,
> > + lmu_bank->bank_id << shift);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + regdata++;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_update_ctrl_mode(struct ti_lmu_bank *lmu_bank)
> > +{
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + const struct lmu_bl_reg_data *regdata =
> > + lmu_bank->cfg->reginfo->mode + lmu_bank->bank_id;
> > + u8 val = regdata->val;
> > +
> > + if (!regdata)
> > + return 0;
> > +
> > + /*
> > + * Update PWM configuration register.
> > + * If the mode is register based, then clear the bit.
> > + */
> > + if (lmu_bank->mode != BL_PWM_BASED)
> > + val = 0;
> > +
> > + return regmap_update_bits(regmap, regdata->reg, regdata->mask, val);
> > +}
> > +
> > +static int ti_lmu_bl_convert_ramp_to_index(struct ti_lmu_bank *lmu_bank,
> > + enum ti_lmu_bl_ramp_mode mode)
> > +{
> > + const int *ramp_table = lmu_bank->cfg->ramp_table;
> > + const int size = lmu_bank->cfg->size_ramp;
> > + unsigned int msec;
> > + int i;
> > +
> > + if (!ramp_table)
> > + return -EINVAL;
> > +
> > + switch (mode) {
> > + case BL_RAMP_UP:
> > + msec = lmu_bank->ramp_up_msec;
> > + break;
> > + case BL_RAMP_DOWN:
> > + msec = lmu_bank->ramp_down_msec;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + if (msec <= ramp_table[0])
> > + return 0;
> > +
> > + if (msec > ramp_table[size - 1])
> > + return size - 1;
> > +
> > + for (i = 1; i < size; i++) {
> > + if (msec == ramp_table[i])
> > + return i;
> > +
> > + /* Find an approximate index by looking up the table */
> > + if (msec > ramp_table[i - 1] && msec < ramp_table[i]) {
> > + if (msec - ramp_table[i - 1] < ramp_table[i] - msec)
> > + return i - 1;
> > + else
> > + return i;
> > + }
> > + }
> > +
> > + return -EINVAL;
> > +}
> > +
> > +
>
> Extra new line
oops.
>
> > +static int ti_lmu_bl_set_ramp(struct ti_lmu_bank *lmu_bank)
> > +{
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + const struct ti_lmu_bl_reg *reginfo = lmu_bank->cfg->reginfo;
> > + int offset = reginfo->ramp_reg_offset;
> > + int i, ret, index;
> > + struct lmu_bl_reg_data regdata;
> > +
> > + for (i = BL_RAMP_UP; i <= BL_RAMP_DOWN; i++) {
> > + index = ti_lmu_bl_convert_ramp_to_index(lmu_bank, i);
> > + if (index > 0) {
> > + if (!reginfo->ramp)
> > + break;
> > +
> > + regdata = reginfo->ramp[i];
> > + if (lmu_bank->bank_id != 0)
> > + regdata.val += offset;
> > +
> > + /* regdata.val is shift bit */
> > + ret = regmap_update_bits(regmap, regdata.reg,
> > + regdata.mask,
> > + index << regdata.val);
> > + if (ret)
> > + return ret;
> > + }
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_configure(struct ti_lmu_bank *lmu_bank)
> > +{
> > + int ret;
> > +
> > + ret = ti_lmu_bl_check_channel(lmu_bank);
> > + if (ret)
> > + return ret;
> > +
> > + ret = ti_lmu_bl_create_channel(lmu_bank);
> > + if (ret)
> > + return ret;
> > +
> > + ret = ti_lmu_bl_update_ctrl_mode(lmu_bank);
> > + if (ret)
> > + return ret;
> > +
> > + return ti_lmu_bl_set_ramp(lmu_bank);
> > +}
> > +
> > +static int ti_lmu_bl_register_backlight(struct ti_lmu_bank *lmu_bank)
> > +{
> > + struct backlight_device *bl_dev;
> > + struct backlight_properties props;
> > +
> > + if (lmu_bank->type != TI_LMU_BL)
> > + return -EINVAL;
> > +
> > + memset(&props, 0, sizeof(struct backlight_properties));
> > + props.type = BACKLIGHT_PLATFORM;
> > + props.brightness = lmu_bank->default_brightness;
> > + props.max_brightness = lmu_bank->cfg->max_brightness;
> > +
> > + bl_dev = devm_backlight_device_register(lmu_bank->dev, lmu_bank->label,
> > + lmu_bank->dev, lmu_bank,
> > + &lmu_backlight_ops, &props);
> > + if (IS_ERR(bl_dev))
> > + return PTR_ERR(bl_dev);
> > +
> > + lmu_bank->backlight = bl_dev;
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_register_led(struct ti_lmu_bank *lmu_bank)
> > +{
> > + int err;
> > +
> > + if (lmu_bank->type != TI_LMU_LED)
> > + return -EINVAL;
> > +
> > + lmu_bank->led = devm_kzalloc(lmu_bank->dev, sizeof(*lmu_bank->led),
> > + GFP_KERNEL);
> > + if (!lmu_bank->led)
> > + return -ENOMEM;
> > +
> > + lmu_bank->led->name = lmu_bank->label;
> > + lmu_bank->led->max_brightness = lmu_bank->cfg->max_brightness;
> > + lmu_bank->led->brightness_set_blocking =
> > + ti_lmu_bl_set_led_blocking;
> > +
> > + err = devm_led_classdev_register(lmu_bank->dev, lmu_bank->led);
> > + if (err)
> > + return err;
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_add_device(struct ti_lmu_bank *lmu_bank)
> > +{
> > + switch (lmu_bank->type) {
> > + case TI_LMU_BL:
> > + return ti_lmu_bl_register_backlight(lmu_bank);
> > + case TI_LMU_LED:
> > + return ti_lmu_bl_register_led(lmu_bank);
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int setup_of_node(struct platform_device *pdev)
>
> This seems a bit generic. Maybe keep with the naming convention
>
> ti_lmu_setup_of_node
right.
> > +{
> > + struct device_node *parent_node = pdev->dev.parent->of_node;
> > + char *name;
> > +
> > + if (!parent_node)
> > + return 0;
> > +
> > + name = kasprintf(GFP_KERNEL, "bank%d", pdev->id);
> > + if (!name)
> > + return -ENOMEM;
> > +
> > + pdev->dev.of_node = of_get_child_by_name(parent_node, name);
> > + kfree(name);
> > +
> > + if (!pdev->dev.of_node)
> > + return -ENODEV;
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_parse_led_sources(struct device *dev)
> > +{
> > + unsigned long mask = 0;
> > + int ret;
> > + int size, i;
> > + u32 *leds;
> > +
> > + size = device_property_read_u32_array(dev, "ti,led-sources", NULL, 0);
> > + if (size <= 0) {
> > + dev_err(dev, "Missing or malformed property led-sources: %d\n",
> > + size);
> > + return size < 0 ? size : -EINVAL;
>
> Why the additional check? Why not just return -EINVAL?
I think it makes sense to pass the actual error code, but I can
simplify it when you insist.
> > + }
> > +
> > + leds = kmalloc_array(size, sizeof(u32), GFP_KERNEL);
> > + if (!leds)
> > + return -ENOMEM;
> > +
> > + ret = device_property_read_u32_array(dev, "ti,led-sources", leds, size);
> > + if (ret) {
> > + dev_err(dev, "Failed to read led-sources property: %d\n", ret);
> > + goto out;
> > + }
> > +
> > + for (i = 0; i < size; i++)
> > + set_bit(leds[i], &mask);
> > +
> > + ret = mask;
> > +
> > +out:
> > + kfree(leds);
> > + return ret;
> > +}
> > +
> > +static int ti_lmu_bl_init(struct ti_lmu_bank *lmu_bank)
> > +{
> > + struct regmap *regmap = lmu_bank->lmu->regmap;
> > + const struct lmu_bl_reg_data *regdata =
> > + lmu_bank->cfg->reginfo->init;
> > + int num_init = lmu_bank->cfg->reginfo->num_init;
> > + int i, ret;
> > +
> > + if (lmu_bank->lmu->backlight_initialized)
> > + return 0;
>
> Add a new line here
ok
> > + lmu_bank->lmu->backlight_initialized = true;
> > +
> > + for (i = 0; regdata && i < num_init; i++) {
> > + ret = regmap_update_bits(regmap, regdata->reg, regdata->mask,
> > + regdata->val);
> > + if (ret)
> > + return ret;
> > +
> > + regdata++;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_reload(struct ti_lmu_bank *lmu_bank)
> > +{
> > + int err;
> > +
> > + ti_lmu_bl_init(lmu_bank);
> > +
> > + err = ti_lmu_bl_configure(lmu_bank);
> > + if (err)
> > + return err;
> > +
> > + return ti_lmu_bl_set_brightness(lmu_bank, lmu_bank->current_brightness);
> > +}
> > +
> > +static int ti_lmu_bl_monitor_notifier(struct notifier_block *nb,
> > + unsigned long action, void *unused)
> > +{
> > + struct ti_lmu_bank *lmu_bank = container_of(nb, struct ti_lmu_bank, nb);
> > +
> > + if (action == LMU_EVENT_MONITOR_DONE) {
> > + if (ti_lmu_bl_reload(lmu_bank))
> > + return NOTIFY_STOP;
> > + }
> > +
> > + return NOTIFY_OK;
> > +}
> > +
> > +static int ti_lmu_bl_probe(struct platform_device *pdev)
> > +{
> > + struct ti_lmu_bank *lmu_bank;
> > + int err;
> > +
> > + err = setup_of_node(pdev);
> > + if (err)
> > + return err;
> > +
> > + lmu_bank = devm_kzalloc(&pdev->dev, sizeof(*lmu_bank), GFP_KERNEL);
> > + if (!lmu_bank)
> > + return -ENOMEM;
>
> Add a new line here
ok
> > + lmu_bank->dev = &pdev->dev;
> > + dev_set_drvdata(&pdev->dev, lmu_bank);
> > +
> > + err = device_property_read_string(&pdev->dev, "label",
> > + &lmu_bank->label);
> > + if (err)
> > + return err;
> > +
> > + lmu_bank->type = TI_LMU_BL;
> > + if (!strcmp(lmu_bank->label, "keyboard")) {
> > + lmu_bank->type = TI_LMU_LED;
> > + lmu_bank->label = "kbd_backlight";
>
> What is the reason for changing the label? Why can't the label in
> the DT be kbd_backlight?
It can, I tried to avoid Linuxism in DT.
> > + }
> > +
> > + lmu_bank->leds = ti_lmu_parse_led_sources(&pdev->dev);
> > + if (lmu_bank->leds < 0)
> > + return lmu_bank->leds;
> > + else if (lmu_bank->leds == 0)
> > + return -EINVAL;
> > +
> > + device_property_read_u32(&pdev->dev, "default-brightness-level",
> > + &lmu_bank->default_brightness);
> > + device_property_read_u32(&pdev->dev, "ti,ramp-up-ms",
> > + &lmu_bank->ramp_up_msec);
> > + device_property_read_u32(&pdev->dev, "ti,ramp-down-ms",
> > + &lmu_bank->ramp_down_msec);
> > + device_property_read_u32(&pdev->dev, "pwm-period",
> > + &lmu_bank->pwm_period);
> > +
> > + if (lmu_bank->pwm_period > 0)
> > + lmu_bank->mode = BL_PWM_BASED;
> > + else
> > + lmu_bank->mode = BL_REGISTER_BASED;
> > +
> > + lmu_bank->lmu = dev_get_drvdata(pdev->dev.parent);
> > + lmu_bank->cfg = &lmu_bl_cfg[lmu_bank->lmu->id];
> > + lmu_bank->bank_id = pdev->id;
> > +
> > + ti_lmu_bl_init(lmu_bank);
> > +
> > + err = ti_lmu_bl_configure(lmu_bank);
> > + if (err)
> > + return err;
> > +
> > + err = ti_lmu_bl_add_device(lmu_bank);
> > + if (err)
> > + return err;
> > +
> > + err = ti_lmu_bl_set_brightness(lmu_bank,
> > + lmu_bank->default_brightness);
> > + if (err)
> > + return err;
> > +
> > + /*
> > + * Notifier callback is required because backlight device needs
> > + * reconfiguration after fault detection procedure is done by
> > + * ti-lmu-fault-monitor driver.
> > + */
> > + if (lmu_bank->cfg->fault_monitor_used) {
> > + lmu_bank->nb.notifier_call = ti_lmu_bl_monitor_notifier;
> > + err = blocking_notifier_chain_register(&lmu_bank->lmu->notifier,
> > + &lmu_bank->nb);
> > + if (err)
> > + return err;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ti_lmu_bl_remove(struct platform_device *pdev)
> > +{
> > + struct ti_lmu_bank *lmu_bank = platform_get_drvdata(pdev);
> > +
> > + if (lmu_bank->cfg->fault_monitor_used)
> > + blocking_notifier_chain_unregister(&lmu_bank->lmu->notifier,
> > + &lmu_bank->nb);
> > +
> > + ti_lmu_bl_set_brightness(lmu_bank, 0);
> > +
> > + return 0;
> > +}
> > +
> > +static struct platform_driver ti_lmu_bl_driver = {
> > + .probe = ti_lmu_bl_probe,
> > + .remove = ti_lmu_bl_remove,
> > + .driver = {
> > + .name = "ti-lmu-led-backlight",
> > + },
> > +};
> > +module_platform_driver(ti_lmu_bl_driver)
> > +
> > +MODULE_DESCRIPTION("TI LMU Backlight LED Driver");
> > +MODULE_AUTHOR("Sebastian Reichel");
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_ALIAS("platform:ti-lmu-led-backlight");
> > diff --git a/drivers/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 @@
> > +/*
>
> Inconsistent licensing here the core has
> // SPDX-License-Identifier: GPL-2.0
>
> > + * TI LMU (Lighting Management Unit) Backlight Device Data
> > + *
> > + * Copyright 2015 Texas Instruments
>
> Copyright?
The original driver is from Milo Kim / TI. I changed almost all of
the core driver, so I took the liberty to update the copyright and
license header. The data part has been taken over almost unmodified,
so I kept the original header. I suppose TI is fine with using the
abbreviated header?
> > + * Author: Milo Kim <milo.kim@ti.com>
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > + */
> > +
> > +#include "ti-lmu-backlight-data.h"
> > +
> > +/* LM3532 */
> > +static const struct lmu_bl_reg_data lm3532_init_data[] = {
> > + { LM3532_REG_ZONE_CFG_A, LM3532_ZONE_MASK, LM3532_ZONE_0 },
> > + { LM3532_REG_ZONE_CFG_B, LM3532_ZONE_MASK, LM3532_ZONE_1 },
> > + { LM3532_REG_ZONE_CFG_C, LM3532_ZONE_MASK, LM3532_ZONE_2 },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3532_channel_data[] = {
> > + { LM3532_REG_OUTPUT_CFG, LM3532_ILED1_CFG_MASK,
> > + LM3532_ILED1_CFG_SHIFT },
> > + { LM3532_REG_OUTPUT_CFG, LM3532_ILED2_CFG_MASK,
> > + LM3532_ILED2_CFG_SHIFT },
> > + { LM3532_REG_OUTPUT_CFG, LM3532_ILED3_CFG_MASK,
> > + LM3532_ILED3_CFG_SHIFT },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3532_mode_data[] = {
> > + { LM3532_REG_PWM_A_CFG, LM3532_PWM_A_MASK, LM3532_PWM_ZONE_0 },
> > + { LM3532_REG_PWM_B_CFG, LM3532_PWM_B_MASK, LM3532_PWM_ZONE_1 },
> > + { LM3532_REG_PWM_C_CFG, LM3532_PWM_C_MASK, LM3532_PWM_ZONE_2 },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3532_ramp_data[] = {
> > + { LM3532_REG_RAMPUP, LM3532_RAMPUP_MASK, LM3532_RAMPUP_SHIFT },
> > + { LM3532_REG_RAMPDN, LM3532_RAMPDN_MASK, LM3532_RAMPDN_SHIFT },
> > +};
> > +
> > +static u8 lm3532_enable_reg = LM3532_REG_ENABLE;
> > +
> > +static u8 lm3532_brightness_regs[] = {
> > + LM3532_REG_BRT_A,
> > + LM3532_REG_BRT_B,
> > + LM3532_REG_BRT_C,
> > +};
> > +
> > +static const struct ti_lmu_bl_reg lm3532_reg_info = {
> > + .init = lm3532_init_data,
> > + .num_init = ARRAY_SIZE(lm3532_init_data),
> > + .channel = lm3532_channel_data,
> > + .mode = lm3532_mode_data,
> > + .ramp = lm3532_ramp_data,
> > + .enable = &lm3532_enable_reg,
> > + .brightness_msb = lm3532_brightness_regs,
> > +};
> > +
> > +/* LM3631 */
> > +static const struct lmu_bl_reg_data lm3631_init_data[] = {
> > + { LM3631_REG_BRT_MODE, LM3631_MODE_MASK, LM3631_DEFAULT_MODE },
> > + { LM3631_REG_BL_CFG, LM3631_MAP_MASK, LM3631_EXPONENTIAL_MAP },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3631_channel_data[] = {
> > + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_SINGLE_CHANNEL },
> > + { LM3631_REG_BL_CFG, LM3631_BL_CHANNEL_MASK, LM3631_BL_DUAL_CHANNEL },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3631_ramp_data[] = {
> > + { LM3631_REG_SLOPE, LM3631_SLOPE_MASK, LM3631_SLOPE_SHIFT },
> > +};
> > +
> > +static u8 lm3631_enable_reg = LM3631_REG_DEVCTRL;
> > +static u8 lm3631_brightness_msb_reg = LM3631_REG_BRT_MSB;
> > +static u8 lm3631_brightness_lsb_reg = LM3631_REG_BRT_LSB;
> > +
> > +static const struct ti_lmu_bl_reg lm3631_reg_info = {
> > + .init = lm3631_init_data,
> > + .num_init = ARRAY_SIZE(lm3631_init_data),
> > + .channel = lm3631_channel_data,
> > + .ramp = lm3631_ramp_data,
> > + .enable = &lm3631_enable_reg,
> > + .brightness_msb = &lm3631_brightness_msb_reg,
> > + .brightness_lsb = &lm3631_brightness_lsb_reg,
> > +};
> > +
> > +/* LM3632 */
> > +static const struct lmu_bl_reg_data lm3632_init_data[] = {
> > + { LM3632_REG_CONFIG1, LM3632_OVP_MASK, LM3632_OVP_25V },
> > + { LM3632_REG_CONFIG2, LM3632_SWFREQ_MASK, LM3632_SWFREQ_1MHZ },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3632_channel_data[] = {
> > + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_SINGLE_CHANNEL },
> > + { LM3632_REG_ENABLE, LM3632_BL_CHANNEL_MASK, LM3632_BL_DUAL_CHANNEL },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3632_mode_data[] = {
> > + { LM3632_REG_IO_CTRL, LM3632_PWM_MASK, LM3632_PWM_MODE },
> > +};
> > +
> > +static u8 lm3632_enable_reg = LM3632_REG_ENABLE;
> > +static u8 lm3632_brightness_msb_reg = LM3632_REG_BRT_MSB;
> > +static u8 lm3632_brightness_lsb_reg = LM3632_REG_BRT_LSB;
> > +
> > +static const struct ti_lmu_bl_reg lm3632_reg_info = {
> > + .init = lm3632_init_data,
> > + .num_init = ARRAY_SIZE(lm3632_init_data),
> > + .channel = lm3632_channel_data,
> > + .mode = lm3632_mode_data,
> > + .enable = &lm3632_enable_reg,
> > + .brightness_msb = &lm3632_brightness_msb_reg,
> > + .brightness_lsb = &lm3632_brightness_lsb_reg,
> > +};
> > +
> > +/* LM3633 */
> > +static const struct lmu_bl_reg_data lm3633_init_data[] = {
> > + { LM3633_REG_BOOST_CFG, LM3633_OVP_MASK, LM3633_OVP_40V },
> > + { LM3633_REG_BL_RAMP_CONF, LM3633_BL_RAMP_MASK, LM3633_BL_RAMP_EACH },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3633_channel_data[] = {
> > + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED1_CFG_MASK,
> > + LM3633_HVLED1_CFG_SHIFT },
> > + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED2_CFG_MASK,
> > + LM3633_HVLED2_CFG_SHIFT },
> > + { LM3633_REG_HVLED_OUTPUT_CFG, LM3633_HVLED3_CFG_MASK,
> > + LM3633_HVLED3_CFG_SHIFT },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3633_mode_data[] = {
> > + { LM3633_REG_PWM_CFG, LM3633_PWM_A_MASK, LM3633_PWM_A_MASK },
> > + { LM3633_REG_PWM_CFG, LM3633_PWM_B_MASK, LM3633_PWM_B_MASK },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3633_ramp_data[] = {
> > + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPUP_MASK, LM3633_BL_RAMPUP_SHIFT },
> > + { LM3633_REG_BL0_RAMP, LM3633_BL_RAMPDN_MASK, LM3633_BL_RAMPDN_SHIFT },
> > +};
> > +
> > +static u8 lm3633_enable_reg = LM3633_REG_ENABLE;
> > +
> > +static u8 lm3633_brightness_msb_regs[] = {
> > + LM3633_REG_BRT_HVLED_A_MSB,
> > + LM3633_REG_BRT_HVLED_B_MSB,
> > +};
> > +
> > +static u8 lm3633_brightness_lsb_regs[] = {
> > + LM3633_REG_BRT_HVLED_A_LSB,
> > + LM3633_REG_BRT_HVLED_B_LSB,
> > +};
> > +
> > +static const struct ti_lmu_bl_reg lm3633_reg_info = {
> > + .init = lm3633_init_data,
> > + .num_init = ARRAY_SIZE(lm3633_init_data),
> > + .channel = lm3633_channel_data,
> > + .mode = lm3633_mode_data,
> > + .ramp = lm3633_ramp_data,
> > + .ramp_reg_offset = 1, /* For LM3633_REG_BL1_RAMPUP/DN */
> > + .enable = &lm3633_enable_reg,
> > + .brightness_msb = lm3633_brightness_msb_regs,
> > + .brightness_lsb = lm3633_brightness_lsb_regs,
> > +};
> > +
> > +/* LM3695 */
> > +static const struct lmu_bl_reg_data lm3695_init_data[] = {
> > + { LM3695_REG_GP, LM3695_BRT_RW_MASK, LM3695_BRT_RW_MASK },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3695_channel_data[] = {
> > + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_SINGLE_CHANNEL },
> > + { LM3695_REG_GP, LM3695_BL_CHANNEL_MASK, LM3695_BL_DUAL_CHANNEL },
> > +};
> > +
> > +static u8 lm3695_enable_reg = LM3695_REG_GP;
> > +static u8 lm3695_brightness_msb_reg = LM3695_REG_BRT_MSB;
> > +static u8 lm3695_brightness_lsb_reg = LM3695_REG_BRT_LSB;
> > +
> > +static const struct ti_lmu_bl_reg lm3695_reg_info = {
> > + .init = lm3695_init_data,
> > + .num_init = ARRAY_SIZE(lm3695_init_data),
> > + .channel = lm3695_channel_data,
> > + .enable = &lm3695_enable_reg,
> > + .enable_usec = 600,
> > + .brightness_msb = &lm3695_brightness_msb_reg,
> > + .brightness_lsb = &lm3695_brightness_lsb_reg,
> > +};
> > +
> > +/* LM3697 */
> > +static const struct lmu_bl_reg_data lm3697_init_data[] = {
> > + { LM3697_REG_RAMP_CONF, LM3697_RAMP_MASK, LM3697_RAMP_EACH },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3697_channel_data[] = {
> > + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED1_CFG_MASK,
> > + LM3697_HVLED1_CFG_SHIFT },
> > + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED2_CFG_MASK,
> > + LM3697_HVLED2_CFG_SHIFT },
> > + { LM3697_REG_HVLED_OUTPUT_CFG, LM3697_HVLED3_CFG_MASK,
> > + LM3697_HVLED3_CFG_SHIFT },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3697_mode_data[] = {
> > + { LM3697_REG_PWM_CFG, LM3697_PWM_A_MASK, LM3697_PWM_A_MASK },
> > + { LM3697_REG_PWM_CFG, LM3697_PWM_B_MASK, LM3697_PWM_B_MASK },
> > +};
> > +
> > +static const struct lmu_bl_reg_data lm3697_ramp_data[] = {
> > + { LM3697_REG_BL0_RAMP, LM3697_RAMPUP_MASK, LM3697_RAMPUP_SHIFT },
> > + { LM3697_REG_BL0_RAMP, LM3697_RAMPDN_MASK, LM3697_RAMPDN_SHIFT },
> > +};
> > +
> > +static u8 lm3697_enable_reg = LM3697_REG_ENABLE;
> > +
> > +static u8 lm3697_brightness_msb_regs[] = {
> > + LM3697_REG_BRT_A_MSB,
> > + LM3697_REG_BRT_B_MSB,
> > +};
> > +
> > +static u8 lm3697_brightness_lsb_regs[] = {
> > + LM3697_REG_BRT_A_LSB,
> > + LM3697_REG_BRT_B_LSB,
> > +};
> > +
> > +static const struct ti_lmu_bl_reg lm3697_reg_info = {
> > + .init = lm3697_init_data,
> > + .num_init = ARRAY_SIZE(lm3697_init_data),
> > + .channel = lm3697_channel_data,
> > + .mode = lm3697_mode_data,
> > + .ramp = lm3697_ramp_data,
> > + .ramp_reg_offset = 1, /* For LM3697_REG_BL1_RAMPUP/DN */
> > + .enable = &lm3697_enable_reg,
> > + .brightness_msb = lm3697_brightness_msb_regs,
> > + .brightness_lsb = lm3697_brightness_lsb_regs,
> > +};
> > +
> > +static int lm3532_ramp_table[] = { 0, 1, 2, 4, 8, 16, 32, 65 };
> > +
> > +static int lm3631_ramp_table[] = {
> > + 0, 1, 2, 5, 10, 20, 50, 100,
> > + 250, 500, 750, 1000, 1500, 2000, 3000, 4000,
> > +};
> > +
> > +static int common_ramp_table[] = {
> > + 2, 250, 500, 1000, 2000, 4000, 8000, 16000,
> > +};
> > +
> > +#define LM3532_MAX_CHANNELS 3
> > +#define LM3631_MAX_CHANNELS 2
> > +#define LM3632_MAX_CHANNELS 2
> > +#define LM3633_MAX_CHANNELS 3
> > +#define LM3695_MAX_CHANNELS 2
> > +#define LM3697_MAX_CHANNELS 3
> > +
> > +const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID] = {
> > + {
> > + .reginfo = &lm3532_reg_info,
> > + .num_channels = LM3532_MAX_CHANNELS,
> > + .max_brightness = MAX_BRIGHTNESS_8BIT,
> > + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> > + .ramp_table = lm3532_ramp_table,
> > + .size_ramp = ARRAY_SIZE(lm3532_ramp_table),
> > + },
> > + {
> > + .reginfo = &lm3631_reg_info,
> > + .num_channels = LM3631_MAX_CHANNELS,
> > + .max_brightness = MAX_BRIGHTNESS_11BIT,
> > + .pwm_action = UPDATE_PWM_ONLY,
> > + .ramp_table = lm3631_ramp_table,
> > + .size_ramp = ARRAY_SIZE(lm3631_ramp_table),
> > + },
> > + {
> > + .reginfo = &lm3632_reg_info,
> > + .num_channels = LM3632_MAX_CHANNELS,
> > + .max_brightness = MAX_BRIGHTNESS_11BIT,
> > + .pwm_action = UPDATE_PWM_ONLY,
> > + },
> > + {
> > + .reginfo = &lm3633_reg_info,
> > + .num_channels = LM3633_MAX_CHANNELS,
> > + .max_brightness = MAX_BRIGHTNESS_11BIT,
> > + .pwm_action = UPDATE_MAX_BRT,
> > + .ramp_table = common_ramp_table,
> > + .size_ramp = ARRAY_SIZE(common_ramp_table),
> > + .fault_monitor_used = true,
> > + },
> > + {
> > + .reginfo = &lm3695_reg_info,
> > + .num_channels = LM3695_MAX_CHANNELS,
> > + .max_brightness = MAX_BRIGHTNESS_11BIT,
> > + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> > + },
> > + {
> > + .reginfo = &lm3697_reg_info,
> > + .num_channels = LM3697_MAX_CHANNELS,
> > + .max_brightness = MAX_BRIGHTNESS_11BIT,
> > + .pwm_action = UPDATE_PWM_AND_BRT_REGISTER,
> > + .ramp_table = common_ramp_table,
> > + .size_ramp = ARRAY_SIZE(common_ramp_table),
> > + .fault_monitor_used = true,
> > + },
> > +};
> > diff --git a/drivers/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 @@
> > +/*
>
> Inconsistent licensing here the core has
> // SPDX-License-Identifier: GPL-2.0
See above.
> > + * TI LMU (Lighting Management Unit) Backlight Device Data Definitions
> > + *
> > + * Copyright 2015 Texas Instruments
> > + *
> > + * Author: Milo Kim <milo.kim@ti.com>
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > + */
> > +
> > +#ifndef __TI_LMU_BACKLIGHT_H__
> > +#define __TI_LMU_BACKLIGHT_H__
> > +
> > +#include <linux/mfd/ti-lmu.h>
> > +#include <linux/mfd/ti-lmu-register.h>
> > +
> > +#define MAX_BRIGHTNESS_8BIT 255
> > +#define MAX_BRIGHTNESS_11BIT 2047
> > +
> > +enum ti_lmu_bl_pwm_action {
> > + /* Update PWM duty, no brightness register update is required */
> > + UPDATE_PWM_ONLY,
> > + /* Update not only duty but also brightness register */
> > + UPDATE_PWM_AND_BRT_REGISTER,
> > + /* Update max value in brightness registers */
> > + UPDATE_MAX_BRT,
> > +};
> > +
> > +struct lmu_bl_reg_data {
> > + u8 reg;
> > + u8 mask;
> > + u8 val;
> > +};
> > +
> > +/**
> > + * struct ti_lmu_bl_reg
> > + *
> > + * @init: Device initialization registers
> > + * @num_init: Numbers of initialization registers
> > + * @channel: Backlight channel configuration registers
> > + * @mode: Brightness control mode registers
> > + * @ramp: Ramp registers for lighting effect
> > + * @ramp_reg_offset: Ramp register offset.
> > + * Only used for multiple ramp registers.
> > + * @enable: Enable control register address
> > + * @enable_usec: Delay time for updating enable register.
> > + * Unit is microsecond.
> > + * @brightness_msb: Brightness MSB(Upper 8 bits) registers.
> > + * Concatenated with LSB in 11 bit dimming mode.
> > + * In 8 bit dimming, only MSB is used.
> > + * @brightness_lsb: Brightness LSB(Lower 3 bits) registers.
> > + * Only valid in 11 bit dimming mode.
> > + */
> > +struct ti_lmu_bl_reg {
> > + const struct lmu_bl_reg_data *init;
> > + int num_init;
> > + const struct lmu_bl_reg_data *channel;
> > + const struct lmu_bl_reg_data *mode;
> > + const struct lmu_bl_reg_data *ramp;
> > + int ramp_reg_offset;
> > + u8 *enable;
> > + unsigned long enable_usec;
> > + u8 *brightness_msb;
> > + u8 *brightness_lsb;
> > +};
> > +
> > +/**
> > + * struct ti_lmu_bl_cfg
> > + *
> > + * @reginfo: Device register configuration
> > + * @num_channels: Number of backlight channels
> > + * @max_brightness: Max brightness value of backlight device
> > + * @pwm_action: How to control brightness registers in PWM mode
> > + * @ramp_table: [Optional] Ramp time table for lighting effect.
> > + * It's used for searching approximate register index.
> > + * @size_ramp: [Optional] Size of ramp table
> > + * @fault_monitor_used: [Optional] Set true if the device needs to handle
> > + * LMU fault monitor event.
> > + *
> > + * This structure is used for device specific data configuration.
> > + */
> > +struct ti_lmu_bl_cfg {
> > + const struct ti_lmu_bl_reg *reginfo;
> > + int num_channels;
> > + int max_brightness;
> > + enum ti_lmu_bl_pwm_action pwm_action;
> > + int *ramp_table;
> > + int size_ramp;
> > + bool fault_monitor_used;
> > +};
> > +
> > +extern const struct ti_lmu_bl_cfg lmu_bl_cfg[LMU_MAX_ID];
> > +#endif
> >
-- Sebastian
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 62+ messages in thread