backlight : add new backlight driver LM36923
authorDaniel Jeong <gshark.jeong@gmail.com>
Wed, 11 Feb 2015 04:09:17 +0000 (13:09 +0900)
committerDaniel Jeong <gshark.jeong@gmail.com>
Wed, 11 Feb 2015 04:09:17 +0000 (13:09 +0900)
Signed-off-by: Daniel Jeong <gshark.jeong@gmail.com>
drivers/video/backlight/Kconfig
drivers/video/backlight/Makefile
drivers/video/backlight/lm36923_bl.c [new file with mode: 0644]
include/linux/platform_data/ti-scbl.h [new file with mode: 0644]

index cc95545810efa0a51e06f63d65dd83e6260e6597..4341377fb2614f6cac62d909c16a56cc25d5aab1 100644 (file)
@@ -391,6 +391,13 @@ config BACKLIGHT_LM3639
        help
          This supports TI LM3639 Backlight + 1.5A Flash LED Driver
 
+config BACKLIGHT_LM36923
+       tristate "Backlight Driver for LM36923"
+       depends on BACKLIGHT_CLASS_DEVICE && I2C
+       select REGMAP_I2C
+       help
+         This supports TI LM36923 Backlight Driver
+
 config BACKLIGHT_LP855X
        tristate "Backlight driver for TI LP855X"
        depends on BACKLIGHT_CLASS_DEVICE && I2C
index f04eceb2a02391a88fbdd534eae554f16f1f03e4..ffd0717bf30dd99ac3266afec30488be444c2c74 100644 (file)
@@ -40,6 +40,7 @@ obj-$(CONFIG_BACKLIGHT_LM3533)                += lm3533_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3585)         += lm3585_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3630A)                += lm3630a_bl.o
 obj-$(CONFIG_BACKLIGHT_LM3639)         += lm3639_bl.o
+obj-$(CONFIG_BACKLIGHT_LM36923)                += lm36923_bl.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/lm36923_bl.c b/drivers/video/backlight/lm36923_bl.c
new file mode 100644 (file)
index 0000000..83ce311
--- /dev/null
@@ -0,0 +1,452 @@
+/*
+ * Simple driver for multi-string single control backlight driver chips of
+ * Texas Instruments.
+ *
+ * Copyright (C) 2015 Texas Instruments
+ * Author: Daniel Jeong  <gshark.jeong@gmail.com>
+ *         Ldd Mlp <ldd-mlp@list.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 <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/platform_data/ti-scbl.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#define scbl_attr(_name, _show, _store)\
+{\
+       .attr = {\
+               .name = _name,\
+               .mode = S_IWUSR | S_IRUSR,\
+       },\
+       .show = _show,\
+       .store = _store,\
+}
+
+struct scbl_reg_ctrl {
+       unsigned int reg;
+       unsigned int mask;
+       unsigned int shift;
+};
+
+/*
+ *brightness control 
+ *@min : minimum brightness
+ *@max : maximum brightness
+ *@msb_shift : msb bit shfit
+ *@lsb_mask  : lsb bit mask
+ *@msb : register for the upper 8bits of the brightness code
+ *@lsb : register for the lower 8bits of the brightness code
+ */
+struct scbl_brt_ctrl {
+       unsigned int min;
+       unsigned int max;
+       unsigned int msb_shift;
+       unsigned int lsb_mask;
+       struct scbl_reg_ctrl msb;
+       struct scbl_reg_ctrl lsb;
+};
+
+/*
+ *fault data
+ *@reg : fault register
+ *@mask: mask bit of fault
+ *@bl_fault : backlight fault mapped the bit
+ */
+struct scbl_fault {
+       unsigned int reg;
+       unsigned int mask;
+       unsigned int bl_fault;
+};
+
+/*
+ *single control backlight data
+ *@name    : chip name
+ *@num_led : number of led string
+ *@en_chip : chip enable register
+ *@en_led  : led string enable register
+ *@brt     : brightness register
+ *@fault   : fault register
+ *@boot    : function that should be called before chip enable
+ *@init    : function that should be called after chip enable
+ *@reset   : reset function
+ *@num_devnode   : number of device node to be created
+ *@scbl_dev_attr : device node attribute
+ */
+struct scbl_data {
+       char *name;
+
+       int num_led;
+       struct scbl_reg_ctrl en_chip;
+       struct scbl_reg_ctrl en_led[SCBL_LED_MAX];
+
+       struct scbl_brt_ctrl brt;
+       struct scbl_fault fault[SCBL_FAULT_MAX];
+
+       int (*boot)(struct regmap* regmap);
+       int (*init)(struct regmap* regmap);
+       int (*reset)(struct regmap* regmap);
+       
+       int num_devnode;
+       struct device_attribute *scbl_dev_attr;
+};
+
+/*
+ *chip data
+ *@dev   : device
+ *@regmap: register map
+ *@bled  : backlight device
+ *@data  : single control backlight data
+ */
+struct scbl_chip {
+       struct device *dev;
+       struct regmap *regmap;
+
+       struct backlight_device *bled;
+       const struct scbl_data *data; 
+};
+
+/* chip reset */
+static ssize_t scbl_reset_store(struct device *dev,
+                                 struct device_attribute *attr,
+                                 const char *buf, size_t size)
+{
+       struct scbl_chip *pchip = dev_get_drvdata(dev);
+       const struct scbl_data *data = pchip->data; 
+       u8 input;
+       int ret;
+
+       if (kstrtou8(buf, 0, &input))
+               return -EINVAL;
+
+       if (input == 1) {
+               ret = data->reset(pchip->regmap);
+               if (ret<0)
+                       return ret;
+       }
+       return size;
+}
+
+/* fault status */
+static ssize_t scbl_fault_show(struct device *dev,
+                                struct device_attribute *attr, char *buf)
+{
+       struct scbl_chip *pchip = dev_get_drvdata(dev);
+       const struct scbl_data *data = pchip->data; 
+       unsigned int reg_val, reg = 0xfff;
+       s32 fault = 0x0000;
+       int ret, icnt;
+
+       for(icnt = 0; icnt < SCBL_FAULT_MAX; icnt++) {
+               if(data->fault[icnt].mask == 0x00)
+                       continue;
+               if(data->fault[icnt].reg != reg) {
+                       reg = data->fault[icnt].reg;
+                       ret = regmap_read(pchip->regmap, reg, &reg_val);
+                       if (ret < 0) {
+                               dev_err(pchip->dev, 
+                                               "fail : i2c access to register.\n");
+                               return sprintf(buf, "%d\n", ret);
+                       }
+               }
+               if (reg_val & data->fault[icnt].mask)
+                       fault |= data->fault[icnt].bl_fault;            
+       }
+       return sprintf(buf, "%d\n", fault);
+}
+
+/* chip enable control */
+static ssize_t scbl_enable_show(struct device *dev,
+                                 struct device_attribute *attr, char *buf)
+{
+       struct scbl_chip *pchip = dev_get_drvdata(dev);
+       const struct scbl_data *data = pchip->data; 
+       unsigned int reg_val;
+       int ret;
+
+       ret = regmap_read(pchip->regmap, data->en_chip.reg, &reg_val);
+       if (ret <0) {           
+               return sprintf(buf, "%d\n", -EINVAL);
+       }
+       return sprintf(buf, "%d\n",
+                       (reg_val & data->en_chip.mask) >> data->en_chip.shift);
+}
+
+static ssize_t scbl_enable_store(struct device *dev,
+                                  struct device_attribute *attr,
+                                  const char *buf, size_t size)
+{
+       struct scbl_chip *pchip = dev_get_drvdata(dev);
+       const struct scbl_data *data = pchip->data; 
+       int ret;
+       u8 input;
+
+       if (kstrtou8(buf, 0, &input))
+               return -EINVAL;
+
+       if (input == 0)
+               ret = regmap_update_bits(pchip->regmap,
+                                       data->en_chip.reg, data->en_chip.mask, 0x00);
+       else
+               ret = regmap_update_bits(pchip->regmap, data->en_chip.reg,
+                                       data->en_chip.mask, 0x01 << data->en_chip.shift);
+       if(ret <0)
+               return ret;
+
+       return size;
+}
+
+/* brightness control */
+static int scbl_update_status(struct backlight_device *bl)
+{
+       struct scbl_chip *pchip = bl_get_data(bl);
+       const struct scbl_data *data = pchip->data; 
+       int ret = -EINVAL;
+       int msb, lsb;
+
+       if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
+               bl->props.brightness = 0;
+       
+       /* lsb write first */
+       if(data->brt.lsb_mask != 0x00) {
+               msb = bl->props.brightness >> data->brt.msb_shift ;
+               lsb = bl->props.brightness & data->brt.lsb_mask ;
+
+               ret = regmap_update_bits(pchip->regmap, data->brt.lsb.reg,
+                                       data->brt.lsb.mask, lsb << data->brt.lsb.shift);
+       } else {
+               msb = bl->props.brightness;
+       }
+
+       /* msb write */
+       ret = regmap_update_bits(pchip->regmap, data->brt.msb.reg,
+                               data->brt.msb.mask, msb << data->brt.msb.shift);
+
+       if (ret < 0)
+               dev_err(pchip->dev, "fail : i2c access to register.\n");
+       else
+               ret = bl->props.brightness;
+
+       return ret;
+}
+
+static int scbl_get_brightness(struct backlight_device *bl)
+{
+       return bl->props.brightness;
+}
+
+static const struct backlight_ops scbl_bled_ops = {
+       .options = BL_CORE_SUSPENDRESUME,
+       .update_status = scbl_update_status,
+       .get_brightness = scbl_get_brightness,
+};
+
+/*
+ * LP36923 backlight chip data
+ *  lp36923 is an ultra-compact, highly efficient, three string white-LED
+ * driver.
+ */
+static struct device_attribute lm36923_dev_attr[] = {
+       scbl_attr("fault", scbl_fault_show, NULL),
+       scbl_attr("reset", NULL, scbl_reset_store),
+       scbl_attr("enable", scbl_enable_show, scbl_enable_store),
+};
+
+static int scbl_lm36923_reset(struct regmap *regmap)
+{
+       return regmap_update_bits(regmap, 0x01, 0x01, 0x01);
+}
+
+static int scbl_lm36923_boot(struct regmap *regmap)
+{
+       int rval;
+
+       rval = regmap_update_bits(regmap, 0x10, 0x0f, 0x00);
+       /* 
+        * For glitch free operation, the following data should
+        * only be written while chine enable bit is 0
+        */
+       /* mapping mode / brt mode / ramp / ramp rate / bl_adj polarity */
+       rval |= regmap_write(regmap, 0x11, 0x81);
+       /* pwm */
+       rval |= regmap_write(regmap, 0x12, 0x73);
+       /* auto frequency high threshold */
+       rval |= regmap_write(regmap, 0x15, 0x00);
+       /* auto frequency low threshold */
+       rval |= regmap_write(regmap, 0x16, 0x00);
+       /* backlight adjust threshold */
+       rval |= regmap_write(regmap, 0x17, 0x00);
+       /* chip and leds enable */
+       rval |= regmap_update_bits(regmap, 0x10, 0x0f, 0x0f);
+       return rval;
+}
+
+static const struct scbl_data bl_lm36923 = {
+       .name = LM36923_NAME,
+       .num_led = 3,
+       .en_chip = {.reg = 0x10, .mask = 0x01, .shift = 0},
+       .en_led = {
+               [0] = {.reg = 0x10, .mask = 0x02, .shift = 1},
+               [1] = {.reg = 0x10, .mask = 0x04, .shift = 2},
+               [2] = {.reg = 0x10, .mask = 0x08, .shift = 3},
+       },
+       .brt = {
+               .min = 0x00, .max = 0x7ff,
+               .msb_shift = 3, .lsb_mask = 0x07,
+               .msb = {.reg = 0x19, .mask = 0xff, .shift = 0},
+               .lsb = {.reg = 0x18, .mask = 0x07, .shift = 0},
+       },
+       .fault = {
+               [0] = {
+                       .reg = 0x1f, .mask = 0x01, 
+                       .bl_fault = SCBL_FAULT_OVER_VOLTAGE
+               },      
+               [1] = {
+                       .reg = 0x1f, .mask = 0x02,
+                       .bl_fault = SCBL_FAULT_OVER_CURRENT
+               },      
+               [2] = {
+                       .reg = 0x1f, .mask = 0x04,
+                       .bl_fault = SCBL_FAULT_THERMAL_SHDN
+               },      
+               [3] = {
+                       .reg = 0x1f, .mask = 0x08,
+                       .bl_fault = SCBL_FAULT_LED_SHORT
+               },      
+               [4] = {
+                       .reg = 0x1f, .mask = 0x10,
+                       .bl_fault = SCBL_FAULT_LED_OPEN
+               },      
+       },
+       .boot = scbl_lm36923_boot,
+       .init = NULL,
+       .reset = scbl_lm36923_reset,
+       .num_devnode = 3,
+       .scbl_dev_attr = lm36923_dev_attr,
+};
+
+static const struct regmap_config scbl_regmap = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .max_register = SCBL_REG_MAX,
+};
+
+static int scbl_probe(struct i2c_client *client,
+                       const struct i2c_device_id *devid)
+{
+       struct scbl_chip *pchip;
+       struct backlight_properties props;
+       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 scbl_chip), GFP_KERNEL);
+       if (!pchip)
+               return -ENOMEM;
+       pchip->dev = &client->dev;
+
+       pchip->regmap = devm_regmap_init_i2c(client, &scbl_regmap);
+       if (IS_ERR(pchip->regmap)) {
+               ret = PTR_ERR(pchip->regmap);
+               dev_err(pchip->dev, "fail : allocate i2c register map.\n");
+               return ret;
+       }
+
+       pchip->data = (const struct scbl_data *)devid->driver_data;
+       i2c_set_clientdata(client, pchip);
+
+       props.brightness = 0;
+       props.type = BACKLIGHT_RAW;
+       props.max_brightness = pchip->data->brt.max;
+       pchip->bled = devm_backlight_device_register(pchip->dev,
+                                                    pchip->data->name,
+                                                        pchip->dev,
+                                                    pchip, &scbl_bled_ops,
+                                                    &props);
+       if (IS_ERR(pchip->bled)) {
+               dev_err(pchip->dev, "fail : backlight register.\n");
+               ret = PTR_ERR(pchip->bled);
+               return ret;
+       }
+
+       for (icnt = 0; icnt < pchip->data->num_devnode; icnt++) {
+               ret =
+                   device_create_file(&(pchip->bled->dev),
+                                             &(pchip->data->scbl_dev_attr)[icnt]);
+               if (ret < 0) {
+                       dev_err(pchip->dev, "fail : node create\n");
+                       goto err_out;
+               }
+       }
+       /* chip boot sequence before chip enable */
+       if(pchip->data->boot != NULL) {
+               ret = pchip->data->boot(pchip->regmap);
+               if (ret < 0)
+                       goto err_out;
+       }
+       /* chip enable */
+       ret = regmap_update_bits(pchip->regmap, pchip->data->en_chip.reg,
+                                                                       pchip->data->en_chip.mask,
+                                                                       0x01<<pchip->data->en_chip.shift);
+
+       /* chip init sequence after chip enable */
+       if(pchip->data->init != NULL) {
+               ret = pchip->data->boot(pchip->regmap);
+               if (ret < 0)
+                       goto err_out;
+       }
+       dev_info(pchip->dev, "[%s] initialized.\n",     pchip->data->name);
+       return 0;
+
+err_out:
+       while (--icnt >= 0)
+               device_remove_file(&(pchip->bled->dev),
+                                                     &(pchip->data->scbl_dev_attr)[icnt]);
+       dev_info(pchip->dev, "[%s] init failed.\n",     pchip->data->name);
+       return ret;
+}
+
+static int scbl_remove(struct i2c_client *client)
+{
+       struct scbl_chip *pchip = i2c_get_clientdata(client);
+       int icnt;
+
+       for (icnt = 0; icnt < pchip->data->num_devnode; icnt++)
+               device_remove_file(&(pchip->bled->dev),
+                                                     &(pchip->data->scbl_dev_attr)[icnt]);
+       return 0;
+}
+
+static const struct i2c_device_id scbl_id[] = {
+       {LM36923_NAME, (unsigned long)&bl_lm36923},
+       {}
+};
+
+MODULE_DEVICE_TABLE(i2c, scbl_id);
+static struct i2c_driver scbl_i2c_driver = {
+       .driver = {
+                  .name = TI_SINGLE_CONTROL_BL_NAME,
+                  },
+       .probe = scbl_probe,
+       .remove = scbl_remove,
+       .id_table = scbl_id,
+};
+
+module_i2c_driver(scbl_i2c_driver);
+
+MODULE_DESCRIPTION("Texas Instruments Single Control Backlight Driver");
+MODULE_AUTHOR("Daniel Jeong <gshark.jeong@gmail.com>");
+MODULE_AUTHOR("Ldd Mlp <ldd-mlp@list.ti.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/platform_data/ti-scbl.h b/include/linux/platform_data/ti-scbl.h
new file mode 100644 (file)
index 0000000..7777cea
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * include/media/ti-scbl.h
+ *
+ * Copyright (C) 2015 Texas Instruments
+ *
+ * Contact: Daniel Jeong <gshark.jeong@gmail.com>
+ *                     Ldd-Mlp <ldd-mlp@list.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_SCBL_H
+#define __TI_SCBL_H
+
+#define LM36923_NAME "lm36923"
+#define LM36923_I2C_ADDR       (0x36)
+
+#define TI_SINGLE_CONTROL_BL_NAME      "ti_sc_backlight"
+
+#define SCBL_FAULT_OVER_VOLTAGE 0x01
+#define SCBL_FAULT_OVER_CURRENT 0x02
+#define SCBL_FAULT_THERMAL_SHDN 0x04
+#define SCBL_FAULT_LED_SHORT    0x08
+#define SCBL_FAULT_LED_OPEN     0x10
+#define SCBL_FAULT_MAX 5
+
+#define SCBL_LED_MAX 8
+#define SCBL_REG_MAX 0xff
+
+#endif /* __TI_SCBL_H */