From 6ecd61845c167adc8dc6b48ad66b0e3205e88b2a Mon Sep 17 00:00:00 2001 From: Dan Murphy Date: Tue, 8 Jun 2010 14:08:27 -0500 Subject: [PATCH] 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 --- drivers/leds/Kconfig | 6 + drivers/leds/Makefile | 1 + drivers/leds/leds-lp8550.c | 490 ++++++++++++++++++++++++++++++++++++ include/linux/leds-lp8550.h | 42 ++++ 4 files changed, 539 insertions(+) create mode 100755 drivers/leds/leds-lp8550.c create mode 100755 include/linux/leds-lp8550.h 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 +#include +#include +#include +#include +#include +#include +#include + +#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__ */ -- 2.34.1