* Changes of lp5521_platform_data
'update_config' is added 'name' is added
Value of CONFIG register(Addr 08h) and name of led channels are dependant on the project. So these fields have been added in lp5521_platform_data.
led pattern data
lp5521 has autonomous operation mode without external control. This patch includes how to configure the led patterns and load them.
* Additional led class device attributes : delay_on, delay_off, blink For supporting blink mode, 3 attributes have been updated.
* Additional i2c device attribute : led_pattern Platform specific led pattern can be loaded via the user-space.
* Base kernel version : 3.0.9
Signed-off-by: Milo(Woogyom) Kim milo.kim@ti.com --- Documentation/leds/leds-lp5521.txt | 60 ++++++++++ drivers/leds/leds-lp5521.c | 222 +++++++++++++++++++++++++++++++++--- include/linux/leds-lp5521.h | 25 ++++ 3 files changed, 289 insertions(+), 18 deletions(-)
diff --git a/Documentation/leds/leds-lp5521.txt b/Documentation/leds/leds-lp5521.txt index c4d8d15..b0d6abe 100644 --- a/Documentation/leds/leds-lp5521.txt +++ b/Documentation/leds/leds-lp5521.txt @@ -3,6 +3,7 @@ Kernel driver for lp5521
* National Semiconductor LP5521 led driver chip * Datasheet: http://www.national.com/pf/LP/LP5521.html + or http://www.ti.com/product/lp5521
Authors: Mathias Nyman, Yuri Zaporozhets, Samu Onkalo Contact: Samu Onkalo (samu.p.onkalo-at-nokia.com) @@ -43,17 +44,23 @@ Format: 10x mA i.e 10 means 1.0 mA example platform data:
Note: chan_nr can have values between 0 and 2. +Each channel can have own name. +If name field is not defined, the default name will be set to 'xxxx:channelN' +(XXXX : pdata->label or i2c client name, N : channel number)
static struct lp5521_led_config lp5521_led_config[] = { { + .name = "red", .chan_nr = 0, .led_current = 50, .max_current = 130, }, { + .name = "green", .chan_nr = 1, .led_current = 0, .max_current = 130, }, { + .name = "blue", .chan_nr = 2, .led_current = 0, .max_current = 130, @@ -86,3 +93,56 @@ static struct lp5521_platform_data lp5521_platform_data = {
If the current is set to 0 in the platform data, that channel is disabled and it is not visible in the sysfs. + +update_config : CONFIG register (ADDR 08h) +This value is platform-specific data. If NULL, the default value will be set. + +example) + +#define LP5521_CONFIGS (LP5521_PWM_HF | LP5521_PWRSAVE_EN | \ + LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT | \ + LP5521_CLK_INT) + +static struct lp5521_platform_data lp5521_pdata = { + .led_config = lp5521_led_config, + .num_channels = ARRAY_SIZE(lp5521_led_config), + .clock_mode = LP5521_CLOCK_INT, + .update_config = LP5521_CONFIGS, +}; + +LED patterns : LP5521 has autonomous operation without external control. +Pattern data can be defined in the platform data. + +example) +/* Pattern : RGB(50,5,0) 500ms on, 500ms off, infinite loop */ +static u8 pattern_red[] = { + 0x40, 0x32, 0x60, 0x00, 0x40, 0x00, 0x60, 0x00, + }; + +static u8 pattern_green[] = { + 0x40, 0x05, 0x60, 0x00, 0x40, 0x00, 0x60, 0x00, + }; + +static struct lp5521_led_pattern board_led_patterns[] = { + { + .r = pattern_r, + .g = pattern_g, + .size_r = ARRAY_SIZE(pattern_r), + .size_g = ARRAY_SIZE(pattern_g), + }, +}; + +static struct lp5521_platform_data lp5521_platform_data = { + .led_config = lp5521_led_config, + .num_channels = ARRAY_SIZE(lp5521_led_config), + .clock_mode = LP5521_CLOCK_EXT, + .patterns = board_led_patterns, + .num_patterns = ARRAY_SIZE(board_led_patterns), +}; + +Then the predefined led pattern can be executed via the user-space. +To start the pattern #1, +# echo 1 > /sys/bus/i2c/devices/xxxx/led_pattern +(xxxx : i2c bus & slave address) +To end the pattern, +# echo 0 > /sys/bus/i2c/devices/xxxx/led_pattern diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index cc1dc48..8239319 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -80,23 +80,23 @@ /* Bits in ENABLE register */ #define LP5521_MASTER_ENABLE 0x40 /* Chip master enable */ #define LP5521_LOGARITHMIC_PWM 0x80 /* Logarithmic PWM adjustment */ +#define LP5521_EXEC_MASK 0xC0 #define LP5521_EXEC_RUN 0x2A - -/* Bits in CONFIG register */ -#define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */ -#define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */ -#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */ -#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */ -#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */ -#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */ -#define LP5521_R_TO_BATT 4 /* R out: 0 = CP, 1 = Vbat */ -#define LP5521_CLK_SRC_EXT 0 /* Ext-clk source (CLK_32K) */ -#define LP5521_CLK_INT 1 /* Internal clock */ -#define LP5521_CLK_AUTO 2 /* Automatic clock selection */ +#define LP5521_EXEC_HOLD 0x00 +#define LP5521_ENABLE_DEFAULT \ + (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM) +#define LP5521_ENABLE_RUN_PROGRAM \ + (LP5521_ENABLE_DEFAULT | LP5521_EXEC_RUN)
/* Status */ #define LP5521_EXT_CLK_USED 0x08
+/* Blink mode */ +#define ALWAYS_OFF 0 + +/* Pattern Mode */ +#define PATTERN_OFF 0 + struct lp5521_engine { int id; u8 mode; @@ -112,6 +112,8 @@ struct lp5521_led { struct led_classdev cdev; struct work_struct brightness_work; u8 brightness; + int delay_on; + int delay_off; };
struct lp5521_chip { @@ -237,6 +239,7 @@ static void lp5521_init_engine(struct lp5521_chip *chip) static int lp5521_configure(struct i2c_client *client) { struct lp5521_chip *chip = i2c_get_clientdata(client); + u8 cfg = chip->pdata->update_config; int ret;
lp5521_init_engine(chip); @@ -244,9 +247,12 @@ static int lp5521_configure(struct i2c_client *client) /* Set all PWMs to direct control mode */ ret = lp5521_write(client, LP5521_REG_OP_MODE, 0x3F);
- /* Enable auto-powersave, set charge pump to auto, red to battery */ - ret |= lp5521_write(client, LP5521_REG_CONFIG, - LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT); + if (cfg) + ret |= lp5521_write(client, LP5521_REG_CONFIG, cfg); + else + ret |= lp5521_write(client, LP5521_REG_CONFIG, + LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | + LP5521_R_TO_BATT);
/* Initialize all channels PWM to zero -> leds off */ ret |= lp5521_write(client, LP5521_REG_R_PWM, 0); @@ -533,13 +539,184 @@ static ssize_t lp5521_selftest(struct device *dev, return sprintf(buf, "%s\n", ret ? "FAIL" : "OK"); }
+static void lp5521_clear_program_memory(struct i2c_client *cl) +{ + int i; + u8 rgb_mem[] = { + LP5521_REG_R_PROG_MEM, + LP5521_REG_G_PROG_MEM, + LP5521_REG_B_PROG_MEM, + }; + + for (i = 0; i < ARRAY_SIZE(rgb_mem); i++) { + lp5521_write(cl, rgb_mem[i], 0); + lp5521_write(cl, rgb_mem[i] + 1, 0); + } +} + +static void lp5521_write_program_memory(struct i2c_client *cl, + u8 base, u8 *rgb, int size) +{ + int i; + + if (!rgb || size <= 0) + return; + + for (i = 0; i < size; i++) + lp5521_write(cl, base + i, *(rgb + i)); + + lp5521_write(cl, base + i, 0); + lp5521_write(cl, base + i + 1, 0); +} + +static inline struct lp5521_led_pattern *lp5521_get_pattern + (struct lp5521_chip *chip, u8 offset) +{ + struct lp5521_led_pattern *pattern; + pattern = chip->pdata->patterns + (offset - 1); + return pattern; +} + +static void lp5521_run_led_pattern(int mode, struct lp5521_chip *chip) +{ + struct lp5521_led_pattern *ptn; + struct i2c_client *cl = chip->client; + u8 num_patterns = chip->pdata->num_patterns; + + if (mode > num_patterns || !(chip->pdata->patterns)) + return; + + if (mode == PATTERN_OFF) { + lp5521_write(cl, LP5521_REG_ENABLE, LP5521_ENABLE_DEFAULT); + usleep_range(1000, 2000); + lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); + } else { + ptn = lp5521_get_pattern(chip, mode); + if (!ptn) + return; + + lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_LOAD); + usleep_range(1000, 2000); + + lp5521_clear_program_memory(cl); + + lp5521_write_program_memory(cl, LP5521_REG_R_PROG_MEM, + ptn->r, ptn->size_r); + lp5521_write_program_memory(cl, LP5521_REG_G_PROG_MEM, + ptn->g, ptn->size_g); + lp5521_write_program_memory(cl, LP5521_REG_B_PROG_MEM, + ptn->b, ptn->size_b); + + lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_RUN); + usleep_range(1000, 2000); + lp5521_write(cl, LP5521_REG_ENABLE, LP5521_ENABLE_RUN_PROGRAM); + } +} + +static ssize_t store_led_pattern(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = i2c_get_clientdata(to_i2c_client(dev)); + unsigned long val; + int ret; + + ret = strict_strtoul(buf, 16, &val); + if (ret) + return ret; + + lp5521_run_led_pattern(val, chip); + + return len; +} + +static ssize_t show_delay_on(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lp5521_led *led = cdev_to_led(led_cdev); + + return sprintf(buf, "%d\n", led->delay_on); +} + +static ssize_t store_delay_on(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lp5521_led *led = cdev_to_led(led_cdev); + unsigned long curr; + + if (strict_strtoul(buf, 0, &curr)) + return -EINVAL; + + led->delay_on = (int)curr; + + return len; +} + +static ssize_t show_delay_off(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lp5521_led *led = cdev_to_led(led_cdev); + + return sprintf(buf, "%d\n", led->delay_off); +} + +static ssize_t store_delay_off(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lp5521_led *led = cdev_to_led(led_cdev); + unsigned long curr; + + if (strict_strtoul(buf, 0, &curr)) + return -EINVAL; + + led->delay_off = (int)curr; + + return len; +} + +static ssize_t store_blink(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lp5521_led *led = cdev_to_led(led_cdev); + unsigned long is_blink_set; + unsigned long on = led->delay_on; + unsigned long off = led->delay_off; + + if (strict_strtoul(buf, 0, &is_blink_set)) + return -EINVAL; + + if (!is_blink_set) + on = ALWAYS_OFF; + + led_blink_set(led_cdev, &on, &off); + + return len; +} + /* led class device attributes */ static DEVICE_ATTR(led_current, S_IRUGO | S_IWUSR, show_current, store_current); static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL); +static DEVICE_ATTR(delay_on, S_IRUGO | S_IWUSR, show_delay_on, store_delay_on); +static DEVICE_ATTR(delay_off, S_IRUGO | S_IWUSR, show_delay_off, + store_delay_off); +static DEVICE_ATTR(blink, S_IWUSR, NULL, store_blink);
static struct attribute *lp5521_led_attributes[] = { &dev_attr_led_current.attr, &dev_attr_max_current.attr, + &dev_attr_delay_on.attr, + &dev_attr_delay_off.attr, + &dev_attr_blink.attr, NULL, };
@@ -558,6 +735,7 @@ static DEVICE_ATTR(engine1_load, S_IWUSR, NULL, store_engine1_load); static DEVICE_ATTR(engine2_load, S_IWUSR, NULL, store_engine2_load); static DEVICE_ATTR(engine3_load, S_IWUSR, NULL, store_engine3_load); static DEVICE_ATTR(selftest, S_IRUGO, lp5521_selftest, NULL); +static DEVICE_ATTR(led_pattern, S_IWUSR, NULL, store_led_pattern);
static struct attribute *lp5521_attributes[] = { &dev_attr_engine1_mode.attr, @@ -567,6 +745,7 @@ static struct attribute *lp5521_attributes[] = { &dev_attr_engine1_load.attr, &dev_attr_engine2_load.attr, &dev_attr_engine3_load.attr, + &dev_attr_led_pattern.attr, NULL };
@@ -617,10 +796,16 @@ static int __devinit lp5521_init_led(struct lp5521_led *led, return -EINVAL; }
- snprintf(name, sizeof(name), "%s:channel%d", - pdata->label ?: client->name, chan); led->cdev.brightness_set = lp5521_set_brightness; - led->cdev.name = name; + + if (pdata->led_config[chan].name) { + led->cdev.name = pdata->led_config[chan].name; + } else { + snprintf(name, sizeof(name), "%s:channel%d", + pdata->label ?: client->name, chan); + led->cdev.name = name; + } + res = led_classdev_register(dev, &led->cdev); if (res < 0) { dev_err(dev, "couldn't register led on channel %d\n", chan); @@ -749,6 +934,7 @@ static int lp5521_remove(struct i2c_client *client) struct lp5521_chip *chip = i2c_get_clientdata(client); int i;
+ lp5521_run_led_pattern(PATTERN_OFF, chip); lp5521_unregister_sysfs(client);
for (i = 0; i < chip->num_leds; i++) { diff --git a/include/linux/leds-lp5521.h b/include/linux/leds-lp5521.h index fd548d2..c842527 100644 --- a/include/linux/leds-lp5521.h +++ b/include/linux/leds-lp5521.h @@ -26,23 +26,48 @@ /* See Documentation/leds/leds-lp5521.txt */
struct lp5521_led_config { + char *name; u8 chan_nr; u8 led_current; /* mA x10, 0 if led is not connected */ u8 max_current; };
+struct lp5521_led_pattern { + u8 *r; + u8 *g; + u8 *b; + u8 size_r; + u8 size_g; + u8 size_b; +}; + #define LP5521_CLOCK_AUTO 0 #define LP5521_CLOCK_INT 1 #define LP5521_CLOCK_EXT 2
+/* Bits in CONFIG register */ +#define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */ +#define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */ +#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */ +#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */ +#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */ +#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */ +#define LP5521_R_TO_BATT 4 /* R out: 0 = CP, 1 = Vbat */ +#define LP5521_CLK_SRC_EXT 0 /* Ext-clk source (CLK_32K) */ +#define LP5521_CLK_INT 1 /* Internal clock */ +#define LP5521_CLK_AUTO 2 /* Automatic clock selection */ + struct lp5521_platform_data { struct lp5521_led_config *led_config; u8 num_channels; u8 clock_mode; + u8 update_config; int (*setup_resources)(void); void (*release_resources)(void); void (*enable)(bool state); const char *label; + struct lp5521_led_pattern *patterns; + u8 num_patterns; };
#endif /* __LINUX_LP5521_H */ -- 1.7.4.1
Best Regards
Milo (Woogyom) Kim Texas Instruments Incorporated