From 1bd465e6b0e2b559db47420fea686507a01cfab0 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Sat, 10 Jan 2009 18:54:39 +0000 Subject: leds: allow led-drivers to use a variable range of brightness values This patch allows drivers to override the default maximum brightness value of 255. We take care to preserve backwards-compatibility as much as possible, so that user-space ABI doesn't change for existing drivers. LED trigger code has also been updated to use the per-LED maximum. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Richard Purdie --- include/linux/leds.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include') diff --git a/include/linux/leds.h b/include/linux/leds.h index 24489da701e3..17d277e0c4a6 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -30,6 +30,7 @@ enum led_brightness { struct led_classdev { const char *name; int brightness; + int max_brightness; int flags; /* Lower 16 bits reflect status */ -- cgit v1.2.3 From 41c42ff5dbe29b7b826e6736f960959c76e7acf0 Mon Sep 17 00:00:00 2001 From: Luotao Fu Date: Wed, 11 Feb 2009 13:24:40 -0800 Subject: leds: simple driver for pwm driven LEDs Add a simple driver for pwm driver LEDs. pwm_id and period can be defined in board file. It is developed for pxa, however it is probably generic enough to be used on other platforms with pwm. Signed-off-by: Luotao Fu Signed-off-by: Andrew Morton Signed-off-by: Richard Purdie --- drivers/leds/Kconfig | 6 ++ drivers/leds/Makefile | 1 + drivers/leds/leds-pwm.c | 153 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/leds_pwm.h | 21 +++++++ 4 files changed, 181 insertions(+) create mode 100644 drivers/leds/leds-pwm.c create mode 100644 include/linux/leds_pwm.h (limited to 'include') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 16a94088beee..89ea7ef39fe4 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -197,6 +197,12 @@ config LEDS_DAC124S085 This option enables support for DAC124S085 SPI DAC from NatSemi, which can be used to control up to four LEDs. +config LEDS_PWM + tristate "PWM driven LED Support" + depends on LEDS_CLASS && HAVE_PWM + help + This option enables support for pwm driven LEDs + comment "LED Triggers" config LEDS_TRIGGERS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 4157e86b9747..584a3f6c2530 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_LEDS_FSG) += leds-fsg.o obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o +obj-$(CONFIG_LEDS_PWM) += leds-pwm.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c new file mode 100644 index 000000000000..cdfdc8714e10 --- /dev/null +++ b/drivers/leds/leds-pwm.c @@ -0,0 +1,153 @@ +/* + * linux/drivers/leds-pwm.c + * + * simple PWM based LED control + * + * Copyright 2009 Luotao Fu @ Pengutronix (l.fu@pengutronix.de) + * + * based on leds-gpio.c by Raphael Assenat + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +struct led_pwm_data { + struct led_classdev cdev; + struct pwm_device *pwm; + unsigned int active_low; + unsigned int period; + unsigned int max_brightness; +}; + +static void led_pwm_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_pwm_data *led_dat = + container_of(led_cdev, struct led_pwm_data, cdev); + unsigned int max = led_dat->max_brightness; + unsigned int period = led_dat->period; + + if (brightness == 0) { + pwm_config(led_dat->pwm, 0, period); + pwm_disable(led_dat->pwm); + } else { + pwm_config(led_dat->pwm, brightness * period / max, period); + pwm_enable(led_dat->pwm); + } +} + +static int led_pwm_probe(struct platform_device *pdev) +{ + struct led_pwm_platform_data *pdata = pdev->dev.platform_data; + struct led_pwm *cur_led; + struct led_pwm_data *leds_data, *led_dat; + int i, ret = 0; + + if (!pdata) + return -EBUSY; + + leds_data = kzalloc(sizeof(struct led_pwm_data) * pdata->num_leds, + GFP_KERNEL); + if (!leds_data) + return -ENOMEM; + + for (i = 0; i < pdata->num_leds; i++) { + cur_led = &pdata->leds[i]; + led_dat = &leds_data[i]; + + led_dat->pwm = pwm_request(cur_led->pwm_id, + cur_led->name); + if (IS_ERR(led_dat->pwm)) { + dev_err(&pdev->dev, "unable to request PWM %d\n", + cur_led->pwm_id); + goto err; + } + + led_dat->cdev.name = cur_led->name; + led_dat->cdev.default_trigger = cur_led->default_trigger; + led_dat->active_low = cur_led->active_low; + led_dat->max_brightness = cur_led->max_brightness; + led_dat->period = cur_led->pwm_period_ns; + led_dat->cdev.brightness_set = led_pwm_set; + led_dat->cdev.brightness = LED_OFF; + led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; + + ret = led_classdev_register(&pdev->dev, &led_dat->cdev); + if (ret < 0) { + pwm_free(led_dat->pwm); + goto err; + } + } + + platform_set_drvdata(pdev, leds_data); + + return 0; + +err: + if (i > 0) { + for (i = i - 1; i >= 0; i--) { + led_classdev_unregister(&leds_data[i].cdev); + pwm_free(leds_data[i].pwm); + } + } + + kfree(leds_data); + + return ret; +} + +static int __devexit led_pwm_remove(struct platform_device *pdev) +{ + int i; + struct led_pwm_platform_data *pdata = pdev->dev.platform_data; + struct led_pwm_data *leds_data; + + leds_data = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->num_leds; i++) { + led_classdev_unregister(&leds_data[i].cdev); + pwm_free(leds_data[i].pwm); + } + + kfree(leds_data); + + return 0; +} + +static struct platform_driver led_pwm_driver = { + .probe = led_pwm_probe, + .remove = __devexit_p(led_pwm_remove), + .driver = { + .name = "leds_pwm", + .owner = THIS_MODULE, + }, +}; + +static int __init led_pwm_init(void) +{ + return platform_driver_register(&led_pwm_driver); +} + +static void __exit led_pwm_exit(void) +{ + platform_driver_unregister(&led_pwm_driver); +} + +module_init(led_pwm_init); +module_exit(led_pwm_exit); + +MODULE_AUTHOR("Luotao Fu "); +MODULE_DESCRIPTION("PWM LED driver for PXA"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:leds-pwm"); diff --git a/include/linux/leds_pwm.h b/include/linux/leds_pwm.h new file mode 100644 index 000000000000..33a071167489 --- /dev/null +++ b/include/linux/leds_pwm.h @@ -0,0 +1,21 @@ +/* + * PWM LED driver data - see drivers/leds/leds-pwm.c + */ +#ifndef __LINUX_LEDS_PWM_H +#define __LINUX_LEDS_PWM_H + +struct led_pwm { + const char *name; + const char *default_trigger; + unsigned pwm_id; + u8 active_low; + unsigned max_brightness; + unsigned pwm_period_ns; +}; + +struct led_pwm_platform_data { + int num_leds; + struct led_pwm *leds; +}; + +#endif -- cgit v1.2.3 From defb512d2576992c63ba1c18c24eea31cfeaa26e Mon Sep 17 00:00:00 2001 From: Richard Purdie Date: Tue, 17 Feb 2009 15:04:07 +0000 Subject: leds: Add suspend/resume state flags to leds-gpio Add an option to preserve LED state when suspending/resuming to the LED gpio driver. Based on a suggestion from Robert Jarzmik. Tested-by: Robert Jarzmik Signed-off-by: Richard Purdie --- drivers/leds/leds-gpio.c | 3 ++- include/linux/leds.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 0daa2d21cbde..8fa352ac20f8 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -93,7 +93,8 @@ static int __devinit create_gpio_led(const struct gpio_led *template, } led_dat->cdev.brightness_set = gpio_led_set; led_dat->cdev.brightness = LED_OFF; - led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; + if (!template->retain_state_suspended) + led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; ret = gpio_direction_output(led_dat->gpio, led_dat->active_low); if (ret < 0) diff --git a/include/linux/leds.h b/include/linux/leds.h index 17d277e0c4a6..376fe07732ea 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -141,7 +141,8 @@ struct gpio_led { const char *name; const char *default_trigger; unsigned gpio; - u8 active_low; + u8 active_low : 1; + u8 retain_state_suspended : 1; }; struct gpio_led_platform_data { -- cgit v1.2.3 From 0b56129be72c38179697b7441aacbe133d515ff9 Mon Sep 17 00:00:00 2001 From: Kim Kyuwon Date: Wed, 4 Mar 2009 11:59:29 -0800 Subject: leds: add BD2802GU LED driver ROHM BD2802GU is a RGB LED controller attached to i2c bus and specifically engineered for decoration purposes. This RGB controller incorporates lighting patterns and illuminates. This driver is designed to minimize power consumption, so when there is no emitting LED, it enters to reset state. And because the BD2802GU has lots of features that can't be covered by the current LED framework, it provides Advanced Configuration Function(ADF) mode, so that user applications can set registers of BD2802GU directly. Here are basic usage examples : ; to turn on LED (not blink) $ echo 1 > /sys/class/leds/led1_R/brightness ; to blink LED $ echo timer > /sys/class/leds/led1_R/trigger $ echo 1 > /sys/class/leds/led1_R/delay_on $ echo 1 > /sys/class/leds/led1_R/delay_off ; to turn off LED $ echo 0 > /sys/class/leds/led1_R/brightness [akpm@linux-foundation.org: coding-style fixes] Signed-off-by: Kim Kyuwon Signed-off-by: Andrew Morton Signed-off-by: Richard Purdie --- drivers/leds/Kconfig | 7 + drivers/leds/Makefile | 1 + drivers/leds/leds-bd2802.c | 765 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/leds-bd2802.h | 26 ++ 4 files changed, 799 insertions(+) create mode 100644 drivers/leds/leds-bd2802.c create mode 100644 include/linux/leds-bd2802.h (limited to 'include') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index db84d8f616dc..b77baecd50ca 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -210,6 +210,13 @@ config LEDS_PWM help This option enables support for pwm driven LEDs +config LEDS_BD2802 + tristate "LED driver for BD2802 RGB LED" + depends on LEDS_CLASS && I2C + help + This option enables support for BD2802GU RGB LED driver chips + accessed via the I2C bus. + comment "LED Triggers" config LEDS_TRIGGERS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 291aea22bf0c..2d41c4dcf92f 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o # LED Platform Drivers obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o +obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o diff --git a/drivers/leds/leds-bd2802.c b/drivers/leds/leds-bd2802.c new file mode 100644 index 000000000000..4149ecb3a9b2 --- /dev/null +++ b/drivers/leds/leds-bd2802.c @@ -0,0 +1,765 @@ +/* + * leds-bd2802.c - RGB LED Driver + * + * Copyright (C) 2009 Samsung Electronics + * Kim Kyuwon + * + * 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. + * + * Datasheet: http://www.rohm.com/products/databook/driver/pdf/bd2802gu-e.pdf + * + */ + +#include +#include +#include +#include +#include +#include + + +#define LED_CTL(rgb2en, rgb1en) ((rgb2en) << 4 | ((rgb1en) << 0)) + +#define BD2802_LED_OFFSET 0xa +#define BD2802_COLOR_OFFSET 0x3 + +#define BD2802_REG_CLKSETUP 0x00 +#define BD2802_REG_CONTROL 0x01 +#define BD2802_REG_HOURSETUP 0x02 +#define BD2802_REG_CURRENT1SETUP 0x03 +#define BD2802_REG_CURRENT2SETUP 0x04 +#define BD2802_REG_WAVEPATTERN 0x05 + +#define BD2802_CURRENT_032 0x10 /* 3.2mA */ +#define BD2802_CURRENT_000 0x00 /* 0.0mA */ + +#define BD2802_PATTERN_FULL 0x07 +#define BD2802_PATTERN_HALF 0x03 + +enum led_ids { + LED1, + LED2, + LED_NUM, +}; + +enum led_colors { + RED, + GREEN, + BLUE, +}; + +enum led_bits { + BD2802_OFF, + BD2802_BLINK, + BD2802_ON, +}; + +/* + * State '0' : 'off' + * State '1' : 'blink' + * State '2' : 'on'. + */ +struct led_state { + unsigned r:2; + unsigned g:2; + unsigned b:2; +}; + +struct bd2802_led { + struct bd2802_led_platform_data *pdata; + struct i2c_client *client; + struct rw_semaphore rwsem; + struct work_struct work; + + struct led_state led[2]; + + /* + * Making led_classdev as array is not recommended, because array + * members prevent using 'container_of' macro. So repetitive works + * are needed. + */ + struct led_classdev cdev_led1r; + struct led_classdev cdev_led1g; + struct led_classdev cdev_led1b; + struct led_classdev cdev_led2r; + struct led_classdev cdev_led2g; + struct led_classdev cdev_led2b; + + /* + * Advanced Configuration Function(ADF) mode: + * In ADF mode, user can set registers of BD2802GU directly, + * therefore BD2802GU doesn't enter reset state. + */ + int adf_on; + + enum led_ids led_id; + enum led_colors color; + enum led_bits state; +}; + + +/*--------------------------------------------------------------*/ +/* BD2802GU helper functions */ +/*--------------------------------------------------------------*/ + +static inline int bd2802_is_rgb_off(struct bd2802_led *led, enum led_ids id, + enum led_colors color) +{ + switch (color) { + case RED: + return !led->led[id].r; + case GREEN: + return !led->led[id].g; + case BLUE: + return !led->led[id].b; + default: + dev_err(&led->client->dev, "%s: Invalid color\n", __func__); + return -EINVAL; + } +} + +static inline int bd2802_is_led_off(struct bd2802_led *led, enum led_ids id) +{ + if (led->led[id].r || led->led[id].g || led->led[id].b) + return 0; + + return 1; +} + +static inline int bd2802_is_all_off(struct bd2802_led *led) +{ + int i; + + for (i = 0; i < LED_NUM; i++) + if (!bd2802_is_led_off(led, i)) + return 0; + + return 1; +} + +static inline u8 bd2802_get_base_offset(enum led_ids id, enum led_colors color) +{ + return id * BD2802_LED_OFFSET + color * BD2802_COLOR_OFFSET; +} + +static inline u8 bd2802_get_reg_addr(enum led_ids id, enum led_colors color, + u8 reg_offset) +{ + return reg_offset + bd2802_get_base_offset(id, color); +} + + +/*--------------------------------------------------------------*/ +/* BD2802GU core functions */ +/*--------------------------------------------------------------*/ + +static int bd2802_write_byte(struct i2c_client *client, u8 reg, u8 val) +{ + int ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret >= 0) + return 0; + + dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", + __func__, reg, val, ret); + + return ret; +} + +static void bd2802_update_state(struct bd2802_led *led, enum led_ids id, + enum led_colors color, enum led_bits led_bit) +{ + int i; + u8 value; + + for (i = 0; i < LED_NUM; i++) { + if (i == id) { + switch (color) { + case RED: + led->led[i].r = led_bit; + break; + case GREEN: + led->led[i].g = led_bit; + break; + case BLUE: + led->led[i].b = led_bit; + break; + default: + dev_err(&led->client->dev, + "%s: Invalid color\n", __func__); + return; + } + } + } + + if (led_bit == BD2802_BLINK || led_bit == BD2802_ON) + return; + + if (!bd2802_is_led_off(led, id)) + return; + + if (bd2802_is_all_off(led) && !led->adf_on) { + gpio_set_value(led->pdata->reset_gpio, 0); + return; + } + + /* + * In this case, other led is turned on, and current led is turned + * off. So set RGB LED Control register to stop the current RGB LED + */ + value = (id == LED1) ? LED_CTL(1, 0) : LED_CTL(0, 1); + bd2802_write_byte(led->client, BD2802_REG_CONTROL, value); +} + +static void bd2802_configure(struct bd2802_led *led) +{ + struct bd2802_led_platform_data *pdata = led->pdata; + u8 reg; + + reg = bd2802_get_reg_addr(LED1, RED, BD2802_REG_HOURSETUP); + bd2802_write_byte(led->client, reg, pdata->rgb_time); + + reg = bd2802_get_reg_addr(LED2, RED, BD2802_REG_HOURSETUP); + bd2802_write_byte(led->client, reg, pdata->rgb_time); +} + +static void bd2802_reset_cancel(struct bd2802_led *led) +{ + gpio_set_value(led->pdata->reset_gpio, 1); + udelay(100); + bd2802_configure(led); +} + +static void bd2802_enable(struct bd2802_led *led, enum led_ids id) +{ + enum led_ids other_led = (id == LED1) ? LED2 : LED1; + u8 value, other_led_on; + + other_led_on = !bd2802_is_led_off(led, other_led); + if (id == LED1) + value = LED_CTL(other_led_on, 1); + else + value = LED_CTL(1 , other_led_on); + + bd2802_write_byte(led->client, BD2802_REG_CONTROL, value); +} + +static void bd2802_set_on(struct bd2802_led *led, enum led_ids id, + enum led_colors color) +{ + u8 reg; + + if (bd2802_is_all_off(led) && !led->adf_on) + bd2802_reset_cancel(led); + + reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT1SETUP); + bd2802_write_byte(led->client, reg, BD2802_CURRENT_032); + reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT2SETUP); + bd2802_write_byte(led->client, reg, BD2802_CURRENT_000); + reg = bd2802_get_reg_addr(id, color, BD2802_REG_WAVEPATTERN); + bd2802_write_byte(led->client, reg, BD2802_PATTERN_FULL); + + bd2802_enable(led, id); + bd2802_update_state(led, id, color, BD2802_ON); +} + +static void bd2802_set_blink(struct bd2802_led *led, enum led_ids id, + enum led_colors color) +{ + u8 reg; + + if (bd2802_is_all_off(led) && !led->adf_on) + bd2802_reset_cancel(led); + + reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT1SETUP); + bd2802_write_byte(led->client, reg, BD2802_CURRENT_000); + reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT2SETUP); + bd2802_write_byte(led->client, reg, BD2802_CURRENT_032); + reg = bd2802_get_reg_addr(id, color, BD2802_REG_WAVEPATTERN); + bd2802_write_byte(led->client, reg, BD2802_PATTERN_HALF); + + bd2802_enable(led, id); + bd2802_update_state(led, id, color, BD2802_BLINK); +} + +static void bd2802_turn_on(struct bd2802_led *led, enum led_ids id, + enum led_colors color, enum led_bits led_bit) +{ + if (led_bit == BD2802_OFF) { + dev_err(&led->client->dev, + "Only 'blink' and 'on' are allowed\n"); + return; + } + + if (led_bit == BD2802_BLINK) + bd2802_set_blink(led, id, color); + else + bd2802_set_on(led, id, color); +} + +static void bd2802_turn_off(struct bd2802_led *led, enum led_ids id, + enum led_colors color) +{ + u8 reg; + + if (bd2802_is_rgb_off(led, id, color)) + return; + + reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT1SETUP); + bd2802_write_byte(led->client, reg, BD2802_CURRENT_000); + reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT2SETUP); + bd2802_write_byte(led->client, reg, BD2802_CURRENT_000); + + bd2802_update_state(led, id, color, BD2802_OFF); +} + +static void bd2802_restore_state(struct bd2802_led *led) +{ + int i; + + for (i = 0; i < LED_NUM; i++) { + if (led->led[i].r) + bd2802_turn_on(led, i, RED, led->led[i].r); + if (led->led[i].g) + bd2802_turn_on(led, i, GREEN, led->led[i].g); + if (led->led[i].b) + bd2802_turn_on(led, i, BLUE, led->led[i].b); + } +} + +#define BD2802_SET_REGISTER(reg_addr, reg_name) \ +static ssize_t bd2802_store_reg##reg_addr(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + struct bd2802_led *led = i2c_get_clientdata(to_i2c_client(dev));\ + unsigned long val; \ + int ret; \ + if (!count) \ + return -EINVAL; \ + ret = strict_strtoul(buf, 16, &val); \ + if (ret) \ + return ret; \ + down_write(&led->rwsem); \ + bd2802_write_byte(led->client, reg_addr, (u8) val); \ + up_write(&led->rwsem); \ + return count; \ +} \ +static struct device_attribute bd2802_reg##reg_addr##_attr = { \ + .attr = {.name = reg_name, .mode = 0644, .owner = THIS_MODULE}, \ + .store = bd2802_store_reg##reg_addr, \ +}; + +BD2802_SET_REGISTER(0x00, "0x00"); +BD2802_SET_REGISTER(0x01, "0x01"); +BD2802_SET_REGISTER(0x02, "0x02"); +BD2802_SET_REGISTER(0x03, "0x03"); +BD2802_SET_REGISTER(0x04, "0x04"); +BD2802_SET_REGISTER(0x05, "0x05"); +BD2802_SET_REGISTER(0x06, "0x06"); +BD2802_SET_REGISTER(0x07, "0x07"); +BD2802_SET_REGISTER(0x08, "0x08"); +BD2802_SET_REGISTER(0x09, "0x09"); +BD2802_SET_REGISTER(0x0a, "0x0a"); +BD2802_SET_REGISTER(0x0b, "0x0b"); +BD2802_SET_REGISTER(0x0c, "0x0c"); +BD2802_SET_REGISTER(0x0d, "0x0d"); +BD2802_SET_REGISTER(0x0e, "0x0e"); +BD2802_SET_REGISTER(0x0f, "0x0f"); +BD2802_SET_REGISTER(0x10, "0x10"); +BD2802_SET_REGISTER(0x11, "0x11"); +BD2802_SET_REGISTER(0x12, "0x12"); +BD2802_SET_REGISTER(0x13, "0x13"); +BD2802_SET_REGISTER(0x14, "0x14"); +BD2802_SET_REGISTER(0x15, "0x15"); + +static struct device_attribute *bd2802_addr_attributes[] = { + &bd2802_reg0x00_attr, + &bd2802_reg0x01_attr, + &bd2802_reg0x02_attr, + &bd2802_reg0x03_attr, + &bd2802_reg0x04_attr, + &bd2802_reg0x05_attr, + &bd2802_reg0x06_attr, + &bd2802_reg0x07_attr, + &bd2802_reg0x08_attr, + &bd2802_reg0x09_attr, + &bd2802_reg0x0a_attr, + &bd2802_reg0x0b_attr, + &bd2802_reg0x0c_attr, + &bd2802_reg0x0d_attr, + &bd2802_reg0x0e_attr, + &bd2802_reg0x0f_attr, + &bd2802_reg0x10_attr, + &bd2802_reg0x11_attr, + &bd2802_reg0x12_attr, + &bd2802_reg0x13_attr, + &bd2802_reg0x14_attr, + &bd2802_reg0x15_attr, +}; + +static void bd2802_enable_adv_conf(struct bd2802_led *led) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(bd2802_addr_attributes); i++) { + ret = device_create_file(&led->client->dev, + bd2802_addr_attributes[i]); + if (ret) { + dev_err(&led->client->dev, "failed to sysfs file %s\n", + bd2802_addr_attributes[i]->attr.name); + goto failed_remove_files; + } + } + + if (bd2802_is_all_off(led)) + bd2802_reset_cancel(led); + + led->adf_on = 1; + + return; + +failed_remove_files: + for (i--; i >= 0; i--) + device_remove_file(&led->client->dev, + bd2802_addr_attributes[i]); +} + +static void bd2802_disable_adv_conf(struct bd2802_led *led) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bd2802_addr_attributes); i++) + device_remove_file(&led->client->dev, + bd2802_addr_attributes[i]); + + if (bd2802_is_all_off(led)) + gpio_set_value(led->pdata->reset_gpio, 0); + + led->adf_on = 0; +} + +static ssize_t bd2802_show_adv_conf(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bd2802_led *led = i2c_get_clientdata(to_i2c_client(dev)); + ssize_t ret; + + down_read(&led->rwsem); + if (led->adf_on) + ret = sprintf(buf, "on\n"); + else + ret = sprintf(buf, "off\n"); + up_read(&led->rwsem); + + return ret; +} + +static ssize_t bd2802_store_adv_conf(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct bd2802_led *led = i2c_get_clientdata(to_i2c_client(dev)); + + if (!count) + return -EINVAL; + + down_write(&led->rwsem); + if (!led->adf_on && !strncmp(buf, "on", 2)) + bd2802_enable_adv_conf(led); + else if (led->adf_on && !strncmp(buf, "off", 3)) + bd2802_disable_adv_conf(led); + up_write(&led->rwsem); + + return count; +} + +static struct device_attribute bd2802_adv_conf_attr = { + .attr = { + .name = "advanced_configuration", + .mode = 0644, + .owner = THIS_MODULE + }, + .show = bd2802_show_adv_conf, + .store = bd2802_store_adv_conf, +}; + +static void bd2802_led_work(struct work_struct *work) +{ + struct bd2802_led *led = container_of(work, struct bd2802_led, work); + + if (led->state) + bd2802_turn_on(led, led->led_id, led->color, led->state); + else + bd2802_turn_off(led, led->led_id, led->color); +} + +#define BD2802_CONTROL_RGBS(name, id, clr) \ +static void bd2802_set_##name##_brightness(struct led_classdev *led_cdev,\ + enum led_brightness value) \ +{ \ + struct bd2802_led *led = \ + container_of(led_cdev, struct bd2802_led, cdev_##name); \ + led->led_id = id; \ + led->color = clr; \ + if (value == LED_OFF) \ + led->state = BD2802_OFF; \ + else \ + led->state = BD2802_ON; \ + schedule_work(&led->work); \ +} \ +static int bd2802_set_##name##_blink(struct led_classdev *led_cdev, \ + unsigned long *delay_on, unsigned long *delay_off) \ +{ \ + struct bd2802_led *led = \ + container_of(led_cdev, struct bd2802_led, cdev_##name); \ + if (*delay_on == 0 || *delay_off == 0) \ + return -EINVAL; \ + led->led_id = id; \ + led->color = clr; \ + led->state = BD2802_BLINK; \ + schedule_work(&led->work); \ + return 0; \ +} + +BD2802_CONTROL_RGBS(led1r, LED1, RED); +BD2802_CONTROL_RGBS(led1g, LED1, GREEN); +BD2802_CONTROL_RGBS(led1b, LED1, BLUE); +BD2802_CONTROL_RGBS(led2r, LED2, RED); +BD2802_CONTROL_RGBS(led2g, LED2, GREEN); +BD2802_CONTROL_RGBS(led2b, LED2, BLUE); + +static int bd2802_register_led_classdev(struct bd2802_led *led) +{ + int ret; + + INIT_WORK(&led->work, bd2802_led_work); + + led->cdev_led1r.name = "led1_R"; + led->cdev_led1r.brightness = LED_OFF; + led->cdev_led1r.brightness_set = bd2802_set_led1r_brightness; + led->cdev_led1r.blink_set = bd2802_set_led1r_blink; + led->cdev_led1r.flags |= LED_CORE_SUSPENDRESUME; + + ret = led_classdev_register(&led->client->dev, &led->cdev_led1r); + if (ret < 0) { + dev_err(&led->client->dev, "couldn't register LED %s\n", + led->cdev_led1r.name); + goto failed_unregister_led1_R; + } + + led->cdev_led1g.name = "led1_G"; + led->cdev_led1g.brightness = LED_OFF; + led->cdev_led1g.brightness_set = bd2802_set_led1g_brightness; + led->cdev_led1g.blink_set = bd2802_set_led1g_blink; + led->cdev_led1g.flags |= LED_CORE_SUSPENDRESUME; + + ret = led_classdev_register(&led->client->dev, &led->cdev_led1g); + if (ret < 0) { + dev_err(&led->client->dev, "couldn't register LED %s\n", + led->cdev_led1g.name); + goto failed_unregister_led1_G; + } + + led->cdev_led1b.name = "led1_B"; + led->cdev_led1b.brightness = LED_OFF; + led->cdev_led1b.brightness_set = bd2802_set_led1b_brightness; + led->cdev_led1b.blink_set = bd2802_set_led1b_blink; + led->cdev_led1b.flags |= LED_CORE_SUSPENDRESUME; + + ret = led_classdev_register(&led->client->dev, &led->cdev_led1b); + if (ret < 0) { + dev_err(&led->client->dev, "couldn't register LED %s\n", + led->cdev_led1b.name); + goto failed_unregister_led1_B; + } + + led->cdev_led2r.name = "led2_R"; + led->cdev_led2r.brightness = LED_OFF; + led->cdev_led2r.brightness_set = bd2802_set_led2r_brightness; + led->cdev_led2r.blink_set = bd2802_set_led2r_blink; + led->cdev_led2r.flags |= LED_CORE_SUSPENDRESUME; + + ret = led_classdev_register(&led->client->dev, &led->cdev_led2r); + if (ret < 0) { + dev_err(&led->client->dev, "couldn't register LED %s\n", + led->cdev_led2r.name); + goto failed_unregister_led2_R; + } + + led->cdev_led2g.name = "led2_G"; + led->cdev_led2g.brightness = LED_OFF; + led->cdev_led2g.brightness_set = bd2802_set_led2g_brightness; + led->cdev_led2g.blink_set = bd2802_set_led2g_blink; + led->cdev_led2g.flags |= LED_CORE_SUSPENDRESUME; + + ret = led_classdev_register(&led->client->dev, &led->cdev_led2g); + if (ret < 0) { + dev_err(&led->client->dev, "couldn't register LED %s\n", + led->cdev_led2g.name); + goto failed_unregister_led2_G; + } + + led->cdev_led2b.name = "led2_B"; + led->cdev_led2b.brightness = LED_OFF; + led->cdev_led2b.brightness_set = bd2802_set_led2b_brightness; + led->cdev_led2b.blink_set = bd2802_set_led2b_blink; + led->cdev_led2b.flags |= LED_CORE_SUSPENDRESUME; + + ret = led_classdev_register(&led->client->dev, &led->cdev_led2b); + if (ret < 0) { + dev_err(&led->client->dev, "couldn't register LED %s\n", + led->cdev_led2b.name); + goto failed_unregister_led2_B; + } + + return 0; + +failed_unregister_led2_B: + led_classdev_unregister(&led->cdev_led2g); +failed_unregister_led2_G: + led_classdev_unregister(&led->cdev_led2r); +failed_unregister_led2_R: + led_classdev_unregister(&led->cdev_led1b); +failed_unregister_led1_B: + led_classdev_unregister(&led->cdev_led1g); +failed_unregister_led1_G: + led_classdev_unregister(&led->cdev_led1r); +failed_unregister_led1_R: + + return ret; +} + +static void bd2802_unregister_led_classdev(struct bd2802_led *led) +{ + cancel_work_sync(&led->work); + led_classdev_unregister(&led->cdev_led1r); +} + +static int __devinit bd2802_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bd2802_led *led; + struct bd2802_led_platform_data *pdata; + int ret; + + led = kzalloc(sizeof(struct bd2802_led), GFP_KERNEL); + if (!led) { + dev_err(&client->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + led->client = client; + pdata = led->pdata = client->dev.platform_data; + i2c_set_clientdata(client, led); + + /* Configure RESET GPIO (L: RESET, H: RESET cancel) */ + gpio_request(pdata->reset_gpio, "RGB_RESETB"); + gpio_direction_output(pdata->reset_gpio, 1); + + /* Tacss = min 0.1ms */ + udelay(100); + + /* Detect BD2802GU */ + ret = bd2802_write_byte(client, BD2802_REG_CLKSETUP, 0x00); + if (ret < 0) { + dev_err(&client->dev, "failed to detect device\n"); + goto failed_free; + } else + dev_info(&client->dev, "return 0x%02x\n", ret); + + /* To save the power, reset BD2802 after detecting */ + gpio_set_value(led->pdata->reset_gpio, 0); + + init_rwsem(&led->rwsem); + + ret = device_create_file(&client->dev, &bd2802_adv_conf_attr); + if (ret) { + dev_err(&client->dev, "failed to create sysfs file %s\n", + bd2802_adv_conf_attr.attr.name); + goto failed_free; + } + + ret = bd2802_register_led_classdev(led); + if (ret < 0) + goto failed_unregister_dev_file; + + return 0; + +failed_unregister_dev_file: + device_remove_file(&client->dev, &bd2802_adv_conf_attr); +failed_free: + i2c_set_clientdata(client, NULL); + kfree(led); + + return ret; +} + +static int __exit bd2802_remove(struct i2c_client *client) +{ + struct bd2802_led *led = i2c_get_clientdata(client); + + bd2802_unregister_led_classdev(led); + gpio_set_value(led->pdata->reset_gpio, 0); + if (led->adf_on) + bd2802_disable_adv_conf(led); + device_remove_file(&client->dev, &bd2802_adv_conf_attr); + i2c_set_clientdata(client, NULL); + kfree(led); + + return 0; +} + +static int bd2802_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct bd2802_led *led = i2c_get_clientdata(client); + + gpio_set_value(led->pdata->reset_gpio, 0); + + return 0; +} + +static int bd2802_resume(struct i2c_client *client) +{ + struct bd2802_led *led = i2c_get_clientdata(client); + + if (!bd2802_is_all_off(led) || led->adf_on) { + gpio_set_value(led->pdata->reset_gpio, 1); + udelay(100); + bd2802_restore_state(led); + } + + return 0; +} + +static const struct i2c_device_id bd2802_id[] = { + { "BD2802", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bd2802_id); + +static struct i2c_driver bd2802_i2c_driver = { + .driver = { + .name = "BD2802", + }, + .probe = bd2802_probe, + .remove = __exit_p(bd2802_remove), + .suspend = bd2802_suspend, + .resume = bd2802_resume, + .id_table = bd2802_id, +}; + +static int __init bd2802_init(void) +{ + return i2c_add_driver(&bd2802_i2c_driver); +} +module_init(bd2802_init); + +static void __exit bd2802_exit(void) +{ + i2c_del_driver(&bd2802_i2c_driver); +} +module_exit(bd2802_exit); + +MODULE_AUTHOR("Kim Kyuwon "); +MODULE_DESCRIPTION("BD2802 LED driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/leds-bd2802.h b/include/linux/leds-bd2802.h new file mode 100644 index 000000000000..42f854a1a199 --- /dev/null +++ b/include/linux/leds-bd2802.h @@ -0,0 +1,26 @@ +/* + * leds-bd2802.h - RGB LED Driver + * + * Copyright (C) 2009 Samsung Electronics + * Kim Kyuwon + * + * 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. + * + * Datasheet: http://www.rohm.com/products/databook/driver/pdf/bd2802gu-e.pdf + * + */ +#ifndef _LEDS_BD2802_H_ +#define _LEDS_BD2802_H_ + +struct bd2802_led_platform_data{ + int reset_gpio; + u8 rgb_time; +}; + +#define RGB_TIME(slopedown, slopeup, waveform) \ + ((slopedown) << 6 | (slopeup) << 4 | (waveform)) + +#endif /* _LEDS_BD2802_H_ */ + -- cgit v1.2.3