Message ID | 20180330172414.26575-9-sebastian.reichel@collabora.co.uk (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
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
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 > -- To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
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 >
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
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
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
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
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