From: Dan Murphy <wldm10@motorola.com>
Date: Tue, 8 Jun 2010 19:08:27 +0000 (-0500)
Subject: leds: National LP8550 LED driver for the display
X-Git-Tag: firefly_0821_release~9834^2~849
X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=6ecd61845c167adc8dc6b48ad66b0e3205e88b2a;p=firefly-linux-kernel-4.4.55.git

leds: National LP8550 LED driver for the display

Initial submission of the National LP8550 driver for p1

Change-Id: I2d2dc42a1c06fbc682fd8f556fa89c864c749db8
Signed-off-by: Dan Murphy <wldm10@motorola.com>
---

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 8012334ab14b..e95844149292 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -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
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 975a65694254..d15cf2dd846a 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -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
index 000000000000..b0a0080c7c6e
--- /dev/null
+++ b/drivers/leds/leds-lp8550.c
@@ -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
index 000000000000..adfcb02a3230
--- /dev/null
+++ b/include/linux/leds-lp8550.h
@@ -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__ */