[PATCH v3] backlight: add new LP8860 backlight driver

From: Daniel Jeong
Date: Thu Mar 13 2014 - 06:30:50 EST


This patch adds LP8860 backlight device driver.
LP8860 is a low EMI and High performance 4 channel LED Driver of TI.
This device driver provides the way to control brightness and current
of each channel and provides the way to change values in the eeprom.

Signed-off-by: Daniel Jeong <gshark.jeong@xxxxxxxxx>
---
To support dt structure, another patch file will be sent.
changes since v2
- removed some magic number.
- fixed the possibility to refer null when backlight is removed.

---
drivers/video/backlight/Kconfig | 7 +
drivers/video/backlight/Makefile | 1 +
drivers/video/backlight/lp8860_bl.c | 526 +++++++++++++++++++++++++++++++
include/linux/platform_data/lp8860_bl.h | 54 ++++
4 files changed, 588 insertions(+)
create mode 100644 drivers/video/backlight/lp8860_bl.c
create mode 100644 include/linux/platform_data/lp8860_bl.h

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 5a3eb2e..908048f 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -397,6 +397,13 @@ config BACKLIGHT_LP8788
help
This supports TI LP8788 backlight driver.

+config BACKLIGHT_LP8860
+ tristate "Backlight Driver for LP8860"
+ depends on BACKLIGHT_CLASS_DEVICE && I2C
+ select REGMAP_I2C
+ help
+ This supports TI LP8860 Backlight Driver
+
config BACKLIGHT_OT200
tristate "Backlight driver for ot200 visualisation device"
depends on BACKLIGHT_CLASS_DEVICE && CS5535_MFGPT && GPIO_CS5535
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index bb82002..cbc5ac3 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_BACKLIGHT_LM3639) += lm3639_bl.o
obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o
obj-$(CONFIG_BACKLIGHT_LP855X) += lp855x_bl.o
obj-$(CONFIG_BACKLIGHT_LP8788) += lp8788_bl.o
+obj-$(CONFIG_BACKLIGHT_LP8860) += lp8860_bl.o
obj-$(CONFIG_BACKLIGHT_LV5207LP) += lv5207lp.o
obj-$(CONFIG_BACKLIGHT_MAX8925) += max8925_bl.o
obj-$(CONFIG_BACKLIGHT_OMAP1) += omap1_bl.o
diff --git a/drivers/video/backlight/lp8860_bl.c b/drivers/video/backlight/lp8860_bl.c
new file mode 100644
index 0000000..e17e94f
--- /dev/null
+++ b/drivers/video/backlight/lp8860_bl.c
@@ -0,0 +1,526 @@
+/*
+* Simple driver for Texas Instruments lp8860 Backlight driver chip
+*
+* Copyright (C) 2014 Texas Instruments
+* Author: Daniel Jeong <gshark.jeong@xxxxxxxxx>
+* Ldd Mlp <ldd-mlp@xxxxxxxxxxx>
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License version 2 as
+* published by the Free Software Foundation.
+*
+*/
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/platform_data/lp8860_bl.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#define REG_CL0_BRT_H 0x00
+#define REG_CL0_BRT_L 0x01
+#define REG_CL0_I_H 0x02
+#define REG_CL0_I_L 0x03
+
+#define REG_CL1_BRT_H 0x04
+#define REG_CL1_BRT_L 0x05
+#define REG_CL1_I 0x06
+
+#define REG_CL2_BRT_H 0x07
+#define REG_CL2_BRT_L 0x08
+#define REG_CL2_I 0x09
+
+#define REG_CL3_BRT_H 0x0a
+#define REG_CL3_BRT_L 0x0b
+#define REG_CL3_I 0x0c
+
+#define REG_CONF 0x0d
+#define REG_STATUS 0x0e
+#define REG_ID 0x12
+
+#define REG_ROM_CTRL 0x19
+#define REG_ROM_UNLOCK 0x1a
+#define REG_ROM_START 0x60
+#define REG_ROM_END 0x78
+
+#define REG_EEPROM_START 0x60
+#define REG_EEPROM_END 0x78
+#define REG_MAX 0xFF
+
+struct lp8860_chip {
+ struct device *dev;
+ struct lp8860_platform_data *pdata;
+ struct backlight_device *bled[LP8860_LED_MAX];
+ struct regmap *regmap;
+};
+
+/* brightness control */
+static int lp8860_bled_update_status(struct backlight_device *bl,
+ enum lp8860_leds nsr)
+{
+ int ret = -EINVAL;
+ struct lp8860_chip *pchip = bl_get_data(bl);
+
+ if (pchip->pdata->mode)
+ return 0;
+
+ if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
+ bl->props.brightness = 0;
+
+ switch (nsr) {
+ case LP8860_LED0:
+ ret = regmap_write(pchip->regmap,
+ REG_CL0_BRT_H, bl->props.brightness >> 8);
+ ret |= regmap_write(pchip->regmap,
+ REG_CL0_BRT_L, bl->props.brightness & 0xff);
+ break;
+ case LP8860_LED1:
+ ret = regmap_write(pchip->regmap,
+ REG_CL1_BRT_H,
+ (bl->props.brightness >> 8) & 0x1f);
+ ret |= regmap_write(pchip->regmap,
+ REG_CL1_BRT_L, bl->props.brightness & 0xff);
+ break;
+ case LP8860_LED2:
+ ret = regmap_write(pchip->regmap,
+ REG_CL2_BRT_H,
+ (bl->props.brightness >> 8) & 0x1f);
+ ret |= regmap_write(pchip->regmap,
+ REG_CL2_BRT_L, bl->props.brightness & 0xff);
+ break;
+ case LP8860_LED3:
+ ret = regmap_write(pchip->regmap,
+ REG_CL3_BRT_H,
+ (bl->props.brightness >> 8) & 0x1f);
+ ret |= regmap_write(pchip->regmap,
+ REG_CL3_BRT_L, bl->props.brightness & 0xff);
+ break;
+ default:
+ BUG();
+ }
+ if (ret < 0)
+ dev_err(pchip->dev, "fail : i2c access to register.\n");
+ else
+ ret = bl->props.brightness;
+
+ return ret;
+}
+
+static int lp8860_bled_get_brightness(struct backlight_device *bl,
+ enum lp8860_leds nsr)
+{
+ struct lp8860_chip *pchip = bl_get_data(bl);
+ unsigned int rval_h, rval_l;
+ int ret = -EINVAL;
+
+ switch (nsr) {
+ case LP8860_LED0:
+ ret = regmap_read(pchip->regmap, REG_CL0_BRT_H, &rval_h);
+ ret |= regmap_read(pchip->regmap, REG_CL0_BRT_L, &rval_l);
+ break;
+ case LP8860_LED1:
+ ret = regmap_read(pchip->regmap, REG_CL1_BRT_H, &rval_h);
+ ret |= regmap_read(pchip->regmap, REG_CL1_BRT_L, &rval_l);
+ break;
+ case LP8860_LED2:
+ ret = regmap_read(pchip->regmap, REG_CL2_BRT_H, &rval_h);
+ ret |= regmap_read(pchip->regmap, REG_CL2_BRT_L, &rval_l);
+ break;
+ case LP8860_LED3:
+ ret = regmap_read(pchip->regmap, REG_CL3_BRT_H, &rval_h);
+ ret |= regmap_read(pchip->regmap, REG_CL3_BRT_L, &rval_l);
+ break;
+ default:
+ BUG();
+ }
+ if (ret < 0) {
+ dev_err(pchip->dev, "fail : i2c access to register.\n");
+ return ret;
+ }
+ bl->props.brightness = (rval_h << 8) | rval_l;
+ return bl->props.brightness;
+}
+
+static int lp8860_update_status_bled0(struct backlight_device *bl)
+{
+ return lp8860_bled_update_status(bl, LP8860_LED0);
+}
+
+static int lp8860_get_brightness_bled0(struct backlight_device *bl)
+{
+ return lp8860_bled_get_brightness(bl, LP8860_LED0);
+}
+
+static int lp8860_update_status_bled1(struct backlight_device *bl)
+{
+ return lp8860_bled_update_status(bl, LP8860_LED1);
+}
+
+static int lp8860_get_brightness_bled1(struct backlight_device *bl)
+{
+ return lp8860_bled_get_brightness(bl, LP8860_LED1);
+}
+
+static int lp8860_update_status_bled2(struct backlight_device *bl)
+{
+ return lp8860_bled_update_status(bl, LP8860_LED2);
+}
+
+static int lp8860_get_brightness_bled2(struct backlight_device *bl)
+{
+ return lp8860_bled_get_brightness(bl, LP8860_LED2);
+}
+
+static int lp8860_update_status_bled3(struct backlight_device *bl)
+{
+ return lp8860_bled_update_status(bl, LP8860_LED3);
+}
+
+static int lp8860_get_brightness_bled3(struct backlight_device *bl)
+{
+ return lp8860_bled_get_brightness(bl, LP8860_LED3);
+}
+
+#define lp8860_bled_ops(_id)\
+{\
+ .options = BL_CORE_SUSPENDRESUME,\
+ .update_status = lp8860_update_status_bled##_id,\
+ .get_brightness = lp8860_get_brightness_bled##_id,\
+}
+
+static const struct backlight_ops lp8860_bled_ops[LP8860_LED_MAX] = {
+ [LP8860_LED0] = lp8860_bled_ops(0),
+ [LP8860_LED1] = lp8860_bled_ops(1),
+ [LP8860_LED2] = lp8860_bled_ops(2),
+ [LP8860_LED3] = lp8860_bled_ops(3),
+};
+
+/* current control */
+static int lp8860_set_current(struct device *dev,
+ const char *buf, enum lp8860_leds nsr)
+{
+ struct lp8860_chip *pchip = dev_get_drvdata(dev);
+ unsigned int ival;
+ ssize_t ret;
+
+ ret = kstrtouint(buf, 10, &ival);
+ if (ret)
+ return ret;
+
+ switch (nsr) {
+ case LP8860_LED0:
+ ival = min_t(unsigned int, ival, LP8860_LED0_BR_MAX);
+ ret = regmap_write(pchip->regmap, REG_CL0_I_H, ival >> 8);
+ ret |= regmap_write(pchip->regmap, REG_CL0_I_L, ival & 0xff);
+ break;
+ case LP8860_LED1:
+ ival = min_t(unsigned int, ival, LP8860_LED1_BR_MAX);
+ ret = regmap_write(pchip->regmap, REG_CL1_I, ival & 0xff);
+ break;
+ case LP8860_LED2:
+ ival = min_t(unsigned int, ival, LP8860_LED2_BR_MAX);
+ ret = regmap_write(pchip->regmap, REG_CL2_I, ival & 0xff);
+ break;
+ case LP8860_LED3:
+ ival = min_t(unsigned int, ival, LP8860_LED3_BR_MAX);
+ ret = regmap_write(pchip->regmap, REG_CL3_I, ival & 0xff);
+ break;
+ default:
+ BUG();
+ }
+ if (ret < 0)
+ dev_err(pchip->dev, "fail : i2c access error.\n");
+
+ return ret;
+}
+
+static ssize_t lp8860_current_store_bled0(struct device *dev,
+ struct device_attribute *devAttr,
+ const char *buf, size_t size)
+{
+ int ret;
+
+ ret = lp8860_set_current(dev, buf, LP8860_LED0);
+ if (ret < 0)
+ return ret;
+ return size;
+}
+
+static ssize_t lp8860_current_store_bled1(struct device *dev,
+ struct device_attribute *devAttr,
+ const char *buf, size_t size)
+{
+ int ret;
+
+ ret = lp8860_set_current(dev, buf, LP8860_LED1);
+ if (ret < 0)
+ return ret;
+ return size;
+}
+
+static ssize_t lp8860_current_store_bled2(struct device *dev,
+ struct device_attribute *devAttr,
+ const char *buf, size_t size)
+{
+ int ret;
+
+ ret = lp8860_set_current(dev, buf, LP8860_LED2);
+ if (ret < 0)
+ return ret;
+ return size;
+}
+
+static ssize_t lp8860_current_store_bled3(struct device *dev,
+ struct device_attribute *devAttr,
+ const char *buf, size_t size)
+{
+ int ret;
+
+ ret = lp8860_set_current(dev, buf, LP8860_LED3);
+ if (ret < 0)
+ return ret;
+ return size;
+}
+
+#define lp8860_attr(_name, _show, _store)\
+{\
+ .attr = {\
+ .name = _name,\
+ .mode = S_IWUSR,\
+ },\
+ .show = _show,\
+ .store = _store,\
+}
+
+static struct device_attribute lp8860_dev_attr[LP8860_LED_MAX] = {
+ [LP8860_LED0] = lp8860_attr("current", NULL,
+ lp8860_current_store_bled0),
+ [LP8860_LED1] = lp8860_attr("current", NULL,
+ lp8860_current_store_bled1),
+ [LP8860_LED2] = lp8860_attr("current", NULL,
+ lp8860_current_store_bled2),
+ [LP8860_LED3] = lp8860_attr("current", NULL,
+ lp8860_current_store_bled3),
+};
+
+/*
+ * eeprom write and readback to check.
+ * eeprom register range is from 60h to 78h
+ * buffer value to write data [reg] [data]
+ * e.g) to change the register 0x60 value to 0xff
+ * buffer value should be 60 ff
+ */
+static ssize_t lp8860_eeprom_store(struct device *dev,
+ struct device_attribute *devAttr,
+ const char *buf, size_t size)
+{
+ struct lp8860_chip *pchip = dev_get_drvdata(dev);
+ unsigned int reg, data, rval;
+ char *tok;
+ int ret;
+
+ /* register no. */
+ tok = strsep((char **)&buf, " ,\n");
+ if (tok == NULL)
+ goto err_input;
+ ret = kstrtouint(tok, 16, &reg);
+ if (ret)
+ goto err_input;
+
+ /* register value */
+ tok = strsep((char **)&buf, " ,\n");
+ if (tok == NULL)
+ goto err_input;
+ ret = kstrtouint(tok, 16, &data);
+ if (ret)
+ goto err_input;
+ /*
+ * EEPROM Programming sequence
+ * (program data permanently from registers to NVM)
+ * 1. Unlock EEPROM by writing
+ * the unlock codes to register 1Ah(08, BA, EF)
+ * 2. Write data to EEPROM registers (address 60h...78h)
+ * 3. Write EE_PROG to 1 in address 19h. (02h to address 19h)
+ * 4. Wait 100ms for chip interanl dealy to access the eeprom
+ * 5. Write EE_PROG to 0 in address 19h. (00h to address 19h)
+ */
+ if (reg < REG_EEPROM_START || reg > REG_EEPROM_END || data > 0xff)
+ goto err_input;
+ ret = regmap_write(pchip->regmap, REG_ROM_UNLOCK, 0x08);
+ ret |= regmap_write(pchip->regmap, REG_ROM_UNLOCK, 0xba);
+ ret |= regmap_write(pchip->regmap, REG_ROM_UNLOCK, 0xef);
+ ret |= regmap_write(pchip->regmap, reg, data);
+ ret |= regmap_write(pchip->regmap, REG_ROM_CTRL, 0x02);
+ msleep(100);
+ ret |= regmap_write(pchip->regmap, REG_ROM_CTRL, 0x00);
+ if (ret < 0)
+ goto err_i2c;
+
+ /* read back */
+ ret = regmap_write(pchip->regmap, REG_ROM_UNLOCK, 0x08);
+ ret |= regmap_write(pchip->regmap, REG_ROM_UNLOCK, 0xba);
+ ret |= regmap_write(pchip->regmap, REG_ROM_UNLOCK, 0xef);
+ ret |= regmap_write(pchip->regmap, REG_ROM_CTRL, 0x01);
+ msleep(100);
+ ret |= regmap_write(pchip->regmap, REG_ROM_CTRL, 0x00);
+ ret |= regmap_read(pchip->regmap, reg, &rval);
+ if (ret < 0)
+ goto err_i2c;
+ if (rval != data)
+ dev_err(pchip->dev, "fail : eeprom did not change.\n");
+
+ return size;
+
+err_i2c:
+ dev_err(pchip->dev, "fail : i2c access error.\n");
+ return ret;
+
+err_input:
+ dev_err(pchip->dev, "fail : input fail.\n");
+ return -EINVAL;
+}
+
+static DEVICE_ATTR(eeprom, S_IWUSR, NULL, lp8860_eeprom_store);
+
+/* backlight register and remove */
+static char *lp8860_bled_name[LP8860_LED_MAX] = {
+ [LP8860_LED0] = "bled0",
+ [LP8860_LED1] = "bled1",
+ [LP8860_LED2] = "bled2",
+ [LP8860_LED3] = "bled3",
+};
+
+static int lp8860_backlight_remove(struct lp8860_chip *pchip)
+{
+ int icnt;
+
+ for (icnt = LP8860_LED0; icnt < LP8860_LED_MAX; icnt++) {
+ if (pchip->bled[icnt])
+ device_remove_file(&(pchip->bled[icnt]->dev),
+ &lp8860_dev_attr[icnt]);
+ }
+ return 0;
+}
+
+static int lp8860_backlight_registers(struct lp8860_chip *pchip)
+{
+ struct backlight_properties props;
+ struct lp8860_platform_data *pdata = pchip->pdata;
+ int icnt, ret;
+
+ props.type = BACKLIGHT_RAW;
+ for (icnt = LP8860_LED0; icnt < LP8860_LED_MAX; icnt++) {
+ props.max_brightness = pdata->max_brt[icnt];
+ pchip->bled[icnt] =
+ devm_backlight_device_register(pchip->dev,
+ lp8860_bled_name[icnt],
+ pchip->dev, pchip,
+ &lp8860_bled_ops[icnt],
+ &props);
+ if (IS_ERR(pchip->bled[icnt])) {
+ dev_err(pchip->dev, "fail : backlight register.\n");
+ ret = PTR_ERR(pchip->bled[icnt]);
+ goto err_out;
+ }
+ /* to control current */
+ ret = device_create_file(&(pchip->bled[icnt]->dev),
+ &lp8860_dev_attr[icnt]);
+ if (ret < 0) {
+ dev_err(pchip->dev, "fail : to add sysfs entries.\n");
+ goto err_out;
+ }
+ }
+ /* to access eeprom */
+ ret = device_create_file(&(pchip->bled[LP8860_LED0]->dev),
+ &dev_attr_eeprom);
+ if (ret < 0) {
+ dev_err(pchip->dev, "fail : to add sysfs entries.\n");
+ goto err_out;
+ }
+ return 0;
+
+err_out:
+ lp8860_backlight_remove(pchip);
+ return ret;
+}
+
+static const struct regmap_config lp8860_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = REG_MAX,
+};
+
+static int lp8860_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct lp8860_chip *pchip;
+ struct lp8860_platform_data *pdata = dev_get_platdata(&client->dev);
+ int ret, icnt;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "fail : i2c functionality check.\n");
+ return -EOPNOTSUPP;
+ }
+
+ pchip = devm_kzalloc(&client->dev,
+ sizeof(struct lp8860_chip), GFP_KERNEL);
+ if (!pchip)
+ return -ENOMEM;
+ pchip->dev = &client->dev;
+
+ pchip->regmap = devm_regmap_init_i2c(client, &lp8860_regmap);
+ if (IS_ERR(pchip->regmap)) {
+ ret = PTR_ERR(pchip->regmap);
+ dev_err(pchip->dev, "fail : allocate i2c register map.\n");
+ return ret;
+ }
+
+ if (pdata == NULL) {
+ pdata = devm_kzalloc(pchip->dev,
+ sizeof(struct lp8860_platform_data),
+ GFP_KERNEL);
+ if (pdata == NULL)
+ return -ENOMEM;
+ pdata->max_brt[LP8860_LED0] = LP8860_LED0_BR_MAX;
+ for (icnt = LP8860_LED1; icnt < LP8860_LED_MAX; icnt++)
+ pdata->max_brt[icnt] = LP8860_LED1_BR_MAX;
+ }
+ pchip->pdata = pdata;
+
+ i2c_set_clientdata(client, pchip);
+ ret = lp8860_backlight_registers(pchip);
+ return ret;
+}
+
+static int lp8860_remove(struct i2c_client *client)
+{
+ struct lp8860_chip *pchip = i2c_get_clientdata(client);
+
+ device_remove_file(&(pchip->bled[LP8860_LED0]->dev), &dev_attr_eeprom);
+ return lp8860_backlight_remove(i2c_get_clientdata(client));
+}
+
+static const struct i2c_device_id lp8860_id[] = {
+ {LP8860_NAME, 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, lp8860_id);
+static struct i2c_driver lp8860_i2c_driver = {
+ .driver = {
+ .name = LP8860_NAME,
+ },
+ .probe = lp8860_probe,
+ .remove = lp8860_remove,
+ .id_table = lp8860_id,
+};
+
+module_i2c_driver(lp8860_i2c_driver);
+
+MODULE_DESCRIPTION("Texas Instruments LP8860 Backlight Driver");
+MODULE_AUTHOR("Daniel Jeong <gshark.jeong@xxxxxxxxx>");
+MODULE_AUTHOR("Ldd Mlp <ldd-mlp@xxxxxxxxxxx>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/platform_data/lp8860_bl.h b/include/linux/platform_data/lp8860_bl.h
new file mode 100644
index 0000000..9edd7cf
--- /dev/null
+++ b/include/linux/platform_data/lp8860_bl.h
@@ -0,0 +1,54 @@
+/*
+ * Simple driver for Texas Instruments LP8860 Backlight driver chip
+ * Copyright (C) 2014 Texas Instruments
+ *
+ * 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 __LP8860_H
+#define __LP8860_H
+
+#define LP8860_NAME "lp8860"
+#define LP8860_ADDR 0x2d
+
+#define LP8860_LED0_BR_MAX 65535
+#define LP8860_LED1_BR_MAX 8191
+#define LP8860_LED2_BR_MAX 8191
+#define LP8860_LED3_BR_MAX 8191
+
+#define LP8860_LED0_I_MAX 4095
+#define LP8860_LED1_I_MAX 255
+#define LP8860_LED2_I_MAX 255
+#define LP8860_LED3_I_MAX 255
+
+enum lp8860_leds {
+ LP8860_LED0 = 0,
+ LP8860_LED1,
+ LP8860_LED2,
+ LP8860_LED3,
+ LP8860_LED_MAX
+};
+
+enum lp8860_ctrl_mode {
+ LP8860_CTRL_I2C = 0,
+ LP8860_CTRL_I2C_PWM,
+};
+
+/* struct lp8860 platform data
+ * @mode : control mode
+ * @max_brt : maximum brightness.
+ * LED0 0 ~ 65535
+ * LED1 0 ~ 8191
+ * LED2 0 ~ 8191
+ * LED3 0 ~ 8191
+ */
+struct lp8860_platform_data {
+
+ enum lp8860_ctrl_mode mode;
+ int max_brt[LP8860_LED_MAX];
+};
+
+#endif /* __LP8860_H */
--
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/