From patchwork Mon Jul 17 13:39:56 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sebastian Reichel X-Patchwork-Id: 9845085 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id A0B1B60386 for ; Mon, 17 Jul 2017 13:40:29 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 8EDFD27C05 for ; Mon, 17 Jul 2017 13:40:29 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 82F7728503; Mon, 17 Jul 2017 13:40:29 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CE23127C05 for ; Mon, 17 Jul 2017 13:40:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751541AbdGQNkN (ORCPT ); Mon, 17 Jul 2017 09:40:13 -0400 Received: from bhuna.collabora.co.uk ([46.235.227.227]:33929 "EHLO bhuna.collabora.co.uk" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751521AbdGQNkL (ORCPT ); Mon, 17 Jul 2017 09:40:11 -0400 Received: from [127.0.0.1] (localhost [127.0.0.1]) (Authenticated sender: sre) with ESMTPSA id 85015260713 From: Sebastian Reichel To: Sebastian Reichel , Milo Kim , Lee Jones , Daniel Thompson , Rob Herring , Tony Lindgren Cc: Jingoo Han , Mark Rutland , linux-kernel@vger.kernel.org, linux-fbdev@vger.kernel.org, devicetree@vger.kernel.org, linux-omap@vger.kernel.org, Milo Kim , Sebastian Reichel Subject: [PATCHv2 2/2] backlight: add TI LMU backlight driver Date: Mon, 17 Jul 2017 15:39:56 +0200 Message-Id: <20170717133956.20691-3-sebastian.reichel@collabora.co.uk> X-Mailer: git-send-email 2.13.2 In-Reply-To: <20170717133956.20691-1-sebastian.reichel@collabora.co.uk> References: <20170717133956.20691-1-sebastian.reichel@collabora.co.uk> Sender: linux-fbdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fbdev@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Milo Kim This is consolidated driver which supports the following backlight devices: LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697. Structure --------- It consists of two parts - core and data. Core part supports features below. - Backlight subsystem control - Channel configuration from DT properties - Light dimming effect control: ramp up and down. - LMU fault monitor notifier handling - PWM brightness control Data part describes device specific data. - Register value configuration for each LMU device : initialization, channel configuration, control mode, enable and brightness. - PWM action configuration - Light dimming effect table - Option for LMU fault monitor support Signed-off-by: Milo Kim Signed-off-by: Sebastian Reichel --- drivers/video/backlight/Kconfig | 7 + drivers/video/backlight/Makefile | 3 + drivers/video/backlight/ti-lmu-backlight-core.c | 729 ++++++++++++++++++++++++ drivers/video/backlight/ti-lmu-backlight-data.c | 304 ++++++++++ drivers/video/backlight/ti-lmu-backlight-data.h | 95 +++ 5 files changed, 1138 insertions(+) create mode 100644 drivers/video/backlight/ti-lmu-backlight-core.c create mode 100644 drivers/video/backlight/ti-lmu-backlight-data.c create mode 100644 drivers/video/backlight/ti-lmu-backlight-data.h diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 4e1d2ad50ba1..c3cc8334f9a2 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -427,6 +427,13 @@ config BACKLIGHT_SKY81452 To compile this driver as a module, choose M here: the module will be called sky81452-backlight +config BACKLIGHT_TI_LMU + tristate "Backlight driver for TI LMU" + depends on BACKLIGHT_CLASS_DEVICE && MFD_TI_LMU + help + Say Y to enable the backlight driver for TI LMU devices. + This supports LM3532, LM3631, LM3632, LM3633, LM3695 and LM3697. + config BACKLIGHT_TPS65217 tristate "TPS65217 Backlight" depends on BACKLIGHT_CLASS_DEVICE && MFD_TPS65217 diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 8905129691e8..c532e43e344b 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -52,6 +52,9 @@ obj-$(CONFIG_BACKLIGHT_PM8941_WLED) += pm8941-wled.o obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o obj-$(CONFIG_BACKLIGHT_SKY81452) += sky81452-backlight.o +ti-lmu-backlight-objs := ti-lmu-backlight-core.o \ + ti-lmu-backlight-data.o +obj-$(CONFIG_BACKLIGHT_TI_LMU) += ti-lmu-backlight.o obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o obj-$(CONFIG_BACKLIGHT_TPS65217) += tps65217_bl.o obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o diff --git a/drivers/video/backlight/ti-lmu-backlight-core.c b/drivers/video/backlight/ti-lmu-backlight-core.c new file mode 100644 index 000000000000..fca95080ec27 --- /dev/null +++ b/drivers/video/backlight/ti-lmu-backlight-core.c @@ -0,0 +1,729 @@ +/* + * TI LMU (Lighting Management Unit) Backlight Driver + * + * Copyright 2015 Texas Instruments + * + * Author: Milo Kim + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ti-lmu-backlight-data.h" + +enum ti_lmu_bl_ctrl_mode { + BL_REGISTER_BASED, + BL_PWM_BASED, +}; + +enum ti_lmu_bl_ramp_mode { + BL_RAMP_UP, + BL_RAMP_DOWN, +}; + +struct ti_lmu_bl; + +/** + * struct ti_lmu_bl_chip + * + * @dev: Parent device pointer + * @lmu: LMU structure. + * Used for register R/W access and notification. + * @cfg: Device configuration data + * @lmu_bl: Multiple backlight channels + * @num_backlights: Number of backlight channels + * @nb: Notifier block for handling LMU fault monitor event + * + * One backlight chip can have multiple backlight channels, 'ti_lmu_bl'. + */ +struct ti_lmu_bl_chip { + struct device *dev; + struct ti_lmu *lmu; + const struct ti_lmu_bl_cfg *cfg; + struct ti_lmu_bl *lmu_bl; + int num_backlights; + struct notifier_block nb; +}; + +/** + * struct ti_lmu_bl + * + * @chip: Pointer to parent backlight device + * @bl_dev: Backlight subsystem device structure + * @bank_id: Backlight bank ID + * @name: Backlight channel name + * @mode: Backlight control mode + * @led_sources: Backlight output channel configuration. + * Bit mask is set on parsing DT. + * @default_brightness: [Optional] Initial brightness value + * @ramp_up_msec: [Optional] Ramp up time + * @ramp_down_msec: [Optional] Ramp down time + * @pwm_period: [Optional] PWM period + * @pwm: [Optional] PWM subsystem structure + * + * Each backlight device has its own channel configuration. + * For chip control, parent chip data structure is used. + */ +struct ti_lmu_bl { + struct ti_lmu_bl_chip *chip; + struct backlight_device *bl_dev; + + int bank_id; + const char *name; + enum ti_lmu_bl_ctrl_mode mode; + unsigned long led_sources; + + unsigned int default_brightness; + + /* Used for lighting effect */ + unsigned int ramp_up_msec; + unsigned int ramp_down_msec; + + /* Only valid in PWM mode */ + unsigned int pwm_period; + struct pwm_device *pwm; +}; + +#define NUM_DUAL_CHANNEL 2 +#define LMU_BACKLIGHT_DUAL_CHANNEL_USED (BIT(0) | BIT(1)) +#define LMU_BACKLIGHT_11BIT_LSB_MASK (BIT(0) | BIT(1) | BIT(2)) +#define LMU_BACKLIGHT_11BIT_MSB_SHIFT 3 +#define DEFAULT_PWM_NAME "lmu-backlight" + +static int ti_lmu_backlight_enable(struct ti_lmu_bl *lmu_bl, bool enable) +{ + struct ti_lmu_bl_chip *chip = lmu_bl->chip; + struct regmap *regmap = chip->lmu->regmap; + unsigned long enable_time = chip->cfg->reginfo->enable_usec; + u8 *reg = chip->cfg->reginfo->enable; + u8 mask = BIT(lmu_bl->bank_id); + u8 val = (enable == true) ? mask : 0; + int ret; + + if (!reg) + return -EINVAL; + + ret = regmap_update_bits(regmap, *reg, mask, val); + if (ret) + return ret; + + if (enable_time > 0) + usleep_range(enable_time, enable_time + 100); + + return 0; +} + +static int ti_lmu_backlight_pwm_ctrl(struct ti_lmu_bl *lmu_bl, int brightness, + int max_brightness) +{ + struct pwm_state state = { }; + int ret; + + if (!lmu_bl->pwm) { + lmu_bl->pwm = devm_pwm_get(lmu_bl->chip->dev, DEFAULT_PWM_NAME); + if (IS_ERR(lmu_bl->pwm)) { + ret = PTR_ERR(lmu_bl->pwm); + lmu_bl->pwm = NULL; + dev_err(lmu_bl->chip->dev, + "Can not get PWM device, err: %d\n", ret); + return ret; + } + } + + pwm_init_state(lmu_bl->pwm, &state); + state.period = lmu_bl->pwm_period; + state.duty_cycle = brightness * state.period / max_brightness; + + if (state.duty_cycle) + state.enabled = true; + else + state.enabled = false; + + ret = pwm_apply_state(lmu_bl->pwm, &state); + if (ret) + dev_err(lmu_bl->chip->dev, "Failed to configure PWM: %d", ret); + + return ret; +} + +static int ti_lmu_backlight_update_brightness_register(struct ti_lmu_bl *lmu_bl, + int brightness) +{ + const struct ti_lmu_bl_cfg *cfg = lmu_bl->chip->cfg; + const struct ti_lmu_bl_reg *reginfo = cfg->reginfo; + struct regmap *regmap = lmu_bl->chip->lmu->regmap; + u8 reg, val; + int ret; + + /* + * Brightness register update + * + * 11 bit dimming: update LSB bits and write MSB byte. + * MSB brightness should be shifted. + * 8 bit dimming: write MSB byte. + */ + if (cfg->max_brightness == MAX_BRIGHTNESS_11BIT) { + reg = reginfo->brightness_lsb[lmu_bl->bank_id]; + ret = regmap_update_bits(regmap, reg, + LMU_BACKLIGHT_11BIT_LSB_MASK, + brightness); + if (ret) + return ret; + + val = brightness >> LMU_BACKLIGHT_11BIT_MSB_SHIFT; + } else { + val = brightness; + } + + reg = reginfo->brightness_msb[lmu_bl->bank_id]; + return regmap_write(regmap, reg, val); +} + +static int ti_lmu_backlight_update_status(struct backlight_device *bl_dev) +{ + struct ti_lmu_bl *lmu_bl = bl_get_data(bl_dev); + const struct ti_lmu_bl_cfg *cfg = lmu_bl->chip->cfg; + int brightness = bl_dev->props.brightness; + bool enable = brightness > 0; + int ret; + + if (bl_dev->props.state & BL_CORE_SUSPENDED) + brightness = 0; + + ret = ti_lmu_backlight_enable(lmu_bl, enable); + if (ret) + return ret; + + if (lmu_bl->mode == BL_PWM_BASED) { + ti_lmu_backlight_pwm_ctrl(lmu_bl, brightness, + bl_dev->props.max_brightness); + + switch (cfg->pwm_action) { + case UPDATE_PWM_ONLY: + /* No register update is required */ + return 0; + case UPDATE_MAX_BRT: + /* + * PWM can start from any non-zero code and dim down + * to zero. So, brightness register should be updated + * even in PWM mode. + */ + if (brightness > 0) + brightness = MAX_BRIGHTNESS_11BIT; + else + brightness = 0; + break; + default: + break; + } + } + + return ti_lmu_backlight_update_brightness_register(lmu_bl, brightness); +} + +static const struct backlight_ops lmu_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = ti_lmu_backlight_update_status, +}; + +static int ti_lmu_backlight_of_get_ctrl_bank(struct device_node *np, + struct ti_lmu_bl *lmu_bl) +{ + const char *name; + u32 *sources; + int num_channels = lmu_bl->chip->cfg->num_channels; + int ret, num_sources; + + sources = devm_kzalloc(lmu_bl->chip->dev, num_channels, GFP_KERNEL); + if (!sources) + return -ENOMEM; + + if (!of_property_read_string(np, "label", &name)) + lmu_bl->name = name; + else + lmu_bl->name = np->name; + + ret = of_property_count_u32_elems(np, "led-sources"); + if (ret < 0 || ret > num_channels) + return -EINVAL; + + num_sources = ret; + ret = of_property_read_u32_array(np, "led-sources", sources, + num_sources); + if (ret) + return ret; + + lmu_bl->led_sources = 0; + while (num_sources--) + set_bit(sources[num_sources], &lmu_bl->led_sources); + + return 0; +} + +static void ti_lmu_backlight_of_get_light_properties(struct device_node *np, + struct ti_lmu_bl *lmu_bl) +{ + of_property_read_u32(np, "default-brightness-level", + &lmu_bl->default_brightness); + + of_property_read_u32(np, "ramp-up-msec", &lmu_bl->ramp_up_msec); + of_property_read_u32(np, "ramp-down-msec", &lmu_bl->ramp_down_msec); +} + +static void ti_lmu_backlight_of_get_brightness_mode(struct device_node *np, + struct ti_lmu_bl *lmu_bl) +{ + of_property_read_u32(np, "pwm-period", &lmu_bl->pwm_period); + + if (lmu_bl->pwm_period > 0) + lmu_bl->mode = BL_PWM_BASED; + else + lmu_bl->mode = BL_REGISTER_BASED; +} + +static int ti_lmu_backlight_of_create(struct ti_lmu_bl_chip *chip, + struct device_node *np) +{ + struct device_node *child; + struct ti_lmu_bl *lmu_bl, *each; + int ret, num_backlights; + int i = 0; + + num_backlights = of_get_child_count(np); + if (num_backlights == 0) { + dev_err(chip->dev, "No backlight strings\n"); + return -ENODEV; + } + + /* One chip can have mulitple backlight strings */ + lmu_bl = devm_kzalloc(chip->dev, sizeof(*lmu_bl) * num_backlights, + GFP_KERNEL); + if (!lmu_bl) + return -ENOMEM; + + /* Child is mapped to LMU backlight control bank */ + for_each_child_of_node(np, child) { + each = lmu_bl + i; + each->bank_id = i; + each->chip = chip; + + ret = ti_lmu_backlight_of_get_ctrl_bank(child, each); + if (ret) { + of_node_put(np); + return ret; + } + + ti_lmu_backlight_of_get_light_properties(child, each); + ti_lmu_backlight_of_get_brightness_mode(child, each); + + i++; + } + + chip->lmu_bl = lmu_bl; + chip->num_backlights = num_backlights; + + return 0; +} + +static int ti_lmu_backlight_check_channel(struct ti_lmu_bl *lmu_bl) +{ + const struct ti_lmu_bl_cfg *cfg = lmu_bl->chip->cfg; + const struct ti_lmu_bl_reg *reginfo = lmu_bl->chip->cfg->reginfo; + + if (!reginfo->brightness_msb) + return -EINVAL; + + if (cfg->max_brightness > MAX_BRIGHTNESS_8BIT) { + if (!reginfo->brightness_lsb) + return -EINVAL; + } + + return 0; +} + +static int ti_lmu_backlight_create_channel(struct ti_lmu_bl *lmu_bl) +{ + struct regmap *regmap = lmu_bl->chip->lmu->regmap; + const struct lmu_bl_reg_data *regdata = + lmu_bl->chip->cfg->reginfo->channel; + int num_channels = lmu_bl->chip->cfg->num_channels; + int i, ret; + u8 shift; + + /* + * How to create backlight output channels: + * Check 'led_sources' bit and update registers. + * + * 1) Dual channel configuration + * The 1st register data is used for single channel. + * The 2nd register data is used for dual channel. + * + * 2) Multiple channel configuration + * Each register data is mapped to bank ID. + * Bit shift operation is defined in channel registers. + * + * Channel register data consists of address, mask, value. + */ + + if (num_channels == NUM_DUAL_CHANNEL) { + if (lmu_bl->led_sources == LMU_BACKLIGHT_DUAL_CHANNEL_USED) + regdata++; + + return regmap_update_bits(regmap, regdata->reg, regdata->mask, + regdata->val); + } + + for (i = 0; regdata && i < num_channels; i++) { + /* + * Note that the result of regdata->val is shift bit. + * The bank_id should be shifted for the channel configuration. + */ + if (test_bit(i, &lmu_bl->led_sources)) { + shift = regdata->val; + ret = regmap_update_bits(regmap, regdata->reg, + regdata->mask, + lmu_bl->bank_id << shift); + if (ret) + return ret; + } + + regdata++; + } + + return 0; +} + +static int ti_lmu_backlight_update_ctrl_mode(struct ti_lmu_bl *lmu_bl) +{ + struct regmap *regmap = lmu_bl->chip->lmu->regmap; + const struct lmu_bl_reg_data *regdata = + lmu_bl->chip->cfg->reginfo->mode + lmu_bl->bank_id; + u8 val = regdata->val; + + if (!regdata) + return 0; + + /* + * Update PWM configuration register. + * If the mode is register based, then clear the bit. + */ + if (lmu_bl->mode != BL_PWM_BASED) + val = 0; + + return regmap_update_bits(regmap, regdata->reg, regdata->mask, val); +} + +static int ti_lmu_backlight_convert_ramp_to_index(struct ti_lmu_bl *lmu_bl, + enum ti_lmu_bl_ramp_mode mode) +{ + const int *ramp_table = lmu_bl->chip->cfg->ramp_table; + const int size = lmu_bl->chip->cfg->size_ramp; + unsigned int msec; + int i; + + if (!ramp_table) + return -EINVAL; + + switch (mode) { + case BL_RAMP_UP: + msec = lmu_bl->ramp_up_msec; + break; + case BL_RAMP_DOWN: + msec = lmu_bl->ramp_down_msec; + break; + default: + return -EINVAL; + } + + if (msec <= ramp_table[0]) + return 0; + + if (msec > ramp_table[size - 1]) + return size - 1; + + for (i = 1; i < size; i++) { + if (msec == ramp_table[i]) + return i; + + /* Find an approximate index by looking up the table */ + if (msec > ramp_table[i - 1] && msec < ramp_table[i]) { + if (msec - ramp_table[i - 1] < ramp_table[i] - msec) + return i - 1; + else + return i; + } + } + + return -EINVAL; +} + +static int ti_lmu_backlight_set_ramp(struct ti_lmu_bl *lmu_bl) +{ + struct regmap *regmap = lmu_bl->chip->lmu->regmap; + const struct ti_lmu_bl_reg *reginfo = lmu_bl->chip->cfg->reginfo; + int offset = reginfo->ramp_reg_offset; + int i, ret, index; + struct lmu_bl_reg_data regdata; + + for (i = BL_RAMP_UP; i <= BL_RAMP_DOWN; i++) { + index = ti_lmu_backlight_convert_ramp_to_index(lmu_bl, i); + if (index > 0) { + if (!reginfo->ramp) + break; + + regdata = reginfo->ramp[i]; + if (lmu_bl->bank_id != 0) + regdata.val += offset; + + /* regdata.val is shift bit */ + ret = regmap_update_bits(regmap, regdata.reg, + regdata.mask, + index << regdata.val); + if (ret) + return ret; + } + } + + return 0; +} + +static int ti_lmu_backlight_configure(struct ti_lmu_bl *lmu_bl) +{ + int ret; + + ret = ti_lmu_backlight_check_channel(lmu_bl); + if (ret) + return ret; + + ret = ti_lmu_backlight_create_channel(lmu_bl); + if (ret) + return ret; + + ret = ti_lmu_backlight_update_ctrl_mode(lmu_bl); + if (ret) + return ret; + + return ti_lmu_backlight_set_ramp(lmu_bl); +} + +static int ti_lmu_backlight_init(struct ti_lmu_bl_chip *chip) +{ + struct regmap *regmap = chip->lmu->regmap; + const struct lmu_bl_reg_data *regdata = + chip->cfg->reginfo->init; + int num_init = chip->cfg->reginfo->num_init; + int i, ret; + + for (i = 0; regdata && i < num_init; i++) { + ret = regmap_update_bits(regmap, regdata->reg, regdata->mask, + regdata->val); + if (ret) + return ret; + + regdata++; + } + + return 0; +} + +static int ti_lmu_backlight_reload(struct ti_lmu_bl_chip *chip) +{ + struct ti_lmu_bl *each; + int i, ret; + + ret = ti_lmu_backlight_init(chip); + if (ret) + return ret; + + for (i = 0; i < chip->num_backlights; i++) { + each = chip->lmu_bl + i; + ret = ti_lmu_backlight_configure(each); + if (ret) + return ret; + + ret = backlight_update_status(each->bl_dev); + if (ret) + return ret; + } + + return 0; +} + +static int ti_lmu_backlight_add_device(struct device *dev, + struct ti_lmu_bl *lmu_bl) +{ + struct backlight_device *bl_dev; + struct backlight_properties props; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.brightness = lmu_bl->default_brightness; + props.max_brightness = lmu_bl->chip->cfg->max_brightness; + + bl_dev = devm_backlight_device_register(dev, lmu_bl->name, + lmu_bl->chip->dev, lmu_bl, + &lmu_backlight_ops, &props); + if (IS_ERR(bl_dev)) + return PTR_ERR(bl_dev); + + lmu_bl->bl_dev = bl_dev; + + return 0; +} + +static struct ti_lmu_bl_chip * +ti_lmu_backlight_register(struct device *dev, struct ti_lmu *lmu, + const struct ti_lmu_bl_cfg *cfg) +{ + struct ti_lmu_bl_chip *chip; + struct ti_lmu_bl *each; + int i, ret; + + if (!cfg) { + dev_err(dev, "Operation is not configured\n"); + return ERR_PTR(-EINVAL); + } + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return ERR_PTR(-ENOMEM); + + chip->dev = dev; + chip->lmu = lmu; + chip->cfg = cfg; + + ret = ti_lmu_backlight_of_create(chip, dev->of_node); + if (ret) + return ERR_PTR(ret); + + ret = ti_lmu_backlight_init(chip); + if (ret) { + dev_err(dev, "Backlight init err: %d\n", ret); + return ERR_PTR(ret); + } + + for (i = 0; i < chip->num_backlights; i++) { + each = chip->lmu_bl + i; + + ret = ti_lmu_backlight_configure(each); + if (ret) { + dev_err(dev, "Backlight config err: %d\n", ret); + return ERR_PTR(ret); + } + + ret = ti_lmu_backlight_add_device(dev, each); + if (ret) { + dev_err(dev, "Backlight device err: %d\n", ret); + return ERR_PTR(ret); + } + + ret = backlight_update_status(each->bl_dev); + if (ret) { + dev_err(dev, "Backlight update err: %d\n", ret); + return ERR_PTR(ret); + } + } + + return chip; +} + +static void ti_lmu_backlight_unregister(struct ti_lmu_bl_chip *chip) +{ + struct ti_lmu_bl *each; + int i; + + /* Turn off the brightness */ + for (i = 0; i < chip->num_backlights; i++) { + each = chip->lmu_bl + i; + each->bl_dev->props.brightness = 0; + backlight_update_status(each->bl_dev); + } +} + +static int ti_lmu_backlight_monitor_notifier(struct notifier_block *nb, + unsigned long action, void *unused) +{ + struct ti_lmu_bl_chip *chip = container_of(nb, struct ti_lmu_bl_chip, + nb); + + if (action == LMU_EVENT_MONITOR_DONE) { + if (ti_lmu_backlight_reload(chip)) + return NOTIFY_STOP; + } + + return NOTIFY_OK; +} + +static int ti_lmu_backlight_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ti_lmu *lmu = dev_get_drvdata(dev->parent); + struct ti_lmu_bl_chip *chip; + int ret; + + chip = ti_lmu_backlight_register(dev, lmu, &lmu_bl_cfg[pdev->id]); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + /* + * Notifier callback is required because backlight device needs + * reconfiguration after fault detection procedure is done by + * ti-lmu-fault-monitor driver. + */ + if (chip->cfg->fault_monitor_used) { + chip->nb.notifier_call = ti_lmu_backlight_monitor_notifier; + ret = blocking_notifier_chain_register(&chip->lmu->notifier, + &chip->nb); + if (ret) + return ret; + } + + platform_set_drvdata(pdev, chip); + + return 0; +} + +static int ti_lmu_backlight_remove(struct platform_device *pdev) +{ + struct ti_lmu_bl_chip *chip = platform_get_drvdata(pdev); + + if (chip->cfg->fault_monitor_used) + blocking_notifier_chain_unregister(&chip->lmu->notifier, + &chip->nb); + + ti_lmu_backlight_unregister(chip); + + return 0; +} + +static struct platform_driver ti_lmu_backlight_driver = { + .probe = ti_lmu_backlight_probe, + .remove = ti_lmu_backlight_remove, + .driver = { + .name = "ti-lmu-backlight", + }, +}; + +module_platform_driver(ti_lmu_backlight_driver) + +MODULE_DESCRIPTION("TI LMU Backlight Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:ti-lmu-backlight"); diff --git a/drivers/video/backlight/ti-lmu-backlight-data.c b/drivers/video/backlight/ti-lmu-backlight-data.c new file mode 100644 index 000000000000..583136cb934d --- /dev/null +++ b/drivers/video/backlight/ti-lmu-backlight-data.c @@ -0,0 +1,304 @@ +/* + * TI LMU (Lighting Management Unit) Backlight Device Data + * + * Copyright 2015 Texas Instruments + * + * Author: Milo Kim + * + * 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 + * + * 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 +#include + +#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