[PATCH 02/10] backlight: Add TI LMU backlight common driver

From: Milo Kim
Date: Fri Feb 14 2014 - 01:31:36 EST


TI LMU backlight driver provides common driver features.
Chip specific configuration is handled by each backlight driver such like
LM3532, LM3631, LM3633, LM3695 and LM3697.

It supports common features as below.
- Consistent device control flow
- Control bank assignment from the platform data
- Backlight subsystem control
- PWM brightness control
- Shared device tree node

Cc: Jingoo Han <jg1.han@xxxxxxxxxxx>
Cc: Bryan Wu <cooloney@xxxxxxxxx>
Cc: Lee Jones <lee.jones@xxxxxxxxxx>
Signed-off-by: Milo Kim <milo.kim@xxxxxx>
---
drivers/video/backlight/Kconfig | 7 +
drivers/video/backlight/Makefile | 1 +
drivers/video/backlight/ti-lmu-backlight.c | 369 ++++++++++++++++++++++++++++
drivers/video/backlight/ti-lmu-backlight.h | 78 ++++++
4 files changed, 455 insertions(+)
create mode 100644 drivers/video/backlight/ti-lmu-backlight.c
create mode 100644 drivers/video/backlight/ti-lmu-backlight.h

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 5a3eb2e..3641698 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -384,6 +384,13 @@ config BACKLIGHT_LM3639
help
This supports TI LM3639 Backlight + 1.5A Flash LED Driver

+config TI_LMU_BACKLIGHT
+ tristate "Backlight driver for TI LMU"
+ depends on BACKLIGHT_LM3532 || BACKLIGHT_LM3631 || BACKLIGHT_LM3633 || BACKLIGHT_LM3695 || BACKLIGHT_LM3697
+ help
+ TI LMU backlight driver provides common driver features.
+ Chip specific configuration is handled by each backlight driver.
+
config BACKLIGHT_LP855X
tristate "Backlight driver for TI LP855X"
depends on BACKLIGHT_CLASS_DEVICE && I2C
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index bb82002..f80e046 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_BACKLIGHT_HP700) += jornada720_bl.o
obj-$(CONFIG_BACKLIGHT_LM3533) += lm3533_bl.o
obj-$(CONFIG_BACKLIGHT_LM3630A) += lm3630a_bl.o
obj-$(CONFIG_BACKLIGHT_LM3639) += lm3639_bl.o
+obj-$(CONFIG_TI_LMU_BACKLIGHT) += ti-lmu-backlight.o
obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o
obj-$(CONFIG_BACKLIGHT_LP855X) += lp855x_bl.o
obj-$(CONFIG_BACKLIGHT_LP8788) += lp8788_bl.o
diff --git a/drivers/video/backlight/ti-lmu-backlight.c b/drivers/video/backlight/ti-lmu-backlight.c
new file mode 100644
index 0000000..5ceb5e8
--- /dev/null
+++ b/drivers/video/backlight/ti-lmu-backlight.c
@@ -0,0 +1,369 @@
+/*
+ * TI LMU Backlight Common Driver
+ *
+ * Copyright 2014 Texas Instruments
+ *
+ * Author: Milo Kim <milo.kim@xxxxxx>
+ *
+ * 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.
+ *
+ * LMU backlight driver supports common features as below.
+ *
+ * - Consistent device control flow by using ti_lmu_bl_ops
+ * - Control bank assignment from the platform data
+ * - Backlight subsystem control
+ * - PWM brightness control
+ * - Shared device tree node
+ *
+ * Sequence of LMU backlight control
+ *
+ * (Chip dependent backlight driver) (TI LMU Backlight Common)
+ *
+ * Operation configuration
+ * ti_lmu_backlight_init_device() --->
+ * Initialization <--- ops->init()
+ *
+ * ti_lmu_backlight_register() --->
+ * Backlight configuration <--- ops->configure()
+ *
+ * Runtime brightness control
+ * Enable register control <--- ops->bl_enable()
+ * Brightness register control <--- ops->update_brightness()
+ *
+ */
+
+#include <linux/backlight.h>
+#include <linux/err.h>
+#include <linux/mfd/ti-lmu.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+
+#include "ti-lmu-backlight.h"
+
+#define DEFAULT_BL_NAME "lcd-backlight"
+
+static int ti_lmu_backlight_enable(struct ti_lmu_bl *lmu_bl, int enable)
+{
+ const struct ti_lmu_bl_ops *ops = lmu_bl->chip->ops;
+
+ if (ops->bl_enable)
+ return ops->bl_enable(lmu_bl, enable);
+
+ return 0;
+}
+
+static void ti_lmu_backlight_pwm_ctrl(struct ti_lmu_bl *lmu_bl, int br,
+ int max_br)
+{
+ struct pwm_device *pwm;
+ unsigned int duty, period;
+
+ /* Request a PWM device with the consumer name */
+ if (!lmu_bl->pwm) {
+ pwm = devm_pwm_get(lmu_bl->chip->dev, lmu_bl->pwm_name);
+ if (IS_ERR(pwm)) {
+ dev_err(lmu_bl->chip->dev,
+ "Can not get PWM device: %s\n",
+ lmu_bl->pwm_name);
+ return;
+ }
+ lmu_bl->pwm = pwm;
+ }
+
+ period = lmu_bl->bl_pdata->pwm_period;
+ duty = br * period / max_br;
+
+ pwm_config(lmu_bl->pwm, duty, period);
+ if (duty)
+ pwm_enable(lmu_bl->pwm);
+ else
+ pwm_disable(lmu_bl->pwm);
+}
+
+static int ti_lmu_backlight_update_status(struct backlight_device *bl_dev)
+{
+ struct ti_lmu_bl *lmu_bl = bl_get_data(bl_dev);
+ const struct ti_lmu_bl_ops *ops = lmu_bl->chip->ops;
+ int ret = 0;
+ int brt;
+
+ if (bl_dev->props.state & BL_CORE_SUSPENDED)
+ bl_dev->props.brightness = 0;
+
+ brt = bl_dev->props.brightness;
+ if (brt > 0)
+ ret = ti_lmu_backlight_enable(lmu_bl, 1);
+ else
+ ret = ti_lmu_backlight_enable(lmu_bl, 0);
+
+ if (ret)
+ return ret;
+
+ if (lmu_bl->mode == BL_PWM_BASED)
+ ti_lmu_backlight_pwm_ctrl(lmu_bl, brt,
+ bl_dev->props.max_brightness);
+
+ /*
+ * In some devices, additional handling is required after PWM control.
+ * So, just call device-specific brightness function.
+ */
+ if (ops->update_brightness)
+ return ops->update_brightness(lmu_bl, brt);
+
+ return 0;
+}
+
+static int ti_lmu_backlight_get_brightness(struct backlight_device *bl_dev)
+{
+ return bl_dev->props.brightness;
+}
+
+static const struct backlight_ops lmu_bl_common_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .update_status = ti_lmu_backlight_update_status,
+ .get_brightness = ti_lmu_backlight_get_brightness,
+};
+
+static int ti_lmu_backlight_parse_dt(struct device *dev, struct ti_lmu *lmu)
+{
+ struct ti_lmu_backlight_platform_data *pdata;
+ struct device_node *node = dev->of_node;
+ struct device_node *child;
+ int num_backlights;
+ int i = 0;
+ u8 imax_mA;
+
+ if (!node) {
+ dev_err(dev, "No device node exists\n");
+ return -ENODEV;
+ }
+
+ num_backlights = of_get_child_count(node);
+ if (num_backlights == 0) {
+ dev_err(dev, "No backlight strings\n");
+ return -EINVAL;
+ }
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata) * num_backlights, GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ for_each_child_of_node(node, child) {
+ of_property_read_string(child, "bl-name", &pdata[i].name);
+
+ /* Make backlight strings */
+ pdata[i].bl_string = 0;
+ if (of_find_property(child, "hvled1-used", NULL))
+ pdata[i].bl_string |= LMU_HVLED1;
+ if (of_find_property(child, "hvled2-used", NULL))
+ pdata[i].bl_string |= LMU_HVLED2;
+ if (of_find_property(child, "hvled3-used", NULL))
+ pdata[i].bl_string |= LMU_HVLED3;
+
+ of_property_read_u8(child, "max-current-milliamp", &imax_mA);
+ pdata[i].imax = ti_lmu_get_current_code(imax_mA);
+
+ of_property_read_u8(child, "initial-brightness",
+ &pdata[i].init_brightness);
+
+ /* Light effect */
+ of_property_read_u32(child, "ramp-up", &pdata[i].ramp_up_ms);
+ of_property_read_u32(child, "ramp-down",
+ &pdata[i].ramp_down_ms);
+
+ /* PWM mode */
+ of_property_read_u32(child, "pwm-period", &pdata[i].pwm_period);
+
+ i++;
+ }
+
+ lmu->pdata->bl_pdata = pdata;
+ lmu->pdata->num_backlights = num_backlights;
+
+ return 0;
+}
+
+struct ti_lmu_bl_chip *
+ti_lmu_backlight_init_device(struct device *dev, struct ti_lmu *lmu,
+ const struct ti_lmu_bl_ops *ops)
+{
+ struct ti_lmu_bl_chip *chip;
+ int ret;
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return ERR_PTR(-ENOMEM);
+
+ chip->dev = dev;
+ chip->lmu = lmu;
+ chip->ops = ops;
+
+ if (!lmu->pdata->bl_pdata) {
+ if (IS_ENABLED(CONFIG_OF))
+ ret = ti_lmu_backlight_parse_dt(dev, lmu);
+ else
+ return ERR_PTR(-ENODEV);
+
+ if (ret)
+ return ERR_PTR(ret);
+ }
+
+ if (chip->ops->init) {
+ ret = chip->ops->init(chip);
+ if (ret)
+ return ERR_PTR(ret);
+ }
+
+ return chip;
+}
+EXPORT_SYMBOL_GPL(ti_lmu_backlight_init_device);
+
+static void ti_lmu_backlight_set_ctrl_mode(struct ti_lmu_bl *lmu_bl)
+{
+ struct ti_lmu_backlight_platform_data *pdata = lmu_bl->bl_pdata;
+
+ if (pdata->pwm_period > 0)
+ lmu_bl->mode = BL_PWM_BASED;
+ else
+ lmu_bl->mode = BL_REGISTER_BASED;
+}
+
+static int ti_lmu_backlight_configure(struct ti_lmu_bl *lmu_bl)
+{
+ const struct ti_lmu_bl_ops *ops = lmu_bl->chip->ops;
+
+ if (ops->configure)
+ return ops->configure(lmu_bl);
+
+ return 0;
+}
+
+static int ti_lmu_backlight_add_device(struct ti_lmu_bl *lmu_bl)
+{
+ struct backlight_device *bl_dev;
+ struct backlight_properties props;
+ struct ti_lmu_backlight_platform_data *pdata = lmu_bl->bl_pdata;
+ int max_brightness = lmu_bl->chip->ops->max_brightness;
+ char name[20];
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.brightness = pdata ? pdata->init_brightness : 0;
+ props.max_brightness = max_brightness;
+
+ /* Backlight device name */
+ if (!pdata->name)
+ snprintf(name, sizeof(name), "%s:%d", DEFAULT_BL_NAME,
+ lmu_bl->bank_id);
+ else
+ snprintf(name, sizeof(name), "%s", pdata->name);
+
+ bl_dev = backlight_device_register(name, lmu_bl->chip->dev, lmu_bl,
+ &lmu_bl_common_ops, &props);
+ if (IS_ERR(bl_dev))
+ return PTR_ERR(bl_dev);
+
+ lmu_bl->bl_dev = bl_dev;
+
+ return 0;
+}
+
+struct ti_lmu_bl *
+ti_lmu_backlight_register(struct ti_lmu_bl_chip *chip,
+ struct ti_lmu_backlight_platform_data *pdata,
+ int num_backlights)
+{
+ struct ti_lmu_bl *lmu_bl, *each;
+ int i, ret;
+
+ lmu_bl = devm_kzalloc(chip->dev, sizeof(*lmu_bl) * num_backlights,
+ GFP_KERNEL);
+ if (!lmu_bl)
+ return ERR_PTR(-ENOMEM);
+
+ for (i = 0; i < num_backlights; i++) {
+ each = lmu_bl + i;
+ each->bank_id = i;
+ each->chip = chip;
+ each->bl_pdata = pdata + i;
+
+ ti_lmu_backlight_set_ctrl_mode(lmu_bl);
+
+ ret = ti_lmu_backlight_configure(each);
+ if (ret) {
+ dev_err(chip->dev, "Backlight config err: %d\n", ret);
+ goto err;
+ }
+
+ ret = ti_lmu_backlight_add_device(each);
+ if (ret) {
+ dev_err(chip->dev, "Backlight device err: %d\n", ret);
+ goto cleanup_backlights;
+ }
+
+ backlight_update_status(each->bl_dev);
+ }
+
+ chip->num_backlights = num_backlights;
+
+ return lmu_bl;
+
+cleanup_backlights:
+ while (--i >= 0) {
+ each = lmu_bl + i;
+ backlight_device_unregister(each->bl_dev);
+ }
+err:
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(ti_lmu_backlight_register);
+
+int ti_lmu_backlight_unregister(struct ti_lmu_bl *lmu_bl)
+{
+ struct ti_lmu_bl *each;
+ struct backlight_device *bl_dev;
+ int num_backlights = lmu_bl->chip->num_backlights;
+ int i;
+
+ for (i = 0; i < num_backlights; i++) {
+ each = lmu_bl + i;
+
+ bl_dev = each->bl_dev;
+ bl_dev->props.brightness = 0;
+ backlight_update_status(bl_dev);
+ backlight_device_unregister(bl_dev);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ti_lmu_backlight_unregister);
+
+/*
+ * This callback function is invoked in case the LMU effect driver is
+ * requested successfully.
+ */
+void ti_lmu_backlight_effect_callback(struct ti_lmu_effect *lmu_effect,
+ int req_id, void *data)
+{
+ struct ti_lmu_bl *lmu_bl = data;
+ unsigned int ramp_time;
+
+ if (req_id == BL_EFFECT_RAMPUP)
+ ramp_time = lmu_bl->bl_pdata->ramp_up_ms;
+ else if (req_id == BL_EFFECT_RAMPDN)
+ ramp_time = lmu_bl->bl_pdata->ramp_down_ms;
+ else
+ return;
+
+ ti_lmu_effect_set_ramp(lmu_effect, ramp_time);
+}
+EXPORT_SYMBOL_GPL(ti_lmu_backlight_effect_callback);
+
+MODULE_DESCRIPTION("TI LMU Backlight Common Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/backlight/ti-lmu-backlight.h b/drivers/video/backlight/ti-lmu-backlight.h
new file mode 100644
index 0000000..7dd2fa5d
--- /dev/null
+++ b/drivers/video/backlight/ti-lmu-backlight.h
@@ -0,0 +1,78 @@
+/*
+ * TI LMU Backlight Common Driver
+ *
+ * Copyright 2014 Texas Instruments
+ *
+ * Author: Milo Kim <milo.kim@xxxxxx>
+ *
+ * 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-effect.h>
+
+#define LMU_BL_DEFAULT_PWM_NAME "lmu-backlight"
+
+enum ti_lmu_bl_ctrl_mode {
+ BL_REGISTER_BASED,
+ BL_PWM_BASED,
+};
+
+struct ti_lmu_bl;
+struct ti_lmu_bl_chip;
+
+/*
+ * struct ti_lmu_bl_ops
+ * @init: Device specific initialization
+ * @configure: Device specific string configuration
+ * @update_brightness: Device specific brightness control
+ * @bl_enable: Device specific backlight enable/disable control
+ * @max_brightness: Max brightness value of backlight device
+ */
+struct ti_lmu_bl_ops {
+ int (*init)(struct ti_lmu_bl_chip *lmu_chip);
+ int (*configure)(struct ti_lmu_bl *lmu_bl);
+ int (*update_brightness)(struct ti_lmu_bl *lmu_bl, int brightness);
+ int (*bl_enable)(struct ti_lmu_bl *lmu_bl, int enable);
+ const int max_brightness;
+};
+
+/* One backlight chip can have multiple backlight strings */
+struct ti_lmu_bl_chip {
+ struct device *dev;
+ struct ti_lmu *lmu;
+ const struct ti_lmu_bl_ops *ops;
+ int num_backlights;
+};
+
+/* Backlight string structure */
+struct ti_lmu_bl {
+ int bank_id;
+ struct backlight_device *bl_dev;
+ struct ti_lmu_bl_chip *chip;
+ struct ti_lmu_backlight_platform_data *bl_pdata;
+ enum ti_lmu_bl_ctrl_mode mode;
+ struct pwm_device *pwm;
+ char pwm_name[20];
+};
+
+struct ti_lmu_bl_chip *
+ti_lmu_backlight_init_device(struct device *dev, struct ti_lmu *lmu,
+ const struct ti_lmu_bl_ops *ops);
+
+struct ti_lmu_bl *
+ti_lmu_backlight_register(struct ti_lmu_bl_chip *chip,
+ struct ti_lmu_backlight_platform_data *pdata,
+ int num_backlights);
+
+int ti_lmu_backlight_unregister(struct ti_lmu_bl *lmu_bl);
+
+void ti_lmu_backlight_effect_callback(struct ti_lmu_effect *lmu_effect,
+ int req_id, void *data);
+#endif
--
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/