leds: National LP8550 LED driver for the display
authorDan Murphy <wldm10@motorola.com>
Tue, 8 Jun 2010 19:08:27 +0000 (14:08 -0500)
committerColin Cross <ccross@android.com>
Wed, 6 Oct 2010 23:33:06 +0000 (16:33 -0700)
Initial submission of the National LP8550 driver for p1

Change-Id: I2d2dc42a1c06fbc682fd8f556fa89c864c749db8
Signed-off-by: Dan Murphy <wldm10@motorola.com>
drivers/leds/Kconfig
drivers/leds/Makefile
drivers/leds/leds-lp8550.c [new file with mode: 0755]
include/linux/leds-lp8550.h [new file with mode: 0755]

index 8012334ab14b73ae72797ffdbe68a2d7a679a2de..e95844149292e70d64a8206be1195cef44422226 100644 (file)
@@ -150,6 +150,12 @@ config LEDS_CPCAP
        help
          This option enables support for display LEDs connected to CPCAP
 
+config LEDS_LP8550
+       tristate "LED support for the LP8550"
+       depends on LEDS_CLASS && I2C
+       help
+         This option enables support for the LP8550 LED driver
+
 config LEDS_GPIO
        tristate "LED Support for GPIO connected LEDs"
        depends on GENERIC_GPIO
index 975a65694254cf18ef364fc10999d20f8e309565..d15cf2dd846ab951a6bfbc55af0a910af8b37aaf 100644 (file)
@@ -40,6 +40,7 @@ obj-$(CONFIG_LEDS_MC13783)            += leds-mc13783.o
 obj-$(CONFIG_LEDS_NS2)                 += leds-ns2.o
 obj-$(CONFIG_LEDS_AUO_PANEL)           += leds-auo-panel-backlight.o
 obj-$(CONFIG_LEDS_CPCAP)               += leds-ld-cpcap.o
+obj-$(CONFIG_LEDS_LP8550)              += leds-lp8550.o
 
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_DAC124S085)          += leds-dac124s085.o
diff --git a/drivers/leds/leds-lp8550.c b/drivers/leds/leds-lp8550.c
new file mode 100755 (executable)
index 0000000..b0a0080
--- /dev/null
@@ -0,0 +1,490 @@
+/*
+ * Copyright (C) 2010 Motorola, Inc.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307, USA
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/earlysuspend.h>
+#include <linux/platform_device.h>
+#include <linux/leds-lp8550.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#define DEBUG
+
+#define LD_LP8550_LAST_BRIGHTNESS_MASK 0xFE
+
+#define LD_LP8550_ALLOWED_R_BYTES 1
+#define LD_LP8550_ALLOWED_W_BYTES 2
+#define LD_LP8550_MAX_RW_RETRIES 5
+#define LD_LP8550_I2C_RETRY_DELAY 10
+
+#define LP8550_BRIGHTNESS_CTRL 0x00
+#define LP8550_DEVICE_CTRL     0x01
+#define LP8550_FAULT           0x02
+#define LP8550_CHIP_ID         0x03
+#define LP8550_DIRECT_CTRL     0x04
+#define LP8550_TEMP_MSB                0x05
+#define LP8550_TEMP_LSB                0x06
+
+/* EEPROM Register address */
+#define LP8550_EEPROM_CTRL     0x72
+#define LP8550_EEPROM_A0       0xa0
+#define LP8550_EEPROM_A1       0xa1
+#define LP8550_EEPROM_A2       0xa2
+#define LP8550_EEPROM_A3       0xa3
+#define LP8550_EEPROM_A4       0xa4
+#define LP8550_EEPROM_A5       0xa5
+#define LP8550_EEPROM_A6       0xa6
+#define LP8550_EEPROM_A7       0xa7
+
+
+struct lp8550_data {
+       struct led_classdev led_dev;
+       struct i2c_client *client;
+       struct work_struct wq;
+       struct lp8550_platform_data *led_pdata;
+       struct early_suspend early_suspend;
+       uint8_t last_requested_brightness;
+       int brightness;
+};
+
+#ifdef DEBUG
+struct lp8550_reg {
+       const char *name;
+       uint8_t reg;
+} lp8550_regs[] = {
+       { "BRIGHTNESS_CTRL",    LP8550_BRIGHTNESS_CTRL },
+       { "DEV_CTRL",           LP8550_DEVICE_CTRL },
+       { "FAULT",              LP8550_FAULT },
+       { "CHIP_ID",            LP8550_CHIP_ID },
+       { "DIRECT_CTRL",        LP8550_DIRECT_CTRL },
+       { "EEPROM_CTRL",        LP8550_EEPROM_CTRL },
+       { "EEPROM_A0",          LP8550_EEPROM_A0 },
+       { "EEPROM_A1",          LP8550_EEPROM_A1 },
+       { "EEPROM_A2",          LP8550_EEPROM_A2 },
+       { "EEPROM_A3",          LP8550_EEPROM_A3 },
+       { "EEPROM_A4",          LP8550_EEPROM_A4 },
+       { "EEPROM_A5",          LP8550_EEPROM_A5 },
+       { "EEPROM_A6",          LP8550_EEPROM_A6 },
+       { "EEPROM_A7",          LP8550_EEPROM_A7 },
+};
+#endif
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void lp8550_early_suspend(struct early_suspend *handler);
+static void lp8550_late_resume(struct early_suspend *handler);
+#endif
+
+static uint32_t lp8550_debug;
+module_param_named(als_debug, lp8550_debug, uint, 0664);
+
+static int lp8550_read_reg(struct lp8550_data *led_data, uint8_t reg,
+                  uint8_t *value)
+{
+       int error = 0;
+       int i = 0;
+       uint8_t dest_buffer;
+
+       if (!value) {
+               pr_err("%s: invalid value pointer\n", __func__);
+               return -EINVAL;
+       }
+       do {
+               dest_buffer = reg;
+               error = i2c_master_send(led_data->client, &dest_buffer, 1);
+               if (error == 1) {
+                       error = i2c_master_recv(led_data->client,
+                               &dest_buffer, LD_LP8550_ALLOWED_R_BYTES);
+               }
+               if (error != LD_LP8550_ALLOWED_R_BYTES) {
+                       pr_err("%s: read[%i] failed: %d\n", __func__, i, error);
+                       msleep(LD_LP8550_I2C_RETRY_DELAY);
+               }
+       } while ((error != LD_LP8550_ALLOWED_R_BYTES) &&
+                       ((++i) < LD_LP8550_MAX_RW_RETRIES));
+
+       if (error == LD_LP8550_ALLOWED_R_BYTES) {
+               error = 0;
+               *value = dest_buffer;
+       }
+
+       return error;
+}
+
+static int lp8550_write_reg(struct lp8550_data *led_data, uint8_t reg,
+                       uint8_t value)
+{
+       uint8_t buf[LD_LP8550_ALLOWED_W_BYTES] = { reg, value };
+       int bytes;
+       int i = 0;
+
+       do {
+               bytes = i2c_master_send(led_data->client, buf,
+                                       LD_LP8550_ALLOWED_W_BYTES);
+
+               if (bytes != LD_LP8550_ALLOWED_W_BYTES) {
+                       pr_err("%s: write %d failed: %d\n", __func__, i, bytes);
+                       msleep(LD_LP8550_I2C_RETRY_DELAY);
+               }
+       } while ((bytes != (LD_LP8550_ALLOWED_W_BYTES))
+                && ((++i) < LD_LP8550_MAX_RW_RETRIES));
+
+       if (bytes != LD_LP8550_ALLOWED_W_BYTES) {
+               pr_err("%s: i2c_master_send error\n", __func__);
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int ld_lp8550_init_registers(struct lp8550_data *led_data)
+{
+       unsigned i, n, reg_count, reg_addr;
+       uint8_t value = 0;
+
+       /* Check the EEPROM values and update if neccessary */
+       reg_count = 8;
+       reg_addr = LP8550_EEPROM_A0;
+       for (i = 0, n = 0; i < reg_count; i++) {
+               lp8550_read_reg(led_data, reg_addr, &value);
+               if (lp8550_debug)
+                       pr_info("%s:Register 0x%x value 0x%X\n", __func__,
+                               reg_addr, value);
+               if (value != led_data->led_pdata->eeprom_table[n].eeprom_data) {
+                       if (lp8550_debug)
+                               pr_info("%s:Writing 0x%x to 0x%X\n", __func__,
+                               led_data->led_pdata->eeprom_table[n].eeprom_data,
+                               reg_addr);
+                       if (lp8550_write_reg(led_data, LP8550_DEVICE_CTRL,
+                                       0x05))
+                               pr_err("%s:Register initialization failed\n",
+                                       __func__);
+                       if (lp8550_write_reg(led_data, reg_addr,
+                               led_data->led_pdata->eeprom_table[n].eeprom_data))
+                               pr_err("%s:Register initialization failed\n",
+                                       __func__);
+                       if (lp8550_write_reg(led_data, LP8550_EEPROM_CTRL,
+                                       0x40))
+                               pr_err("%s:Register initialization failed\n",
+                                       __func__);
+                       if (lp8550_write_reg(led_data, LP8550_EEPROM_CTRL,
+                                       0x20))
+                               pr_err("%s:Register initialization failed\n",
+                                       __func__);
+                       msleep(100);
+                       if (lp8550_write_reg(led_data, LP8550_EEPROM_CTRL,
+                                       0x00))
+                               pr_err("%s:Register initialization failed\n",
+                                       __func__);
+               }
+               n++;
+               reg_addr++;
+       }
+
+       if (lp8550_write_reg(led_data, LP8550_DEVICE_CTRL,
+               led_data->led_pdata->dev_ctrl_config)) {
+               pr_err("%s:Register initialization failed\n", __func__);
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static void ld_lp8550_brightness_set(struct led_classdev *led_cdev,
+                                    enum led_brightness brightness)
+{
+       struct lp8550_data *led_data =
+               container_of(led_cdev, struct lp8550_data, led_dev);
+
+       if (brightness > 255)
+               brightness = 255;
+
+       led_data->brightness = brightness;
+       schedule_work(&led_data->wq);
+}
+EXPORT_SYMBOL(ld_lp8550_brightness_set);
+
+static void lp8550_brightness_work(struct work_struct *work)
+{
+       int brightness = 0;
+       int error = 0;
+       struct lp8550_data *led_data =
+               container_of(work, struct lp8550_data, wq);
+
+       brightness = led_data->brightness;
+       if (brightness == LED_OFF) {
+               brightness &= LD_LP8550_LAST_BRIGHTNESS_MASK;
+               if (lp8550_write_reg(led_data, LP8550_DEVICE_CTRL,
+                               brightness)) {
+                       pr_err("%s:writing failed while setting brightness:%d\n",
+                               __func__, error);
+               }
+       } else {
+               brightness |= 0x01;
+               if (lp8550_write_reg(led_data, LP8550_DEVICE_CTRL,
+                               brightness)) {
+                       pr_err("%s:writing failed while setting brightness:%d\n",
+                               __func__, error);
+               }
+               if (lp8550_write_reg(led_data, LP8550_BRIGHTNESS_CTRL, brightness)) {
+                               pr_err("%s:Failed to set brightness:%d\n",
+                               __func__, error);
+               }
+               led_data->last_requested_brightness = brightness;
+       }
+}
+
+#ifdef DEBUG
+static ssize_t ld_lp8550_registers_show(struct device *dev,
+                             struct device_attribute *attr, char *buf)
+{
+       struct i2c_client *client = container_of(dev->parent, struct i2c_client,
+                                                dev);
+       struct lp8550_data *led_data = i2c_get_clientdata(client);
+       unsigned i, n, reg_count;
+       uint8_t value = 0;
+
+       reg_count = sizeof(lp8550_regs) / sizeof(lp8550_regs[0]);
+       for (i = 0, n = 0; i < reg_count; i++) {
+               lp8550_read_reg(led_data, lp8550_regs[i].reg, &value);
+               n += scnprintf(buf + n, PAGE_SIZE - n,
+                              "%-20s = 0x%02X\n",
+                              lp8550_regs[i].name,
+                              value);
+       }
+
+       return n;
+}
+
+static ssize_t ld_lp8550_registers_store(struct device *dev,
+                              struct device_attribute *attr,
+                              const char *buf, size_t count)
+{
+       struct i2c_client *client = container_of(dev->parent, struct i2c_client,
+                                                dev);
+       struct lp8550_data *led_data = i2c_get_clientdata(client);
+       unsigned i, reg_count, value;
+       int error;
+       char name[30];
+
+       if (count >= 30) {
+               pr_err("%s:input too long\n", __func__);
+               return -1;
+       }
+
+       if (sscanf(buf, "%s %x", name, &value) != 2) {
+               pr_err("%s:unable to parse input\n", __func__);
+               return -1;
+       }
+
+       reg_count = sizeof(lp8550_regs) / sizeof(lp8550_regs[0]);
+       for (i = 0; i < reg_count; i++) {
+               if (!strcmp(name, lp8550_regs[i].name)) {
+                       error = lp8550_write_reg(led_data,
+                               lp8550_regs[i].reg,
+                               value);
+                       if (error) {
+                               pr_err("%s:Failed to write register %s\n",
+                                       __func__, name);
+                               return -1;
+                       }
+                       return count;
+               }
+       }
+
+       pr_err("%s:no such register %s\n", __func__, name);
+       return -1;
+}
+static DEVICE_ATTR(registers, 0644, ld_lp8550_registers_show,
+               ld_lp8550_registers_store);
+#endif
+
+static int ld_lp8550_probe(struct i2c_client *client,
+                          const struct i2c_device_id *id)
+{
+       struct lp8550_platform_data *pdata = client->dev.platform_data;
+       struct lp8550_data *led_data;
+       int error = 0;
+
+       if (pdata == NULL) {
+               pr_err("%s: platform data required\n", __func__);
+               return -ENODEV;
+       } else if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+               pr_err("%s:I2C_FUNC_I2C not supported\n", __func__);
+               return -ENODEV;
+       }
+
+       led_data = kzalloc(sizeof(struct lp8550_data), GFP_KERNEL);
+       if (led_data == NULL) {
+               error = -ENOMEM;
+               goto err_alloc_data_failed;
+       }
+
+       led_data->client = client;
+
+       led_data->led_dev.name = LD_LP8550_LED_DEV;
+       led_data->led_dev.brightness_set = ld_lp8550_brightness_set;
+       led_data->led_pdata = client->dev.platform_data;
+
+       i2c_set_clientdata(client, led_data);
+
+       error = ld_lp8550_init_registers(led_data);
+       if (error < 0) {
+               pr_err("%s: Register Initialization failed: %d\n",
+                      __func__, error);
+               error = -ENODEV;
+               goto err_reg_init_failed;
+       }
+
+       error = lp8550_write_reg(led_data, LP8550_BRIGHTNESS_CTRL,
+                                pdata->power_up_brightness);
+       if (error) {
+               pr_err("%s:Setting power up brightness failed %d\n",
+                      __func__, error);
+               error = -ENODEV;
+               goto err_reg_init_failed;
+       }
+
+       INIT_WORK(&led_data->wq, lp8550_brightness_work);
+
+       error = led_classdev_register((struct device *) &client->dev,
+                               &led_data->led_dev);
+       if (error < 0) {
+               pr_err("%s: Register led class failed: %d\n", __func__, error);
+               error = -ENODEV;
+               goto err_class_reg_failed;
+       }
+
+#ifdef DEBUG
+       error = device_create_file(led_data->led_dev.dev, &dev_attr_registers);
+       if (error < 0) {
+               pr_err("%s:File device creation failed: %d\n", __func__, error);
+       }
+#endif
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+       led_data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
+       led_data->early_suspend.suspend = lp8550_early_suspend;
+       led_data->early_suspend.resume = lp8550_late_resume;
+       register_early_suspend(&led_data->early_suspend);
+#endif
+       return 0;
+
+err_class_reg_failed:
+err_reg_init_failed:
+       kfree(led_data);
+err_alloc_data_failed:
+       return error;
+}
+
+static int ld_lp8550_remove(struct i2c_client *client)
+{
+       struct lp8550_data *led_data = i2c_get_clientdata(client);
+
+       unregister_early_suspend(&led_data->early_suspend);
+#ifdef DEBUG
+       device_remove_file(led_data->led_dev.dev, &dev_attr_registers);
+#endif
+       led_classdev_unregister(&led_data->led_dev);
+       kfree(led_data);
+       return 0;
+}
+
+static int lp8550_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+       struct lp8550_data *led_data = i2c_get_clientdata(client);
+       int brightness;
+
+       if (lp8550_debug)
+               pr_info("%s: Suspending\n", __func__);
+
+       brightness = (led_data->last_requested_brightness &
+                       LD_LP8550_LAST_BRIGHTNESS_MASK);
+       lp8550_write_reg(led_data, LP8550_DEVICE_CTRL, brightness);
+
+       return 0;
+}
+
+static int lp8550_resume(struct i2c_client *client)
+{
+       struct lp8550_data *led_data = i2c_get_clientdata(client);
+       int brightness;
+
+       brightness = (led_data->last_requested_brightness | 0x01);
+       if (lp8550_debug)
+               pr_info("%s: Resuming with brightness %i\n",
+               __func__, brightness);
+
+       lp8550_write_reg(led_data, LP8550_DEVICE_CTRL, brightness);
+
+       return 0;
+}
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void lp8550_early_suspend(struct early_suspend *handler)
+{
+       struct lp8550_data *led_data;
+
+       led_data = container_of(handler, struct lp8550_data, early_suspend);
+       lp8550_suspend(led_data->client, PMSG_SUSPEND);
+}
+
+static void lp8550_late_resume(struct early_suspend *handler)
+{
+       struct lp8550_data *led_data;
+
+       led_data = container_of(handler, struct lp8550_data, early_suspend);
+       lp8550_resume(led_data->client);
+}
+#endif
+
+static const struct i2c_device_id lp8550_id[] = {
+       {LD_LP8550_NAME, 0},
+       {}
+};
+
+static struct i2c_driver ld_lp8550_i2c_driver = {
+       .probe = ld_lp8550_probe,
+       .remove = ld_lp8550_remove,
+#ifndef CONFIG_HAS_EARLYSUSPEND
+       .suspend        = lp8550_suspend,
+       .resume         = lp8550_resume,
+#endif
+       .id_table = lp8550_id,
+       .driver = {
+                  .name = LD_LP8550_NAME,
+                  .owner = THIS_MODULE,
+       },
+};
+
+static int __init ld_lp8550_init(void)
+{
+       return i2c_add_driver(&ld_lp8550_i2c_driver);
+}
+
+static void __exit ld_lp8550_exit(void)
+{
+       i2c_del_driver(&ld_lp8550_i2c_driver);
+
+}
+
+module_init(ld_lp8550_init);
+module_exit(ld_lp8550_exit);
+
+MODULE_DESCRIPTION("Lighting driver for LP8550");
+MODULE_AUTHOR("Dan Murphy D.Murphy@Motorola.com");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/leds-lp8550.h b/include/linux/leds-lp8550.h
new file mode 100755 (executable)
index 0000000..adfcb02
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 Motorola, Inc.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307, USA
+ */
+
+#ifndef _LINUX_LED_LD_LP8550_H__
+#define _LINUX_LED_LD_LP8550_H__
+
+#ifdef __KERNEL__
+
+#define LD_LP8550_LED_DEV "lcd-backlight"
+#define LD_LP8550_NAME "lp8550_led"
+
+struct lp8550_eeprom_data {
+       u8 eeprom_data;
+};
+
+struct lp8550_platform_data {
+       u8 power_up_brightness;
+       u8 dev_ctrl_config;
+       u8 brightness_control;
+       u8 dev_id;
+       u8 direct_ctrl;
+       struct lp8550_eeprom_data *eeprom_table;
+       int eeprom_tbl_sz;
+};
+
+#endif /* __KERNEL__ */
+#endif /* _LINUX_LED_LD_LP8550_H__ */