Hello, Backlight Class Maintainer
I send the patch for new lp855x backlight driver. This patch provides Texas Instruments LP855x backlight driver.
* Supported Backlight ICs LP8550, LP8551, LP8552 and LP8556
* Consolidated Driver Just one driver file can support several LP855x backlight ICs.
* Driver Description 1) Brightness control Brightness can be controlled by pwm input or i2c command. lp855x driver support both cases. PWM are platform specific data, so the driver provides pwm functions in lp855x_platform_data structure.
2) Debugfs nodes : For debug purpose, 3 files are exported in the debugfs. a) bl_ctl_mode : backlight control mode pwm based or register based
b) chip_id : lp855x chip id lp8550/lp8551/lp8552/lp8556
c) registers we can control the lp855x registers via the debugfs. read/write/dump mode are supported.
3) Platform data for lp855x a) name : backlight driver name b) mode : brightness control by pwm or lp855x register c) device_control : value of DEVICE CONTROL register d) initial_brightness : initial value of backlight brightness e) max_brightness : maximum value of backlight brightness f) pwm_data : platform specific pwm generation functions. Only valid when brightness is pwm input mode. g) load_new_rom_data : 0 : use default configuration data 1 : update values of eeprom or eprom registers on loading driver h) size_program : total size of lp855x_rom_data i) rom_data : list of new eeprom/eprom registers
example 1) lp8552 platform data : i2c register mode with new eeprom data
static struct lp855x_rom_data lp8552_eeprom_arr[] = { {0xa5, 0x4f}, /* EN_VSYNC=0 */ };
static struct lp855x_platform_data lp8552_pdata = { .name = "lcd-backlight", .mode = REGISTER_BASED, .device_control = I2C_CONFIG(LP8552), .initial_brightness = INITIAL_BRT, .max_brightness = MAX_BRT, .load_new_rom_data = 1, .size_program = ARRAY_SIZE(lp8552_eeprom_arr), .rom_data = lp8552_eeprom_arr, };
example 2) lp8556 platform data : pwm input mode
static struct lp855x_platform_data lp8556_pdata = { .name = "lcd-backlight", .mode = PWM_BASED, .device_control = PWM_CONFIG(LP8556), .initial_brightness = INITIAL_BRT, .max_brightness = MAX_BRT, .pwm_data = { .pwm_set_intensity = platform_pwm_set_intensity, .pwm_get_intensity = platform_pwm_get_intensity, }, };
* Test Environment OMAP3530 beagelborad with LP8551/LP8552/LP8556 ICs
From 903ca54f2da059321f6f1519af79aeec19aff577 Mon Sep 17 00:00:00 2001
From: Milo(Woogyom) Kim milo.kim@ti.com Date: Mon, 28 Nov 2011 10:48:07 +0900 Subject: [PATCH] backlight : add lp855x backlight driver
Signed-off-by: Milo(Woogyom) Kim milo.kim@ti.com --- drivers/video/backlight/Kconfig | 6 + drivers/video/backlight/Makefile | 1 + drivers/video/backlight/lp855x_bl.c | 473 +++++++++++++++++++++++++++++++++++ include/linux/lp855x.h | 123 +++++++++ 4 files changed, 603 insertions(+), 0 deletions(-) create mode 100755 drivers/video/backlight/lp855x_bl.c create mode 100644 include/linux/lp855x.h
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 278aeaa..9e7ba76 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -342,6 +342,12 @@ config BACKLIGHT_AAT2870 If you have a AnalogicTech AAT2870 say Y to enable the backlight driver.
+config BACKLIGHT_LP855X + tristate "Backlight driver for Texas Instruments LP855X" + depends on BACKLIGHT_CLASS_DEVICE && I2C + help + This supports TI LP8550, LP8551, LP8552 and LP8556 backlight driver. + endif # BACKLIGHT_CLASS_DEVICE
endif # BACKLIGHT_LCD_SUPPORT diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index fdd1fc4..91ae232 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -39,4 +39,5 @@ obj-$(CONFIG_BACKLIGHT_ADP8870) += adp8870_bl.o obj-$(CONFIG_BACKLIGHT_88PM860X) += 88pm860x_bl.o obj-$(CONFIG_BACKLIGHT_PCF50633) += pcf50633-backlight.o obj-$(CONFIG_BACKLIGHT_AAT2870) += aat2870_bl.o +obj-$(CONFIG_BACKLIGHT_LP855X) += lp855x_bl.o
diff --git a/drivers/video/backlight/lp855x_bl.c b/drivers/video/backlight/lp855x_bl.c new file mode 100755 index 0000000..2bae816 --- /dev/null +++ b/drivers/video/backlight/lp855x_bl.c @@ -0,0 +1,473 @@ +/* + * TI LP855x Backlight Driver + * + * Copyright (C) 2011 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/slab.h> +#include <linux/i2c.h> +#include <linux/backlight.h> +#include <linux/err.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> +#include <linux/lp855x.h> + +#define BRIGHTNESS_CTRL (0x00) +#define DEVICE_CTRL (0x01) + +#ifdef CONFIG_DEBUG_FS +struct debug_dentry { + struct dentry *dir; + struct dentry *reg; + struct dentry *chip; + struct dentry *blmode; +}; +#endif + +struct lp855x { + const char *chipid; + struct i2c_client *client; + struct backlight_device *bl; + struct device *dev; + struct mutex xfer_lock; + struct lp855x_platform_data *pdata; +#ifdef CONFIG_DEBUG_FS + struct debug_dentry dd; +#endif +}; + +static int lp855x_i2c_read(struct lp855x *lp, u8 reg, u8 *data, u8 len) +{ + s32 ret; + + mutex_lock(&lp->xfer_lock); + ret = i2c_smbus_read_i2c_block_data(lp->client, reg, len, data); + mutex_unlock(&lp->xfer_lock); + + return (ret != len) ? -EIO : 0; +} + +static int lp855x_i2c_write(struct lp855x *lp, u8 reg, u8 *data, u8 len) +{ + s32 ret; + + mutex_lock(&lp->xfer_lock); + ret = i2c_smbus_write_i2c_block_data(lp->client, reg, len, data); + mutex_unlock(&lp->xfer_lock); + + return ret; +} + +static inline int lp855x_read_byte(struct lp855x *lp, u8 reg, u8 *data) +{ + return lp855x_i2c_read(lp, reg, data, 1); +} + +static inline int lp855x_write_byte(struct lp855x *lp, u8 reg, u8 data) +{ + u8 written = data; + return lp855x_i2c_write(lp, reg, &written, 1); +} + +#ifdef CONFIG_DEBUG_FS +static int lp855x_dbg_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t lp855x_help_register(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + char buf[320]; + unsigned int len; + const char *help = "\n How to read/write LP855x registers\n\n \ + (example) To read 0x00 register,\n \ + echo 0x00 r > /sys/kernel/debug/lp855x/registers\n \ + To write 0xff into 0x1 address,\n \ + echo 0x00 0xff w > /sys/kernel/debug/lp855x/registers \n \ + To dump values from 0x00 to 0x06 address,\n \ + echo 0x00 0x06 d > /sys/kernel/debug/lp855x/registers\n"; + + len = snprintf(buf, sizeof(buf), "%s\n", help); + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static char *lp855x_parse_register_cmd(const char *cmd, u8 *byte) +{ + char tmp[10]; + char *blank; + unsigned long arg; + + blank = strchr(cmd, ' '); + memset(tmp, 0x0, sizeof(tmp)); + memcpy(tmp, cmd, blank - cmd); + + if (strict_strtol(tmp, 16, &arg) < 0) + return NULL; + + *byte = arg; + return blank; +} + +static ssize_t lp855x_ctrl_register(struct file *file, + const char __user *userbuf, size_t count, + loff_t *ppos) +{ + char mode, buf[20]; + char *pos, *pos2; + u8 i, arg1, arg2, val; + struct lp855x *lp = file->private_data; + + if (copy_from_user(buf, userbuf, min(count, sizeof(buf)))) + return -EFAULT; + + mode = buf[count - 2]; + switch (mode) { + case 'r': + if (!lp855x_parse_register_cmd(buf, &arg1)) + return -EINVAL; + + lp855x_read_byte(lp, arg1, &val); + dev_info(lp->dev, "Read [0x%.2x] = 0x%.2x\n", arg1, val); + break; + case 'w': + pos = lp855x_parse_register_cmd(buf, &arg1); + if (!pos) + return -EINVAL; + pos2 = lp855x_parse_register_cmd(pos + 1, &arg2); + if (!pos2) + return -EINVAL; + + lp855x_write_byte(lp, arg1, arg2); + dev_info(lp->dev, "Written [0x%.2x] = 0x%.2x\n", arg1, arg2); + break; + case 'd': + pos = lp855x_parse_register_cmd(buf, &arg1); + if (!pos) + return -EINVAL; + pos2 = lp855x_parse_register_cmd(pos + 1, &arg2); + if (!pos2) + return -EINVAL; + + for (i = arg1; i <= arg2; i++) { + lp855x_read_byte(lp, i, &val); + dev_info(lp->dev, "Read [0x%.2x] = 0x%.2x\n", i, val); + } + break; + default: + break; + } + + return count; +} + +static ssize_t lp855x_get_chipid(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct lp855x *lp = file->private_data; + char buf[10]; + unsigned int len; + + len = snprintf(buf, sizeof(buf), "%s\n", lp->chipid); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static ssize_t lp855x_get_bl_mode(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + char buf[20]; + unsigned int len; + char *strmode = NULL; + struct lp855x *lp = file->private_data; + enum lp855x_brightness_ctrl_mode mode = lp->pdata->mode; + + if (mode == PWM_BASED) + strmode = "pwm based"; + else if (mode == REGISTER_BASED) + strmode = "register based"; + + len = snprintf(buf, sizeof(buf), "%s\n", strmode); + + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +#define LP855X_DBG_ENTRY(name, pread, pwrite) \ +static const struct file_operations dbg_##name##_fops = { \ + .open = lp855x_dbg_open, \ + .read = pread, \ + .write = pwrite, \ + .owner = THIS_MODULE, \ + .llseek = default_llseek, \ +} + +LP855X_DBG_ENTRY(registers, lp855x_help_register, lp855x_ctrl_register); +LP855X_DBG_ENTRY(chip, lp855x_get_chipid, NULL); +LP855X_DBG_ENTRY(blmode, lp855x_get_bl_mode, NULL); + +static void lp855x_create_debugfs(struct lp855x *lp) +{ + struct debug_dentry *dd = &lp->dd; + + dd->dir = debugfs_create_dir("lp855x", NULL); + + dd->reg = debugfs_create_file("registers", S_IWUSR | S_IRUGO, + dd->dir, lp, &dbg_registers_fops); + + dd->chip = debugfs_create_file("chip_id", S_IRUGO, + dd->dir, lp, &dbg_chip_fops); + + dd->blmode = debugfs_create_file("bl_ctl_mode", S_IRUGO, + dd->dir, lp, &dbg_blmode_fops); +} + +static void lp855x_remove_debugfs(struct lp855x *lp) +{ + struct debug_dentry *dd = &lp->dd; + + debugfs_remove(dd->blmode); + debugfs_remove(dd->chip); + debugfs_remove(dd->reg); + debugfs_remove(dd->dir); +} +#else +static inline void lp855x_create_debugfs(struct lp855x *lp) +{ + return; +} + +static inline void lp855x_remove_debugfs(struct lp855x *lp) +{ + return; +} +#endif + +static int lp855x_is_valid_rom_area(struct lp855x *lp, u8 addr) +{ + const char *id = lp->chipid; + u8 start, end; + + if (strstr(id, "lp8550") || strstr(id, "lp8551") + || strstr(id, "lp8552")) { + start = EEPROM_START; + end = EEPROM_END; + } else if (strstr(id, "lp8556")) { + start = EPROM_START; + end = EPROM_END; + } + + return (addr >= start && addr <= end) ? 1 : 0; +} + +static void lp855x_init_device(struct lp855x *lp) +{ + u8 val, addr; + int i, ret; + struct lp855x_platform_data *pd = lp->pdata; + + if (pd->load_new_rom_data && pd->size_program) { + for (i = 0; i < pd->size_program; i++) { + addr = pd->rom_data[i].addr; + val = pd->rom_data[i].val; + if (!lp855x_is_valid_rom_area(lp, addr)) + continue; + + ret |= lp855x_write_byte(lp, addr, val); + } + } + + val = pd->initial_brightness; + ret |= lp855x_write_byte(lp, BRIGHTNESS_CTRL, val); + + val = pd->device_control; + ret |= lp855x_write_byte(lp, DEVICE_CTRL, val); + + if (ret) + dev_err(lp->dev, "i2c write err\n"); +} + +static int lp855x_bl_update_status(struct backlight_device *bl) +{ + struct lp855x *lp = bl_get_data(bl); + enum lp855x_brightness_ctrl_mode mode = lp->pdata->mode; + + if (bl->props.state & BL_CORE_SUSPENDED) + bl->props.brightness = 0; + + if (mode == PWM_BASED) { + struct lp855x_pwm_data *pd = &lp->pdata->pwm_data; + int br = bl->props.brightness; + int max_br = bl->props.max_brightness; + + if (pd->pwm_set_intensity) + pd->pwm_set_intensity(br, max_br); + + } else if (mode == REGISTER_BASED) { + u8 val = bl->props.brightness; + lp855x_write_byte(lp, BRIGHTNESS_CTRL, val); + } + + return (bl->props.brightness); +} + +static int lp855x_bl_get_brightness(struct backlight_device *bl) +{ + struct lp855x *lp = bl_get_data(bl); + enum lp855x_brightness_ctrl_mode mode = lp->pdata->mode; + + if (mode == PWM_BASED) { + struct lp855x_pwm_data *pd = &lp->pdata->pwm_data; + int max_br = bl->props.max_brightness; + + if (pd->pwm_get_intensity) + bl->props.brightness = pd->pwm_get_intensity(max_br); + + } else if (mode == REGISTER_BASED) { + u8 val; + + lp855x_read_byte(lp, BRIGHTNESS_CTRL, &val); + bl->props.brightness = val; + } + + return (bl->props.brightness); +} + +static const struct backlight_ops lp855x_bl_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = lp855x_bl_update_status, + .get_brightness = lp855x_bl_get_brightness, +}; + +static int lp855x_backlight_register(struct lp855x *lp) +{ + struct backlight_device *bl; + struct backlight_properties props; + const char *name = lp->pdata->name; + + if (!name) + return -ENODEV; + + props.brightness = lp->pdata->initial_brightness; + props.max_brightness = + (lp->pdata->max_brightness < lp->pdata->initial_brightness) ? + 255 : lp->pdata->max_brightness; + + bl = backlight_device_register(name, lp->dev, lp, + &lp855x_bl_ops, &props); + if (IS_ERR(bl)) + return -EIO; + + lp->bl = bl; + + return 0; +} + +static void lp855x_backlight_unregister(struct lp855x *lp) +{ + if (lp->bl) + backlight_device_unregister(lp->bl); +} + +static int lp855x_probe(struct i2c_client *cl, const struct i2c_device_id *id) +{ + struct lp855x *lp; + struct lp855x_platform_data *pdata = cl->dev.platform_data; + int ret; + + if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + goto err_io; + + lp = kzalloc(sizeof(struct lp855x), GFP_KERNEL); + if (!lp) + goto err_mem; + + lp->client = cl; + lp->dev = &cl->dev; + lp->pdata = pdata; + lp->chipid = id->name; + i2c_set_clientdata(cl, lp); + + mutex_init(&lp->xfer_lock); + + lp855x_init_device(lp); + ret = lp855x_backlight_register(lp); + if (ret) + goto err_dev; + + backlight_update_status(lp->bl); + lp855x_create_debugfs(lp); + + return ret; + +err_io: + return -EIO; +err_mem: + return -ENOMEM; +err_dev: + dev_err(lp->dev, "can not register backlight device. errcode = %d\n", + ret); + kfree(lp); + return ret; +} + +static int __devexit lp855x_remove(struct i2c_client *cl) +{ + struct lp855x *lp = i2c_get_clientdata(cl); + + lp->bl->props.brightness = 0; + backlight_update_status(lp->bl); + lp855x_remove_debugfs(lp); + lp855x_backlight_unregister(lp); + kfree(lp); + + return 0; +} + +static const struct i2c_device_id lp855x_ids[] = { + {"lp8550", LP8550}, + {"lp8551", LP8551}, + {"lp8552", LP8552}, + {"lp8556", LP8556}, +}; + +static struct i2c_driver lp855x_driver = { + .driver = { + .name = "lp855x", + }, + .probe = lp855x_probe, + .remove = __devexit_p(lp855x_remove), + .id_table = lp855x_ids, +}; + +static int __init lp855x_init(void) +{ + return i2c_add_driver(&lp855x_driver); +} + +static void __exit lp855x_exit(void) +{ + i2c_del_driver(&lp855x_driver); +} + +module_init(lp855x_init); +module_exit(lp855x_exit); + +MODULE_DESCRIPTION("Texas Instruments LP855x Backlight driver"); +MODULE_AUTHOR("Milo Kim milo.kim@ti.com, Dainel Jeong daniel.jeong@ti.com"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/lp855x.h b/include/linux/lp855x.h new file mode 100644 index 0000000..5078558 --- /dev/null +++ b/include/linux/lp855x.h @@ -0,0 +1,123 @@ +/* + * lp855x.h - TI LP8556 Backlight Driver + * + * Copyright (C) 2011 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 _LP855X_H +#define _LP855X_H + +#define BL_CTL_SHFT (0) +#define BRT_MODE_SHFT (1) +#define BRT_MODE_MASK (0x06) + +/* Enable backlight. Only valid when BRT_MODE=10(I2C only) */ +#define ENABLE_BL (1) +#define DISABLE_BL (0) + +#define I2C_CONFIG(id) id ## _I2C_CONFIG +#define PWM_CONFIG(id) id ## _PWM_CONFIG + +/* DEVICE CONTROL register - LP8550 */ +#define LP8550_PWM_CONFIG (LP8550_PWM_ONLY << BRT_MODE_SHFT) +#define LP8550_I2C_CONFIG ((ENABLE_BL << BL_CTL_SHFT) | \ + (LP8550_I2C_ONLY << BRT_MODE_SHFT)) + +/* DEVICE CONTROL register - LP8551 */ +#define LP8551_PWM_CONFIG LP8550_PWM_CONFIG +#define LP8551_I2C_CONFIG LP8550_I2C_CONFIG + +/* DEVICE CONTROL register - LP8552 */ +#define LP8552_PWM_CONFIG LP8550_PWM_CONFIG +#define LP8552_I2C_CONFIG LP8550_I2C_CONFIG + +/* DEVICE CONTROL register - LP8556 */ +#define LP8556_PWM_CONFIG (LP8556_PWM_ONLY << BRT_MODE_SHFT) +#define LP8556_COMB1_CONFIG (LP8556_COMBINED1 << BRT_MODE_SHFT) +#define LP8556_I2C_CONFIG ((ENABLE_BL << BL_CTL_SHFT) | \ + (LP8556_I2C_ONLY << BRT_MODE_SHFT)) +#define LP8556_COMB2_CONFIG (LP8556_COMBINED2 << BRT_MODE_SHFT) + +/* ROM area boundary */ +#define EEPROM_START (0xA0) +#define EEPROM_END (0xA7) +#define EPROM_START (0xA0) +#define EPROM_END (0xAF) + +enum lp855x_chip_id { + LP8550, + LP8551, + LP8552, + LP8556, +}; + +enum lp855x_brightness_ctrl_mode { + PWM_BASED = 1, + REGISTER_BASED, +}; + +enum lp8550_brighntess_source { + LP8550_PWM_ONLY, + LP8550_I2C_ONLY = 2, +}; + +enum lp8551_brighntess_source { + LP8551_PWM_ONLY = LP8550_PWM_ONLY, + LP8551_I2C_ONLY = LP8550_I2C_ONLY, +}; + +enum lp8552_brighntess_source { + LP8552_PWM_ONLY = LP8550_PWM_ONLY, + LP8552_I2C_ONLY = LP8550_I2C_ONLY, +}; + +enum lp8556_brightness_source { + LP8556_PWM_ONLY, + LP8556_COMBINED1, /* pwm + i2c before the shaper block */ + LP8556_I2C_ONLY, + LP8556_COMBINED2, /* pwm + i2c after the shaper block */ +}; + +struct lp855x_pwm_data { + void (*pwm_set_intensity) (int brightness, int max_brightness); + int (*pwm_get_intensity) (int max_brightness); +}; + +struct lp855x_rom_data { + u8 addr; + u8 val; +}; + +/** + * struct lp855x_platform_data + * @name : backlight driver name + * @mode : brightness control by pwm or lp855x register + * @device_control : value of DEVICE CONTROL register + * @initial_brightness : initial value of backlight brightness + * @max_brightness : maximum value of backlight brightness + * @pwm_data : platform specific pwm generation functions. + Only valid when mode is PWM_BASED. + * @load_new_rom_data : + 0 : use default configuration data + 1 : update values of eeprom or eprom registers on loading driver + * @size_program : total size of lp855x_rom_data + * @rom_data : list of new eeprom/eprom registers + */ +struct lp855x_platform_data { + const char *name; + enum lp855x_brightness_ctrl_mode mode; + u8 device_control; + int initial_brightness; + int max_brightness; + struct lp855x_pwm_data pwm_data; + u8 load_new_rom_data; + int size_program; + struct lp855x_rom_data *rom_data; +}; + +#endif -- 1.7.4.1
Best Regards
Milo (Woogyom) Kim Texas Instruments Incorporated Software Engineer Mobile Lighting & Power Office : +82-2-560-6963