Add LM3646 driver using LED subsystem structure - V0 v0.4.LM3646.LED.V0
authorDaniel jeong <daniel.jeong@ti.com>
Mon, 26 May 2014 00:24:24 +0000 (09:24 +0900)
committerDaniel jeong <daniel.jeong@ti.com>
Mon, 26 May 2014 00:24:24 +0000 (09:24 +0900)
Signed-off-by: Daniel jeong <daniel.jeong@ti.com>
drivers/leds/Kconfig
drivers/leds/Makefile
drivers/leds/leds-lm3646.c [new file with mode: 0644]
include/linux/platform_data/leds-lm3646.h [new file with mode: 0644]

index 72156c123033342f566497155154d31eeef79baf..8dc4932c45f800dcf755272cecc7302b17d9b79f 100644 (file)
@@ -472,6 +472,14 @@ config LEDS_LM355x
          This option enables support for LEDs connected to LM355x.
          LM355x includes Torch, Flash and Indicator functions.
 
+config LEDS_LM3646
+       tristate "LED support for LM3646 Chip"
+       depends on LEDS_CLASS && I2C
+       select REGMAP_I2C
+       help
+         This option enables support for LEDs connected to LM3646.
+         LM3646 includes dual Torch and Flash functions.
+
 config LEDS_OT200
        tristate "LED support for the Bachmann OT200"
        depends on LEDS_CLASS && HAS_IOMEM
index 3cd76dbd9be2ff9bd1763b34adad1d0c692ce054..4b61b5c4540b57b475fbf541cc11ecc7b5ebb364 100644 (file)
@@ -53,6 +53,7 @@ obj-$(CONFIG_LEDS_NETXBIG)            += leds-netxbig.o
 obj-$(CONFIG_LEDS_ASIC3)               += leds-asic3.o
 obj-$(CONFIG_LEDS_MAX8997)             += leds-max8997.o
 obj-$(CONFIG_LEDS_LM355x)              += leds-lm355x.o
+obj-$(CONFIG_LEDS_LM3646)              += leds-lm3646.o
 obj-$(CONFIG_LEDS_BLINKM)              += leds-blinkm.o
 
 # LED SPI Drivers
diff --git a/drivers/leds/leds-lm3646.c b/drivers/leds/leds-lm3646.c
new file mode 100644 (file)
index 0000000..58e36cb
--- /dev/null
@@ -0,0 +1,516 @@
+
+/*
+* Simple driver for Texas Instruments LM3646 LED Flash driver chip
+* Copyright (C) 2013 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.
+*/
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/fs.h>
+#include <linux/regmap.h>
+#include <linux/workqueue.h>
+#include <linux/platform_data/leds-lm3646.h>
+
+#define REG_REV                        0x00
+#define REG_MODE               0x01
+#define REG_STR_CTRL   0x04
+#define REG_MAX_BR             0x05
+#define REG_FLASH_BR   0x06
+#define REG_TORCH_BR   0x07
+
+#define STR_OFF "off"
+#define NUM_OFF "0"
+
+#define WARM_FLASH_CTRL_SIZE 8
+
+struct warm_flash {
+       u8 max_current;
+       u8 led1_current;
+};
+
+enum lm3646_mode {
+       MODE_STDBY = 0x0,
+       MODE_TORCH = 0x2,
+       MODE_FLASH = 0x3,
+       MODE_MAX
+};
+
+enum lm3646_devfile {
+       DFILE_FLASH_CTRL = 0,
+       DFILE_FLASH_LED1,
+       DFILE_FLASH_DUR,
+       DFILE_TORCH_CTRL,
+       DFILE_TORCH_LED1,
+       DFILE_WARM_FLASH,
+       DFILE_MAX
+};
+
+struct lm3646 {
+       struct device *dev;
+
+       struct led_classdev cdev_flash;
+       struct led_classdev cdev_torch;
+
+       struct work_struct work_flash;
+       struct work_struct work_torch;
+
+       u8 br_flash;
+       u8 br_torch;
+
+       struct lm3646_platform_data *pdata;
+       struct regmap *regmap;
+       struct mutex lock;
+};
+
+static int lm3646_read_byte(struct lm3646 *pchip, u8 addr)
+{
+       int rval, ret;
+       ret = regmap_read(pchip->regmap, addr, &rval);
+       if (ret < 0)
+               return ret;
+       return rval;
+}
+
+static int lm3646_update_byte(struct lm3646 *pchip, u8 addr, u8 mask, u8 data)
+{
+       return regmap_update_bits(pchip->regmap, addr, mask, data);
+}
+
+static int lm3646_chip_init(struct lm3646 *pchip,
+                           struct lm3646_platform_data *pdata)
+{
+       int rval;
+
+       rval = lm3646_read_byte(pchip, REG_REV);
+       if (rval < 0)
+               goto out;
+       dev_info(pchip->dev, "LM3646 CHIP_ID/REV[0x%x]\n", rval);
+
+       if (pdata == NULL) {
+               pdata =
+                   kzalloc(sizeof(struct lm3646_platform_data), GFP_KERNEL);
+               if (pdata == NULL)
+                       return -ENODEV;
+               pdata->flash_imax = 0x0F;
+               pdata->torch_imax = 0x07;
+               pdata->led1_flash_imax = 0x7F;
+               pdata->led1_torch_imax = 0x7F;
+       }
+       pchip->pdata = pdata;
+
+       rval = lm3646_update_byte(pchip, REG_MODE, 0x08, pdata->tx_pin);
+       if (rval < 0)
+               goto out;
+       rval = lm3646_update_byte(pchip, REG_TORCH_BR, 0xFF,
+                                 pdata->torch_pin | pdata->led1_torch_imax);
+       if (rval < 0)
+               goto out;
+       rval = lm3646_update_byte(pchip, REG_FLASH_BR, 0xFF,
+                                 pdata->strobe_pin | pdata->led1_flash_imax);
+       if (rval < 0)
+               goto out;
+       rval = lm3646_update_byte(pchip, REG_MAX_BR, 0x7F,
+                                 (pdata->torch_imax << 4) | pdata->flash_imax);
+       if (rval < 0)
+               goto out;
+       pchip->br_flash = pdata->flash_imax;
+       pchip->br_torch = pdata->torch_imax;
+
+       return rval;
+out:
+       dev_err(pchip->dev, "i2c acces fail.\n");
+       return rval;
+}
+
+static void lm3646_mode_ctrl(struct lm3646 *pchip,
+                            const char *buf, enum lm3646_mode mode)
+{
+       int rval;
+
+       if (strncmp(buf, STR_OFF, 3) == 0 || strncmp(buf, NUM_OFF, 1) == 0)
+               mode = MODE_STDBY;
+
+       mutex_lock(&pchip->lock);
+       rval = lm3646_update_byte(pchip, REG_MODE, 0x03, mode);
+       mutex_unlock(&pchip->lock);
+       if (rval < 0)
+               dev_err(pchip->dev, "i2c access fail.\n");
+}
+
+static void lm3646_input_control(struct lm3646 *pchip,
+                                const char *buf, u8 reg, u8 mask)
+{
+       int rval, ival;
+
+       rval = kstrtouint(buf, 10, &ival);
+       if (rval) {
+               dev_err(pchip->dev, "str to int fail.\n");
+               return;
+       }
+       mutex_lock(&pchip->lock);
+       rval = lm3646_update_byte(pchip, reg, mask, ival);
+       mutex_unlock(&pchip->lock);
+       if (rval < 0)
+               dev_err(pchip->dev, "i2c access fail.\n");
+}
+
+/* torch brightness(max current) control */
+static void lm3646_deferred_torch_brightness_set(struct work_struct *work)
+{
+       int rval;
+       struct lm3646 *pchip = container_of(work, struct lm3646, work_torch);
+
+       rval =
+           lm3646_update_byte(pchip, REG_MAX_BR, 0x70, (pchip->br_torch) << 4);
+       if (rval < 0)
+               dev_err(pchip->dev, "i2c access fail.\n");
+}
+
+static void lm3646_torch_brightness_set(struct led_classdev *cdev,
+                                       enum led_brightness brightness)
+{
+       struct lm3646 *pchip = container_of(cdev, struct lm3646, cdev_torch);
+
+       pchip->br_torch = brightness;
+       schedule_work(&pchip->work_torch);
+}
+
+/* torch on/off(mode) control */
+static ssize_t lm3646_torch_ctrl_store(struct device *dev,
+                                      struct device_attribute *devAttr,
+                                      const char *buf, size_t size)
+{
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct lm3646 *pchip =
+           container_of(led_cdev, struct lm3646, cdev_torch);
+
+       lm3646_mode_ctrl(pchip, buf, MODE_TORCH);
+       return size;
+}
+
+/* torch dual led control */
+static ssize_t lm3646_torch_iled1_ctrl_store(struct device *dev,
+                                            struct device_attribute *devAttr,
+                                            const char *buf, size_t size)
+{
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct lm3646 *pchip =
+           container_of(led_cdev, struct lm3646, cdev_torch);
+
+       lm3646_input_control(pchip, buf, REG_TORCH_BR, 0x7F);
+       return size;
+}
+
+/* flash brightness(max current) control */
+static void lm3646_deferred_flash_brightness_set(struct work_struct *work)
+{
+       int rval;
+       struct lm3646 *pchip = container_of(work, struct lm3646, work_flash);
+
+       rval = lm3646_update_byte(pchip, REG_MAX_BR, 0x0F, pchip->br_flash);
+       if (rval < 0)
+               dev_err(pchip->dev, "i2c access fail.\n");
+}
+
+static void lm3646_flash_brightness_set(struct led_classdev *cdev,
+                                       enum led_brightness brightness)
+{
+       struct lm3646 *pchip = container_of(cdev, struct lm3646, cdev_flash);
+
+       pchip->br_flash = brightness;
+       schedule_work(&pchip->work_flash);
+}
+
+/* flash on(mode) control */
+static ssize_t lm3646_flash_ctrl_store(struct device *dev,
+                                      struct device_attribute *devAttr,
+                                      const char *buf, size_t size)
+{
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct lm3646 *pchip =
+           container_of(led_cdev, struct lm3646, cdev_flash);
+
+       lm3646_mode_ctrl(pchip, buf, MODE_FLASH);
+       return size;
+}
+
+/* flash dual led control */
+static ssize_t lm3646_flash_iled1_ctrl_store(struct device *dev,
+                                            struct device_attribute *devAttr,
+                                            const char *buf, size_t size)
+{
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct lm3646 *pchip =
+           container_of(led_cdev, struct lm3646, cdev_flash);
+
+       lm3646_input_control(pchip, buf, REG_FLASH_BR, 0x7F);
+       return size;
+}
+
+/* flash duration(timeout) control */
+static ssize_t lm3646_flash_duration_store(struct device *dev,
+                                          struct device_attribute *devAttr,
+                                          const char *buf, size_t size)
+{
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct lm3646 *pchip =
+           container_of(led_cdev, struct lm3646, cdev_flash);
+
+       lm3646_input_control(pchip, buf, REG_STR_CTRL, 0x07);
+       return size;
+}
+
+/* warm-flash setting data */
+static struct warm_flash warm_flash_set[WARM_FLASH_CTRL_SIZE] = {
+       /* LED1 = MAX, LED2 = Diabled */
+       [0] = {0x0F, 0x7F},
+       [1] = {0x0F, 0x3F},
+       [2] = {0x0F, 0x1F},
+       [3] = {0x0F, 0x0F},
+       [4] = {0x0F, 0x07},
+       [5] = {0x0F, 0x03},
+       [6] = {0x0F, 0x01},
+       /* LED1 = Diabled, LED2 = MAX */
+       [7] = {0x0F, 0x00},
+};
+
+/* flash duration(timeout) control */
+static ssize_t lm3646_warm_flash_store(struct device *dev,
+                                      struct device_attribute *devAttr,
+                                      const char *buf, size_t size)
+{
+       struct led_classdev *led_cdev = dev_get_drvdata(dev);
+       struct lm3646 *pchip =
+           container_of(led_cdev, struct lm3646, cdev_flash);
+
+       int rval, ival;
+
+       rval = kstrtouint(buf, 10, &ival);
+       if (rval) {
+               dev_err(pchip->dev, "str to int fail.\n");
+               goto out_err;
+       }
+
+       if (ival > WARM_FLASH_CTRL_SIZE - 1) {
+               dev_err(pchip->dev, "input error.\n");
+               goto out_err;
+       }
+
+       mutex_lock(&pchip->lock);
+       rval =
+           lm3646_update_byte(pchip, REG_MAX_BR, 0x0F,
+                              warm_flash_set[ival].max_current);
+       if (rval < 0)
+               goto out;
+       rval =
+           lm3646_update_byte(pchip, REG_FLASH_BR, 0x7F,
+                              warm_flash_set[ival].led1_current);
+       if (rval < 0)
+               goto out;
+       if (pchip->pdata->strobe_pin == LM3646_STROBE_PIN_DISABLED)
+               lm3646_update_byte(pchip, REG_MODE, 0x03, MODE_FLASH);
+out:
+       mutex_unlock(&pchip->lock);
+       if (rval < 0)
+               dev_err(pchip->dev, "i2c access fail.\n");
+out_err:
+       return size;
+}
+
+#define lm3646_attr(_name, _show, _store)\
+{\
+       .attr = {\
+               .name = _name,\
+               .mode = 0644,\
+       },\
+       .show = _show,\
+       .store = _store,\
+}
+
+static struct device_attribute dev_attr_ctrl[DFILE_MAX] = {
+       [DFILE_FLASH_CTRL] = lm3646_attr("ctrl", NULL, lm3646_flash_ctrl_store),
+       [DFILE_FLASH_LED1] =
+           lm3646_attr("iled1", NULL, lm3646_flash_iled1_ctrl_store),
+       [DFILE_FLASH_DUR] = lm3646_attr("duration",
+                                       NULL, lm3646_flash_duration_store),
+       [DFILE_TORCH_CTRL] = lm3646_attr("ctrl", NULL, lm3646_torch_ctrl_store),
+       [DFILE_TORCH_LED1] =
+           lm3646_attr("iled1", NULL, lm3646_torch_iled1_ctrl_store),
+       [DFILE_WARM_FLASH] =
+           lm3646_attr("warmness", NULL, lm3646_warm_flash_store),
+};
+
+static const struct regmap_config lm3646_regmap = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .max_register = 0xFF,
+};
+
+/* module initialize */
+static int lm3646_probe(struct i2c_client *client,
+                       const struct i2c_device_id *id)
+{
+       struct lm3646_platform_data *pdata = client->dev.platform_data;
+       struct lm3646 *pchip;
+
+       int err;
+       /* i2c check */
+       if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+               dev_err(&client->dev, "i2c functionality check fail.\n");
+               return -EOPNOTSUPP;
+       }
+
+       pchip = devm_kzalloc(&client->dev, sizeof(struct lm3646), GFP_KERNEL);
+       if (!pchip)
+               return -ENOMEM;
+
+       pchip->dev = &client->dev;
+       pchip->regmap = devm_regmap_init_i2c(client, &lm3646_regmap);
+       if (IS_ERR(pchip->regmap)) {
+               err = PTR_ERR(pchip->regmap);
+               dev_err(&client->dev, "Failed to allocate register map: %d\n",
+                       err);
+               return err;
+       }
+       mutex_init(&pchip->lock);
+       i2c_set_clientdata(client, pchip);
+
+       /* platform data check */
+       err = lm3646_chip_init(pchip, pdata);
+       if (err < 0)
+               goto err_out;
+       /* flash brightness control */
+       INIT_WORK(&pchip->work_flash, lm3646_deferred_flash_brightness_set);
+       pchip->cdev_flash.name = "flash";
+       pchip->cdev_flash.max_brightness = 16;
+       pchip->cdev_flash.brightness = pchip->br_flash;
+       pchip->cdev_flash.brightness_set = lm3646_flash_brightness_set;
+       pchip->cdev_flash.default_trigger = "flash";
+       err = led_classdev_register((struct device *)
+                                   &client->dev, &pchip->cdev_flash);
+       if (err < 0)
+               goto err_out;
+       /* flash on control */
+       err = device_create_file(pchip->cdev_flash.dev,
+                                &dev_attr_ctrl[DFILE_FLASH_CTRL]);
+       if (err < 0)
+               goto err_create_flash_ctrl_file;
+       /* flash duration control */
+       err = device_create_file(pchip->cdev_flash.dev,
+                                &dev_attr_ctrl[DFILE_FLASH_DUR]);
+       if (err < 0)
+               goto err_create_flash_duration_file;
+       /* flash - dual led control */
+       err = device_create_file(pchip->cdev_flash.dev,
+                                &dev_attr_ctrl[DFILE_FLASH_LED1]);
+       if (err < 0)
+               goto err_create_flash_iled1_file;
+
+       /* flash - warmness input */
+       err = device_create_file(pchip->cdev_flash.dev,
+                                &dev_attr_ctrl[DFILE_WARM_FLASH]);
+       if (err < 0)
+               goto err_create_flash_warmness_file;
+
+       /* torch brightness control */
+       INIT_WORK(&pchip->work_torch, lm3646_deferred_torch_brightness_set);
+       pchip->cdev_torch.name = "torch";
+       pchip->cdev_torch.max_brightness = 8;
+       pchip->cdev_torch.brightness = pchip->br_torch;
+       pchip->cdev_torch.brightness_set = lm3646_torch_brightness_set;
+       pchip->cdev_torch.default_trigger = "torch";
+       err = led_classdev_register((struct device *)
+                                   &client->dev, &pchip->cdev_torch);
+       if (err < 0)
+               goto err_create_torch_file;
+       /* torch on/off control */
+       err = device_create_file(pchip->cdev_torch.dev,
+                                &dev_attr_ctrl[DFILE_TORCH_CTRL]);
+       if (err < 0)
+               goto err_create_torch_ctrl_file;
+       /* torch - dual led control */
+       err = device_create_file(pchip->cdev_torch.dev,
+                                &dev_attr_ctrl[DFILE_TORCH_LED1]);
+       if (err < 0)
+               goto err_create_torch_iled1_file;
+       return 0;
+
+err_create_torch_iled1_file:
+       device_remove_file(pchip->cdev_flash.dev,
+                          &dev_attr_ctrl[DFILE_TORCH_CTRL]);
+err_create_torch_ctrl_file:
+       led_classdev_unregister(&pchip->cdev_torch);
+err_create_torch_file:
+       device_remove_file(pchip->cdev_flash.dev,
+                          &dev_attr_ctrl[DFILE_WARM_FLASH]);
+err_create_flash_warmness_file:
+       device_remove_file(pchip->cdev_flash.dev,
+                          &dev_attr_ctrl[DFILE_FLASH_LED1]);
+err_create_flash_iled1_file:
+       device_remove_file(pchip->cdev_flash.dev,
+                          &dev_attr_ctrl[DFILE_FLASH_DUR]);
+err_create_flash_duration_file:
+       device_remove_file(pchip->cdev_flash.dev,
+                          &dev_attr_ctrl[DFILE_FLASH_CTRL]);
+err_create_flash_ctrl_file:
+       led_classdev_unregister(&pchip->cdev_flash);
+err_out:
+       return err;
+}
+
+static int lm3646_remove(struct i2c_client *client)
+{
+       struct lm3646 *pchip = i2c_get_clientdata(client);
+       /* set standby mode */
+       lm3646_update_byte(pchip, REG_MODE, 0x03, MODE_STDBY);
+       /* flash */
+       device_remove_file(pchip->cdev_flash.dev,
+                          &dev_attr_ctrl[DFILE_FLASH_LED1]);
+       device_remove_file(pchip->cdev_flash.dev,
+                          &dev_attr_ctrl[DFILE_FLASH_DUR]);
+       device_remove_file(pchip->cdev_flash.dev,
+                          &dev_attr_ctrl[DFILE_TORCH_CTRL]);
+       device_remove_file(pchip->cdev_flash.dev,
+                          &dev_attr_ctrl[DFILE_WARM_FLASH]);
+       led_classdev_unregister(&pchip->cdev_flash);
+       /* torch */
+       device_remove_file(pchip->cdev_torch.dev,
+                          &dev_attr_ctrl[DFILE_TORCH_LED1]);
+       device_remove_file(pchip->cdev_torch.dev,
+                          &dev_attr_ctrl[DFILE_TORCH_CTRL]);
+       led_classdev_unregister(&pchip->cdev_torch);
+
+       return 0;
+}
+
+static const struct i2c_device_id lm3646_id[] = {
+       {LM3646_NAME, 0},
+       {}
+};
+
+MODULE_DEVICE_TABLE(i2c, lm3646_id);
+
+static struct i2c_driver lm3646_i2c_driver = {
+       .driver = {
+                  .name = LM3646_NAME,
+                  .owner = THIS_MODULE,
+                  .pm = NULL,
+                  },
+       .probe = lm3646_probe,
+       .remove = lm3646_remove,
+       .id_table = lm3646_id,
+};
+
+module_i2c_driver(lm3646_i2c_driver);
+
+MODULE_DESCRIPTION("Texas Instruments Flash Lighting driver for LM3646");
+MODULE_AUTHOR("Daniel Jeong <daniel.jeong@ti.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/platform_data/leds-lm3646.h b/include/linux/platform_data/leds-lm3646.h
new file mode 100644 (file)
index 0000000..55a9ea7
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Simple driver for Texas Instruments LM3642 LED Flash driver chip
+ * Copyright (C) 2013 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 __LINUX_LM3646_H
+#define __LINUX_LM3646_H
+
+#define LM3646_NAME "leds-lm3646"
+#define LM3646_ADDR 0x63
+
+enum lm3646_tx_pin {
+       LM3646_TX_PIN_DISABLED = 0,
+       LM3646_TX_PIN_ENABLED = 0x08,
+};
+
+enum lm3646_torch_pin {
+       LM3646_TORCH_PIN_DISABLED = 0,
+       LM3646_TORCH_PIN_ENABLED = 0x80,
+};
+
+enum lm3646_strobe_pin {
+       LM3646_STROBE_PIN_DISABLED = 0,
+       LM3646_STROBE_PIN_ENABLED = 0x80,
+};
+
+/* struct lm3646 platform data
+ * @flash_imax 0x0 =  93.4 mA
+ *                        0x1 = 187.1 mA
+ *                             .....
+ *                        0xF = 1500 mA
+ * @torch_imax 0x0 = 23.1 mA
+ *                        0x1 = 46.5 mA
+ *                             .....
+ *                        0x7 = 187.1 mA
+ * @led1_flash_imax 0x00 = 0 mA(disabled), led2=flash_imax
+ *                                     0x01 = 23.1 mA, led2=flash_imax-23.1mA
+ *                                     .....
+ *                                     0x7F = 1.5A, led2=disabled
+ * @led1_torch_imax 0x00 = 0 mA(disabled), led2=torch_imax
+ *                                     0x01 = 2.6 mA, led2=torch_imax-2.6mA
+ *                                     .....
+ *                                     0x7F = 187.1 mA, led2=disabled
+ * @tx_pin Enable tx pin and tx current reduction function
+ * @torch_pin : torch pin enable
+ * @strobe_pin : strobe pin enable
+ */
+struct lm3646_platform_data {
+
+       u8 flash_imax;
+       u8 torch_imax;
+       u8 led1_flash_imax;
+       u8 led1_torch_imax;
+
+       enum lm3646_tx_pin tx_pin;
+       enum lm3646_torch_pin torch_pin;
+       enum lm3646_strobe_pin strobe_pin;
+};
+
+#endif /* __LINUX_LM3646_H */