diff options
author | Benn Pörscke <benn.porscke@stericsson.com> | 2011-10-07 15:31:57 +0200 |
---|---|---|
committer | Benn Pörscke <benn.porscke@stericsson.com> | 2011-10-07 15:31:57 +0200 |
commit | 47a4dbf83a75014d6b3467be18997894f1c617db (patch) | |
tree | 7f5d116db48205309fbc4ae0954f20ab8a651e46 /drivers/leds | |
parent | ea8a52f9f4bcc3420c38ae07f8378a2f18443970 (diff) |
Squashandroid-20111012
Change-Id: If0ae9fa8067740ab2ede33703c79ec134f204a5e
Diffstat (limited to 'drivers/leds')
-rw-r--r-- | drivers/leds/Kconfig | 33 | ||||
-rw-r--r-- | drivers/leds/Makefile | 4 | ||||
-rw-r--r-- | drivers/leds/leds-ab5500.c | 432 | ||||
-rw-r--r-- | drivers/leds/leds-lm3530.c | 422 | ||||
-rw-r--r-- | drivers/leds/leds-lp5521.c | 751 | ||||
-rw-r--r-- | drivers/leds/leds-pwm.c | 8 | ||||
-rw-r--r-- | drivers/leds/ledtrig-sleep.c | 80 |
7 files changed, 1729 insertions, 1 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 81bf25e67ce..8a7ac599a8d 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -33,6 +33,23 @@ config LEDS_ATMEL_PWM This option enables support for LEDs driven using outputs of the dedicated PWM controller found on newer Atmel SOCs. +config LEDS_LM3530 + tristate "LCD Backlight driver for LM3530" + depends on I2C + help + This option enables support for the LCD backlight using + LM3530 ambient light sensor chip. This ALS chip can be + controlled manually or using PWM input or using ambient + light automatically. + +config LEDS_AB5500 + tristate "HVLED driver for AB5500" + depends on AB5500_CORE + help + This option enables support for the HVLED in AB5500 + multi function device. Currently Ab5500 v1.0 chip leds + are supported. + config LEDS_LOCOMO tristate "LED Support for Locomo device" depends on SHARP_LOCOMO @@ -295,6 +312,16 @@ config LEDS_DELL_NETBOOKS This adds support for the Latitude 2100 and similar notebooks that have an external LED. +config LEDS_LP5521 + tristate "LED Support for the LP5521 LEDs" + depends on LEDS_CLASS && I2C + help + If you say 'Y' here you get support for the National Semiconductor + LP5521 LED driver used in n8x0 boards. + + This driver can be built as a module by choosing 'M'. The module + will be called leds-lp5521. + config LEDS_MC13783 tristate "LED Support for MC13783 PMIC" depends on MFD_MC13783 @@ -364,6 +391,12 @@ config LEDS_TRIGGER_DEFAULT_ON This allows LEDs to be initialised in the ON state. If unsure, say Y. +config LEDS_TRIGGER_SLEEP + tristate "LED Sleep Mode Trigger" + depends on LEDS_TRIGGERS && HAS_EARLYSUSPEND + help + This turns LEDs on when the screen is off but the cpu still running. + comment "iptables trigger is under Netfilter config (LED target)" depends on LEDS_TRIGGERS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 2493de49937..a10b9b3a8dd 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -9,6 +9,8 @@ obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o 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_LM3530) += leds-lm3530.o +obj-$(CONFIG_LEDS_AB5500) += leds-ab5500.o obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o obj-$(CONFIG_LEDS_AMS_DELTA) += leds-ams-delta.o @@ -23,6 +25,7 @@ obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o +obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o obj-$(CONFIG_LEDS_FSG) += leds-fsg.o @@ -48,3 +51,4 @@ obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o +obj-$(CONFIG_LEDS_TRIGGER_SLEEP) += ledtrig-sleep.o diff --git a/drivers/leds/leds-ab5500.c b/drivers/leds/leds-ab5500.c new file mode 100644 index 00000000000..94db3a6feea --- /dev/null +++ b/drivers/leds/leds-ab5500.c @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2011 ST-Ericsson SA. + * + * License Terms: GNU General Public License v2 + * + * Driver for LED in ST-Ericsson AB5500 v1.0 Analog baseband Controller + * + * Author: Shreshtha Kumar SAHU <shreshthakumar.sahu@stericsson.com> + */ + +#include <linux/leds.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/leds-ab5500.h> +#include <linux/types.h> + +#define AB5500LED_NAME "ab5500-leds" + +/* Register offsets */ +#define AB5500_LED_REG_ENABLE 0x03 +#define AB5500_LED_FADE_CTL 0x0D + +/* LED-0 */ +#define AB5500_LED0_PWM_DUTY 0x01 +#define AB5500_LED0_PWMFREQ 0x02 +#define AB5500_LED0_SINKCTL 0x0A + +/* LED-1 */ +#define AB5500_LED1_PWM_DUTY 0x05 +#define AB5500_LED1_PWMFREQ 0x06 +#define AB5500_LED1_SINKCTL 0x0B + +/* LED-2 */ +#define AB5500_LED2_PWM_DUTY 0x08 +#define AB5500_LED2_PWMFREQ 0x09 +#define AB5500_LED2_SINKCTL 0x0C + +/* pwm duty cycle */ +#define AB5500_LED_PWMDUTY_OFF 0x0 +#define AB5500_LED_PWMDUTY_MAX 0x3FF +#define AB5500_LED_PWMDUTY_STEP (AB5500_LED_PWMDUTY_MAX/LED_FULL) + +/* pwm frequency */ +#define AB5500_LED_PWMFREQ_MAX 0x0F /* 373.39 @sysclk=26MHz */ +#define AB5500_LED_PWMFREQ_SHIFT 4 + +/* LED sink current control */ +#define AB5500_LED_SINKCURR_MAX 0x0F /* 40mA */ +#define AB5500_LED_SINKCURR_SHIFT 4 + +struct ab5500_led { + u8 id; + u8 max_current; + u16 brt_val; + enum ab5500_led_status status; + struct led_classdev led_cdev; + struct work_struct led_work; +}; + +struct ab5500_hvleds { + struct mutex lock; + struct device *dev; + struct ab5500_hvleds_platform_data *pdata; + struct ab5500_led leds[AB5500_HVLEDS_MAX]; +}; + +static u8 ab5500_led_pwmduty_reg[] = { + AB5500_LED0_PWM_DUTY, + AB5500_LED1_PWM_DUTY, + AB5500_LED2_PWM_DUTY, +}; + +static u8 ab5500_led_pwmfreq_reg[] = { + AB5500_LED0_PWMFREQ, + AB5500_LED1_PWMFREQ, + AB5500_LED2_PWMFREQ, +}; + +static u8 ab5500_led_sinkctl_reg[] = { + AB5500_LED0_SINKCTL, + AB5500_LED1_SINKCTL, + AB5500_LED2_SINKCTL +}; + +#define to_led(_x) container_of(_x, struct ab5500_led, _x) + +static inline struct ab5500_hvleds *led_to_hvleds(struct ab5500_led *led) +{ + return container_of(led, struct ab5500_hvleds, leds[led->id]); +} + +static int ab5500_led_pwmduty_write(struct ab5500_hvleds *hvleds, + unsigned int led_id, u16 val) +{ + int ret; + int val_lsb = val & 0xFF; + int val_msb = (val & 0x300) >> 8; + + mutex_lock(&hvleds->lock); + + dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val = %d\n" + "reg[%d] w val = %d\n", + ab5500_led_pwmduty_reg[led_id] - 1, val_lsb, + ab5500_led_pwmduty_reg[led_id], val_msb); + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id] - 1, val_lsb); + ret |= abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id], val_msb); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_pwmduty_reg[led_id], ret); + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_pwmfreq_write(struct ab5500_hvleds *hvleds, + unsigned int led_id, u8 val) +{ + int ret; + + val = (val & 0x0F) << AB5500_LED_PWMFREQ_SHIFT; + + mutex_lock(&hvleds->lock); + + dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val=%d\n", + ab5500_led_pwmfreq_reg[led_id], val); + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmfreq_reg[led_id], val); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_pwmfreq_reg[led_id], ret); + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_sinkctl_write(struct ab5500_hvleds *hvleds, + unsigned int led_id, u8 val) +{ + int ret; + + val = (val & 0x0F) << AB5500_LED_SINKCURR_SHIFT; + + mutex_lock(&hvleds->lock); + + dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val=%d\n", + ab5500_led_sinkctl_reg[led_id], val); + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_sinkctl_reg[led_id], val); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_sinkctl_reg[led_id], ret); + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_sinkctl_read(struct ab5500_hvleds *hvleds, + unsigned int led_id) +{ + int ret; + u8 val; + + mutex_lock(&hvleds->lock); + ret = abx500_get_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_sinkctl_reg[led_id], &val); + if (ret < 0) { + dev_err(hvleds->dev, "reg[%d] r failed: %d\n", + ab5500_led_sinkctl_reg[led_id], ret); + mutex_unlock(&hvleds->lock); + return ret; + } + val = (val & 0xF0) >> AB5500_LED_SINKCURR_SHIFT; + mutex_unlock(&hvleds->lock); + + return val; +} + +static void ab5500_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brt_val) +{ + struct ab5500_led *led = to_led(led_cdev); + + /* adjust LED_FULL to 10bit range */ + brt_val &= LED_FULL; + led->brt_val = brt_val * AB5500_LED_PWMDUTY_STEP; + schedule_work(&led->led_work); +} + +static void ab5500_led_work(struct work_struct *led_work) +{ + struct ab5500_led *led = to_led(led_work); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + if (led->status == AB5500_LED_ON) + ab5500_led_pwmduty_write(hvleds, led->id, led->brt_val); +} + +static ssize_t ab5500_led_show_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int led_curr = 0; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ab5500_led *led = to_led(led_cdev); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + led_curr = ab5500_led_sinkctl_read(hvleds, led->id); + + if (led_curr < 0) + return led_curr; + + return sprintf(buf, "%d\n", led_curr); +} + +static ssize_t ab5500_led_store_current(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int ret; + unsigned long led_curr; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ab5500_led *led = to_led(led_cdev); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + if (strict_strtoul(buf, 0, &led_curr)) + return -EINVAL; + + if (led_curr > led->max_current) + led_curr = led->max_current; + + ret = ab5500_led_sinkctl_write(hvleds, led->id, led_curr); + if (ret < 0) + return ret; + + return len; +} + +/* led class device attributes */ +static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, + ab5500_led_show_current, ab5500_led_store_current); + +static int ab5500_led_init_registers(struct ab5500_hvleds *hvleds) +{ + int ret = 0; + unsigned int led_id; + + /* fade - manual : dur mid : pwm duty mid */ + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + AB5500_LED_REG_ENABLE, true); + if (ret < 0) { + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + AB5500_LED_REG_ENABLE, ret); + return ret; + } + + for (led_id = 0; led_id < AB5500_HVLEDS_MAX; led_id++) { + /* Set pwm freq. and sink current to mid values */ + ret = ab5500_led_pwmfreq_write( + hvleds, led_id, AB5500_LED_PWMFREQ_MAX); + if (ret < 0) + return ret; + + ret = ab5500_led_sinkctl_write( + hvleds, led_id, AB5500_LED_SINKCURR_MAX); + if (ret < 0) + return ret; + + /* init led off */ + ret = ab5500_led_pwmduty_write( + hvleds, led_id, AB5500_LED_PWMDUTY_OFF); + if (ret < 0) + return ret; + } + + return ret; +} + +static int ab5500_led_register_leds(struct device *dev, + struct ab5500_hvleds_platform_data *pdata, + struct ab5500_hvleds *hvleds) +{ + int i_led; + int err; + struct ab5500_led_conf *pled; + struct ab5500_led *led; + + hvleds->dev = dev; + hvleds->pdata = pdata; + for (i_led = 0; i_led < AB5500_HVLEDS_MAX; i_led++) { + pled = &pdata->leds[i_led]; + led = &hvleds->leds[i_led]; + + INIT_WORK(&led->led_work, ab5500_led_work); + + led->id = pled->led_id; + led->max_current = pled->max_current; + led->status = pled->status; + led->led_cdev.name = pled->name; + led->led_cdev.brightness_set = ab5500_led_brightness_set; + + err = led_classdev_register(dev, &led->led_cdev); + if (err < 0) { + dev_err(dev, "Register led class failed: %d\n", err); + goto bailout1; + } + + err = device_create_file(led->led_cdev.dev, + &dev_attr_led_current); + if (err < 0) { + dev_err(dev, "sysfs device creation failed: %d\n", err); + goto bailout2; + } + } + + return err; + for (; i_led >= 0; i_led--) { + device_remove_file(led->led_cdev.dev, &dev_attr_led_current); +bailout2: + led_classdev_unregister(&hvleds->leds[i_led].led_cdev); +bailout1: + cancel_work_sync(&hvleds->leds[i_led].led_work); + } + return err; +} + +static int __devinit ab5500_hvleds_probe(struct platform_device *pdev) +{ + struct ab5500_hvleds_platform_data *pdata = pdev->dev.platform_data; + struct ab5500_hvleds *hvleds = NULL; + int err = 0, i; + + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data required\n"); + err = -ENODEV; + goto err_out; + } + + hvleds = kzalloc(sizeof(struct ab5500_hvleds), GFP_KERNEL); + if (hvleds == NULL) { + err = -ENOMEM; + goto err_out; + } + + mutex_init(&hvleds->lock); + + /* init leds data and register led_classdev */ + err = ab5500_led_register_leds(&pdev->dev, pdata, hvleds); + if (err < 0) { + dev_err(&pdev->dev, "leds registeration failed\n"); + goto err_out; + } + + /* init device registers and set initial led current */ + err = ab5500_led_init_registers(hvleds); + if (err < 0) { + dev_err(&pdev->dev, "reg init failed: %d\n", err); + goto err_reg_init; + } + + dev_info(&pdev->dev, "enabled\n"); + + return err; + +err_reg_init: + for (i = 0; i < AB5500_HVLEDS_MAX; i++) { + struct ab5500_led *led = &hvleds->leds[i]; + + led_classdev_unregister(&led->led_cdev); + device_remove_file(led->led_cdev.dev, &dev_attr_led_current); + cancel_work_sync(&led->led_work); + } +err_out: + kfree(hvleds); + return err; +} + +static int __devexit ab5500_hvleds_remove(struct platform_device *pdev) +{ + struct ab5500_hvleds *hvleds = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < AB5500_HVLEDS_MAX; i++) { + struct ab5500_led *led = &hvleds->leds[i]; + + led_classdev_unregister(&led->led_cdev); + device_remove_file(led->led_cdev.dev, &dev_attr_led_current); + cancel_work_sync(&led->led_work); + } + kfree(hvleds); + return 0; +} + +static struct platform_driver ab5500_hvleds_driver = { + .driver = { + .name = AB5500LED_NAME, + .owner = THIS_MODULE, + }, + .probe = ab5500_hvleds_probe, + .remove = __devexit_p(ab5500_hvleds_remove), +}; + +static int __init ab5500_hvleds_module_init(void) +{ + return platform_driver_register(&ab5500_hvleds_driver); +} + +static void __exit ab5500_hvleds_module_exit(void) +{ + platform_driver_unregister(&ab5500_hvleds_driver); +} + +module_init(ab5500_hvleds_module_init); +module_exit(ab5500_hvleds_module_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Shreshtha Kumar SAHU <shreshthakumar.sahu@stericsson.com>"); +MODULE_DESCRIPTION("Driver for AB5500 HVLED"); + diff --git a/drivers/leds/leds-lm3530.c b/drivers/leds/leds-lm3530.c new file mode 100644 index 00000000000..01ba7395cd1 --- /dev/null +++ b/drivers/leds/leds-lm3530.c @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2011 ST-Ericsson SA. + * Copyright (C) 2009 Motorola, Inc. + * + * License Terms: GNU General Public License v2 + * + * Simple driver for National Semiconductor LM3530 Backlight driver chip + * + * Author: Shreshtha Kumar SAHU <shreshthakumar.sahu@stericsson.com> + * based on leds-lm3530.c by Dan Murphy <D.Murphy@motorola.com> + */ + +#include <linux/i2c.h> +#include <linux/leds.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/led-lm3530.h> +#include <linux/types.h> +#include <linux/regulator/consumer.h> + +#define LM3530_LED_DEV "lcd-backlight" +#define LM3530_NAME "lm3530-led" + +#define LM3530_GEN_CONFIG 0x10 +#define LM3530_ALS_CONFIG 0x20 +#define LM3530_BRT_RAMP_RATE 0x30 +#define LM3530_ALS_ZONE_REG 0x40 +#define LM3530_ALS_IMP_SELECT 0x41 +#define LM3530_BRT_CTRL_REG 0xA0 +#define LM3530_ALS_ZB0_REG 0x60 +#define LM3530_ALS_ZB1_REG 0x61 +#define LM3530_ALS_ZB2_REG 0x62 +#define LM3530_ALS_ZB3_REG 0x63 +#define LM3530_ALS_Z0T_REG 0x70 +#define LM3530_ALS_Z1T_REG 0x71 +#define LM3530_ALS_Z2T_REG 0x72 +#define LM3530_ALS_Z3T_REG 0x73 +#define LM3530_ALS_Z4T_REG 0x74 +#define LM3530_REG_MAX 15 + +/* General Control Register */ +#define LM3530_EN_I2C_SHIFT (0) +#define LM3530_RAMP_LAW_SHIFT (1) +#define LM3530_MAX_CURR_SHIFT (2) +#define LM3530_EN_PWM_SHIFT (5) +#define LM3530_PWM_POL_SHIFT (6) +#define LM3530_EN_PWM_SIMPLE_SHIFT (7) + +#define LM3530_ENABLE_I2C (1 << LM3530_EN_I2C_SHIFT) +#define LM3530_ENABLE_PWM (1 << LM3530_EN_PWM_SHIFT) +#define LM3530_POL_LOW (1 << LM3530_PWM_POL_SHIFT) +#define LM3530_ENABLE_PWM_SIMPLE (1 << LM3530_EN_PWM_SIMPLE_SHIFT) + +/* ALS Config Register Options */ +#define LM3530_ALS_AVG_TIME_SHIFT (0) +#define LM3530_EN_ALS_SHIFT (3) +#define LM3530_ALS_SEL_SHIFT (5) + +#define LM3530_ENABLE_ALS (3 << LM3530_EN_ALS_SHIFT) + +/* Brightness Ramp Rate Register */ +#define LM3530_BRT_RAMP_FALL_SHIFT (0) +#define LM3530_BRT_RAMP_RISE_SHIFT (3) + +/* ALS Resistor Select */ +#define LM3530_ALS1_IMP_SHIFT (0) +#define LM3530_ALS2_IMP_SHIFT (4) + +/* Zone Boundary Register defaults */ +#define LM3530_DEF_ZB_0 (0x33) +#define LM3530_DEF_ZB_1 (0x66) +#define LM3530_DEF_ZB_2 (0x99) +#define LM3530_DEF_ZB_3 (0xCC) + +/* Zone Target Register defaults */ +#define LM3530_DEF_ZT_0 (0x19) +#define LM3530_DEF_ZT_1 (0x33) +#define LM3530_DEF_ZT_2 (0x4C) +#define LM3530_DEF_ZT_3 (0x66) +#define LM3530_DEF_ZT_4 (0x7F) + +struct lm3530_mode_map { + const char *mode; + enum lm3530_mode mode_val; +}; + +static struct lm3530_mode_map mode_map[] = { + { "man", LM3530_BL_MODE_MANUAL }, + { "als", LM3530_BL_MODE_ALS }, + { "pwm", LM3530_BL_MODE_PWM }, +}; + +/** + * struct lm3530_data + * @led_dev: led class device + * @client: i2c client + * @pdata: LM3530 platform data + * @mode: mode of operation - manual, ALS, PWM + * @regulator: regulator + * @enable: regulator is enabled + */ +struct lm3530_data { + struct led_classdev led_dev; + struct i2c_client *client; + struct lm3530_platform_data *pdata; + enum lm3530_mode mode; + struct regulator *regulator; + bool enable; +}; + +static const u8 lm3530_reg[LM3530_REG_MAX] = { + LM3530_GEN_CONFIG, + LM3530_ALS_CONFIG, + LM3530_BRT_RAMP_RATE, + LM3530_ALS_ZONE_REG, + LM3530_ALS_IMP_SELECT, + LM3530_BRT_CTRL_REG, + LM3530_ALS_ZB0_REG, + LM3530_ALS_ZB1_REG, + LM3530_ALS_ZB2_REG, + LM3530_ALS_ZB3_REG, + LM3530_ALS_Z0T_REG, + LM3530_ALS_Z1T_REG, + LM3530_ALS_Z2T_REG, + LM3530_ALS_Z3T_REG, + LM3530_ALS_Z4T_REG, +}; + +static int lm3530_get_mode_from_str(const char *str) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mode_map); i++) + if (sysfs_streq(str, mode_map[i].mode)) + return mode_map[i].mode_val; + + return -1; +} + +static int lm3530_init_registers(struct lm3530_data *drvdata) +{ + int ret = 0; + int i; + u8 gen_config; + u8 als_config = 0; + u8 brt_ramp; + u8 als_imp_sel = 0; + u8 brightness; + u8 reg_val[LM3530_REG_MAX]; + struct lm3530_platform_data *pltfm = drvdata->pdata; + struct i2c_client *client = drvdata->client; + + gen_config = (pltfm->brt_ramp_law << LM3530_RAMP_LAW_SHIFT) | + ((pltfm->max_current & 7) << LM3530_MAX_CURR_SHIFT); + + if (drvdata->mode == LM3530_BL_MODE_MANUAL || + drvdata->mode == LM3530_BL_MODE_ALS) + gen_config |= (LM3530_ENABLE_I2C); + + if (drvdata->mode == LM3530_BL_MODE_ALS) { + als_config = + (pltfm->als_avrg_time << LM3530_ALS_AVG_TIME_SHIFT) | + (LM3530_ENABLE_ALS) | + (pltfm->als_input_mode << LM3530_ALS_SEL_SHIFT); + + als_imp_sel = + (pltfm->als1_resistor_sel << LM3530_ALS1_IMP_SHIFT) | + (pltfm->als2_resistor_sel << LM3530_ALS2_IMP_SHIFT); + } + + if (drvdata->mode == LM3530_BL_MODE_PWM) + gen_config |= (LM3530_ENABLE_PWM) | + (pltfm->pwm_pol_hi << LM3530_PWM_POL_SHIFT) | + (LM3530_ENABLE_PWM_SIMPLE); + + brt_ramp = (pltfm->brt_ramp_fall << LM3530_BRT_RAMP_FALL_SHIFT) | + (pltfm->brt_ramp_rise << LM3530_BRT_RAMP_RISE_SHIFT); + + brightness = pltfm->brt_val; + + reg_val[0] = gen_config; /* LM3530_GEN_CONFIG */ + reg_val[1] = als_config; /* LM3530_ALS_CONFIG */ + reg_val[2] = brt_ramp; /* LM3530_BRT_RAMP_RATE */ + reg_val[3] = 0x00; /* LM3530_ALS_ZONE_REG */ + reg_val[4] = als_imp_sel; /* LM3530_ALS_IMP_SELECT */ + reg_val[5] = brightness; /* LM3530_BRT_CTRL_REG */ + reg_val[6] = LM3530_DEF_ZB_0; /* LM3530_ALS_ZB0_REG */ + reg_val[7] = LM3530_DEF_ZB_1; /* LM3530_ALS_ZB1_REG */ + reg_val[8] = LM3530_DEF_ZB_2; /* LM3530_ALS_ZB2_REG */ + reg_val[9] = LM3530_DEF_ZB_3; /* LM3530_ALS_ZB3_REG */ + reg_val[10] = LM3530_DEF_ZT_0; /* LM3530_ALS_Z0T_REG */ + reg_val[11] = LM3530_DEF_ZT_1; /* LM3530_ALS_Z1T_REG */ + reg_val[12] = LM3530_DEF_ZT_2; /* LM3530_ALS_Z2T_REG */ + reg_val[13] = LM3530_DEF_ZT_3; /* LM3530_ALS_Z3T_REG */ + reg_val[14] = LM3530_DEF_ZT_4; /* LM3530_ALS_Z4T_REG */ + + for (i = 0; i < LM3530_REG_MAX; i++) { + ret = i2c_smbus_write_byte_data(client, + lm3530_reg[i], reg_val[i]); + if (ret) + break; + } + + return ret; +} + +static void lm3530_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brt_val) +{ + int err; + struct lm3530_data *drvdata = + container_of(led_cdev, struct lm3530_data, led_dev); + + switch (drvdata->mode) { + case LM3530_BL_MODE_MANUAL: + + if (!drvdata->enable) { + err = regulator_enable(drvdata->regulator); + if (err) { + dev_err(&drvdata->client->dev, + "Unable to enable regulator\n"); + break; + } + drvdata->enable = true; + lm3530_init_registers(drvdata); + if (err) { + dev_err(&drvdata->client->dev, + "Register Init failed: %d\n", err); + break; + } + } + + /* set the brightness in brightness control register*/ + err = i2c_smbus_write_byte_data(drvdata->client, + LM3530_BRT_CTRL_REG, brt_val / 2); + if (err) + dev_err(&drvdata->client->dev, + "Unable to set brightness: %d\n", err); + + if (brt_val == 0 && drvdata->enable) { + err = regulator_disable(drvdata->regulator); + if (err) + dev_err(&drvdata->client->dev, + "Unable to disable regulator\n"); + drvdata->enable = false; + } + break; + case LM3530_BL_MODE_ALS: + break; + case LM3530_BL_MODE_PWM: + break; + default: + break; + } +} + + +static ssize_t lm3530_mode_set(struct device *dev, struct device_attribute + *attr, const char *buf, size_t size) +{ + int err; + struct i2c_client *client = container_of( + dev->parent, struct i2c_client, dev); + struct lm3530_data *drvdata = i2c_get_clientdata(client); + int mode; + + mode = lm3530_get_mode_from_str(buf); + if (mode < 0) { + dev_err(dev, "Invalid mode\n"); + return -EINVAL; + } + + if (mode == LM3530_BL_MODE_MANUAL) + drvdata->mode = LM3530_BL_MODE_MANUAL; + else if (mode == LM3530_BL_MODE_ALS) + drvdata->mode = LM3530_BL_MODE_ALS; + else if (mode == LM3530_BL_MODE_PWM) { + dev_err(dev, "PWM mode not supported\n"); + return -EINVAL; + } + + err = lm3530_init_registers(drvdata); + if (err) { + dev_err(dev, "Setting %s Mode failed :%d\n", buf, err); + return err; + } + + return sizeof(drvdata->mode); +} + +static DEVICE_ATTR(mode, 0644, NULL, lm3530_mode_set); + +static int __devinit lm3530_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lm3530_platform_data *pdata = client->dev.platform_data; + struct lm3530_data *drvdata; + int err = 0; + + if (pdata == NULL) { + dev_err(&client->dev, "platform data required\n"); + err = -ENODEV; + goto err_out; + } + + /* BL mode */ + if (pdata->mode > LM3530_BL_MODE_PWM) { + dev_err(&client->dev, "Illegal Mode request\n"); + err = -EINVAL; + goto err_out; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "I2C_FUNC_I2C not supported\n"); + err = -EIO; + goto err_out; + } + + drvdata = kzalloc(sizeof(struct lm3530_data), GFP_KERNEL); + if (drvdata == NULL) { + err = -ENOMEM; + goto err_out; + } + + drvdata->mode = pdata->mode; + drvdata->client = client; + drvdata->pdata = pdata; + drvdata->enable = false; + drvdata->led_dev.name = LM3530_LED_DEV; + drvdata->led_dev.brightness_set = lm3530_brightness_set; + + i2c_set_clientdata(client, drvdata); + + drvdata->regulator = regulator_get(&client->dev, "vin"); + if (IS_ERR(drvdata->regulator)) { + dev_err(&client->dev, "regulator get failed\n"); + err = PTR_ERR(drvdata->regulator); + drvdata->regulator = NULL; + goto err_regulator_get; + } + + err = lm3530_init_registers(drvdata); + if (err < 0) { + dev_err(&client->dev, "Register Init failed: %d\n", err); + err = -ENODEV; + goto err_reg_init; + } + + err = led_classdev_register((struct device *) + &client->dev, &drvdata->led_dev); + if (err < 0) { + dev_err(&client->dev, "Register led class failed: %d\n", err); + err = -ENODEV; + goto err_class_register; + } + + err = device_create_file(drvdata->led_dev.dev, &dev_attr_mode); + if (err < 0) { + dev_err(&client->dev, "File device creation failed: %d\n", err); + err = -ENODEV; + goto err_create_file; + } + + return 0; + +err_create_file: + regulator_put(drvdata->regulator); +err_regulator_get: + led_classdev_unregister(&drvdata->led_dev); +err_class_register: +err_reg_init: + kfree(drvdata); +err_out: + return err; +} + +static int __devexit lm3530_remove(struct i2c_client *client) +{ + struct lm3530_data *drvdata = i2c_get_clientdata(client); + + device_remove_file(drvdata->led_dev.dev, &dev_attr_mode); + + if (drvdata->enable) + regulator_disable(drvdata->regulator); + regulator_put(drvdata->regulator); + led_classdev_unregister(&drvdata->led_dev); + kfree(drvdata); + return 0; +} + +static const struct i2c_device_id lm3530_id[] = { + {LM3530_NAME, 0}, + {} +}; + +static struct i2c_driver lm3530_i2c_driver = { + .probe = lm3530_probe, + .remove = lm3530_remove, + .id_table = lm3530_id, + .driver = { + .name = LM3530_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init lm3530_init(void) +{ + return i2c_add_driver(&lm3530_i2c_driver); +} + +static void __exit lm3530_exit(void) +{ + i2c_del_driver(&lm3530_i2c_driver); +} + +module_init(lm3530_init); +module_exit(lm3530_exit); + +MODULE_DESCRIPTION("Back Light driver for LM3530"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Shreshtha Kumar SAHU <shreshthakumar.sahu@stericsson.com>"); diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c new file mode 100644 index 00000000000..8f412c6b2f0 --- /dev/null +++ b/drivers/leds/leds-lp5521.c @@ -0,0 +1,751 @@ +/* + * lp5521.c - LP5521 LED Driver + * + * Copyright (C) 2007 Nokia Corporation + * + * Written by Mathias Nyman <mathias.nyman@xxxxxxxxx> + * Updated by Felipe Balbi <felipe.balbi@xxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/leds.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/i2c/lp5521.h> + +#define LP5521_DRIVER_NAME "lp5521" + +#define LP5521_REG_R_PWM 0x02 +#define LP5521_REG_B_PWM 0x04 +#define LP5521_REG_ENABLE 0x00 +#define LP5521_REG_OP_MODE 0x01 +#define LP5521_REG_G_PWM 0x03 +#define LP5521_REG_R_CNTRL 0x05 +#define LP5521_REG_G_CNTRL 0x06 +#define LP5521_REG_B_CNTRL 0x07 +#define LP5521_REG_MISC 0x08 +#define LP5521_REG_R_CHANNEL_PC 0x09 +#define LP5521_REG_G_CHANNEL_PC 0x0a +#define LP5521_REG_B_CHANNEL_PC 0x0b +#define LP5521_REG_STATUS 0x0c +#define LP5521_REG_RESET 0x0d +#define LP5521_REG_GPO 0x0e +#define LP5521_REG_R_PROG_MEM 0x10 +#define LP5521_REG_G_PROG_MEM 0x30 +#define LP5521_REG_B_PROG_MEM 0x50 + +#define LP5521_CURRENT_1m5 0x0f +#define LP5521_CURRENT_3m1 0x1f +#define LP5521_CURRENT_4m7 0x2f +#define LP5521_CURRENT_6m3 0x3f +#define LP5521_CURRENT_7m9 0x4f +#define LP5521_CURRENT_9m5 0x5f +#define LP5521_CURRENT_11m1 0x6f +#define LP5521_CURRENT_12m7 0x7f +#define LP5521_CURRENT_14m3 0x8f +#define LP5521_CURRENT_15m9 0x9f +#define LP5521_CURRENT_17m5 0xaf +#define LP5521_CURRENT_19m1 0xbf +#define LP5521_CURRENT_20m7 0xcf +#define LP5521_CURRENT_22m3 0xdf +#define LP5521_CURRENT_23m9 0xef +#define LP5521_CURRENT_25m5 0xff + +#define LP5521_PROGRAM_LENGTH 32 /* in bytes */ + +struct lp5521_chip { + /* device lock */ + struct mutex lock; + struct i2c_client *client; + + struct work_struct red_work; + struct work_struct green_work; + struct work_struct blue_work; + + struct led_classdev ledr; + struct led_classdev ledg; + struct led_classdev ledb; + + enum lp5521_mode mode; + + int red; + int green; + int blue; +}; + +static int lp5521_set_mode(struct lp5521_chip *chip, + enum lp5521_mode mode); + +static inline int lp5521_write(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static inline int lp5521_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +static int lp5521_configure(struct i2c_client *client) +{ + int ret = 0; + + /* Enable chip and set light to logarithmic mode */ + ret |= lp5521_write(client, LP5521_REG_ENABLE, 0xc0); + if (ret < 0) + return ret; + + /* setting all color pwms to direct control mode */ + ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x3f); + if (ret < 0) + return ret; + + /* setting current to 4.7 mA for all channels */ + ret |= lp5521_write(client, LP5521_REG_R_CNTRL, LP5521_CURRENT_4m7); + if (ret < 0) + return ret; + ret |= lp5521_write(client, LP5521_REG_G_CNTRL, LP5521_CURRENT_4m7); + if (ret < 0) + return ret; + ret |= lp5521_write(client, LP5521_REG_B_CNTRL, LP5521_CURRENT_4m7); + if (ret < 0) + return ret; + + /* Enable auto-powersave, set charge pump to auto, red to battery */ + ret |= lp5521_write(client, LP5521_REG_MISC, 0x3c); + if (ret < 0) + return ret; + + /* initialize all channels pwm to zero */ + ret |= lp5521_write(client, LP5521_REG_R_PWM, 0); + if (ret < 0) + return ret; + ret |= lp5521_write(client, LP5521_REG_G_PWM, 0); + if (ret < 0) + return ret; + ret |= lp5521_write(client, LP5521_REG_B_PWM, 0); + + /* Not much can be done about errors at this point */ + return ret; +} + +static int lp5521_load_program(struct lp5521_chip *chip, u8 * pattern) +{ + struct i2c_client *client = chip->client; + int ret = 0; + + /* Enter load program mode for all led channels */ + ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x15); /* 0001 0101 */ + if (ret) + return ret; + + if (chip->red) + ret |= i2c_smbus_write_i2c_block_data(client, + LP5521_REG_R_PROG_MEM, + LP5521_PROGRAM_LENGTH, + pattern); + if (chip->green) + ret |= i2c_smbus_write_i2c_block_data(client, + LP5521_REG_G_PROG_MEM, + LP5521_PROGRAM_LENGTH, + pattern); + if (chip->blue) + ret |= i2c_smbus_write_i2c_block_data(client, + LP5521_REG_B_PROG_MEM, + LP5521_PROGRAM_LENGTH, + pattern); + + return ret; +} + +static int lp5521_run_program(struct lp5521_chip *chip) +{ + struct i2c_client *client = chip->client; + int reg; + u8 mask = 0xc0; + u8 exec_state = 0; + + reg = lp5521_read(client, LP5521_REG_ENABLE); + if (reg < 0) + return reg; + + reg &= mask; + + /* set all active channels exec state to countinous run */ + exec_state |= (chip->red << 5); + exec_state |= (chip->green << 3); + exec_state |= (chip->blue << 1); + + reg |= exec_state; + + if (lp5521_write(client, LP5521_REG_ENABLE, reg)) + dev_dbg(&client->dev, "failed writing to register %02x\n", + LP5521_REG_ENABLE); + + /* set op-mode to run for active channels, disabled for others */ + if (lp5521_write(client, LP5521_REG_OP_MODE, exec_state)) + dev_dbg(&client->dev, "failed writing to register %02x\n", + LP5521_REG_OP_MODE); + + return 0; +} + +/*--------------------------------------------------------------*/ +/* Sysfs interface */ +/*--------------------------------------------------------------*/ + +static ssize_t show_active_channels(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + char channels[4]; + int pos = 0; + + if (chip->red) + pos += sprintf(channels + pos, "r"); + if (chip->green) + pos += sprintf(channels + pos, "g"); + if (chip->blue) + pos += sprintf(channels + pos, "b"); + + channels[pos] = '\0'; + + return sprintf(buf, "%s\n", channels); +} + +static ssize_t store_active_channels(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + + chip->red = 0; + chip->green = 0; + chip->blue = 0; + + if (strchr(buf, 'r') != NULL) + chip->red = 1; + if (strchr(buf, 'b') != NULL) + chip->blue = 1; + if (strchr(buf, 'g') != NULL) + chip->green = 1; + + return len; +} + +static ssize_t show_color(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int r, g, b; + + r = lp5521_read(client, LP5521_REG_R_PWM); + g = lp5521_read(client, LP5521_REG_G_PWM); + b = lp5521_read(client, LP5521_REG_B_PWM); + + if (r < 0 || g < 0 || b < 0) + return -EINVAL; + + return sprintf(buf, "%.2x:%.2x:%.2x\n", r, g, b); +} + +static ssize_t store_color(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lp5521_chip *chip = i2c_get_clientdata(client); + int ret; + unsigned r, g, b; + + + ret = sscanf(buf, "%2x:%2x:%2x", &r, &g, &b); + if (ret != 3) + return -EINVAL; + + mutex_lock(&chip->lock); + + ret = lp5521_write(client, LP5521_REG_R_PWM, (u8) r); + ret = lp5521_write(client, LP5521_REG_G_PWM, (u8) g); + ret = lp5521_write(client, LP5521_REG_B_PWM, (u8) b); + + mutex_unlock(&chip->lock); + + return len; +} + +static ssize_t store_load(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + int ret, nrchars, offset = 0, i = 0; + char c[3]; + unsigned cmd; + u8 pattern[LP5521_PROGRAM_LENGTH] = { 0 }; + + while ((offset < len - 1) && (i < LP5521_PROGRAM_LENGTH)) { + + /* separate sscanfs because length is working only for %s */ + ret = sscanf(buf + offset, "%2s%n ", c, &nrchars); + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto fail; + pattern[i] = (u8) cmd; + + offset += nrchars; + i++; + } + + /* pattern commands are always two bytes long */ + if (i % 2) + goto fail; + + mutex_lock(&chip->lock); + + ret = lp5521_load_program(chip, pattern); + mutex_unlock(&chip->lock); + + if (ret) { + dev_err(dev, "lp5521 failed loading pattern\n"); + return ret; + } + + return len; +fail: + dev_err(dev, "lp5521 wrong pattern format\n"); + return -EINVAL; +} + +static ssize_t show_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + char *mode; + + mutex_lock(&chip->lock); + switch (chip->mode) { + case LP5521_MODE_RUN: + mode = "run"; + break; + case LP5521_MODE_LOAD: + mode = "load"; + break; + case LP5521_MODE_DIRECT_CONTROL: + mode = "direct"; + break; + default: + mode = "undefined"; + } + mutex_unlock(&chip->lock); + + return sprintf(buf, "%s\n", mode); +} + +static ssize_t store_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + + mutex_lock(&chip->lock); + + if (sysfs_streq(buf, "run")) + lp5521_set_mode(chip, LP5521_MODE_RUN); + else if (sysfs_streq(buf, "load")) + lp5521_set_mode(chip, LP5521_MODE_LOAD); + else if (sysfs_streq(buf, "direct")) + lp5521_set_mode(chip, LP5521_MODE_DIRECT_CONTROL); + else + len = -EINVAL; + + mutex_unlock(&chip->lock); + + return len; +} + +static ssize_t show_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int r, g, b; + + r = lp5521_read(client, LP5521_REG_R_CNTRL); + g = lp5521_read(client, LP5521_REG_G_CNTRL); + b = lp5521_read(client, LP5521_REG_B_CNTRL); + + if (r < 0 || g < 0 || b < 0) + return -EINVAL; + + r >>= 4; + g >>= 4; + b >>= 4; + + return sprintf(buf, "%x %x %x\n", r, g, b); +} + +static ssize_t store_current(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = dev_get_drvdata(dev); + struct i2c_client *client = chip->client; + int ret; + unsigned curr; + + ret = sscanf(buf, "%1x", &curr); + if (ret != 1) + return -EINVAL; + + /* current level is determined by the 4 upper bits, rest is ones */ + curr = (curr << 4) | 0x0f; + + mutex_lock(&chip->lock); + + ret |= lp5521_write(client, LP5521_REG_R_CNTRL, (u8) curr); + ret |= lp5521_write(client, LP5521_REG_G_CNTRL, (u8) curr); + ret |= lp5521_write(client, LP5521_REG_B_CNTRL, (u8) curr); + + mutex_unlock(&chip->lock); + + return len; +} + +static DEVICE_ATTR(color, S_IRUGO | S_IWUGO, show_color, store_color); +static DEVICE_ATTR(load, S_IWUGO, NULL, store_load); +static DEVICE_ATTR(mode, S_IRUGO | S_IWUGO, show_mode, store_mode); +static DEVICE_ATTR(active_channels, S_IRUGO | S_IWUGO, + show_active_channels, store_active_channels); +static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, show_current, + store_current); + +static int lp5521_register_sysfs(struct i2c_client *client) +{ + struct device *dev = &client->dev; + int ret; + + ret = device_create_file(dev, &dev_attr_color); + if (ret) + goto fail1; + ret = device_create_file(dev, &dev_attr_load); + if (ret) + goto fail2; + ret = device_create_file(dev, &dev_attr_active_channels); + if (ret) + goto fail3; + ret = device_create_file(dev, &dev_attr_mode); + if (ret) + goto fail4; + ret = device_create_file(dev, &dev_attr_led_current); + if (ret) + goto fail5; + + return 0; + +fail5: + device_remove_file(dev, &dev_attr_mode); +fail4: + device_remove_file(dev, &dev_attr_active_channels); +fail3: + device_remove_file(dev, &dev_attr_load); +fail2: + device_remove_file(dev, &dev_attr_color); +fail1: + return ret; +} + +static void lp5521_unregister_sysfs(struct i2c_client *client) +{ + struct device *dev = &client->dev; + + device_remove_file(dev, &dev_attr_led_current); + device_remove_file(dev, &dev_attr_mode); + device_remove_file(dev, &dev_attr_active_channels); + device_remove_file(dev, &dev_attr_color); + device_remove_file(dev, &dev_attr_load); +} + +/*--------------------------------------------------------------*/ +/* Set chip operating mode */ +/*--------------------------------------------------------------*/ + +static int lp5521_set_mode(struct lp5521_chip *chip, enum lp5521_mode mode) +{ + struct i2c_client *client = chip->client; + int ret = 0; + + /* if in that mode already do nothing, except for run */ + if (chip->mode == mode && mode != LP5521_MODE_RUN) + return 0; + + switch (mode) { + case LP5521_MODE_RUN: + ret = lp5521_run_program(chip); + break; + case LP5521_MODE_LOAD: + ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x15); + break; + case LP5521_MODE_DIRECT_CONTROL: + ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x3F); + break; + default: + dev_dbg(&client->dev, "unsupported mode %d\n", mode); + } + + chip->mode = mode; + + return ret; +} + +static void lp5521_red_work(struct work_struct *work) +{ + struct lp5521_chip *chip = + container_of(work, struct lp5521_chip, red_work); + int ret; + + ret = lp5521_configure(chip->client); + if (ret) { + dev_dbg(&chip->client->dev, + "could not configure lp5521, %d\n", ret); + return; + } + + ret = lp5521_write(chip->client, LP5521_REG_R_PWM, chip->red); + if (ret) + dev_dbg(&chip->client->dev, + "could not set brightness, %d\n", ret); +} + +static void lp5521_red_set(struct led_classdev *led, + enum led_brightness value) +{ + struct lp5521_chip *chip = + container_of(led, struct lp5521_chip, ledr); + + chip->red = value; + schedule_work(&chip->red_work); +} + +static void lp5521_green_work(struct work_struct *work) +{ + struct lp5521_chip *chip = + container_of(work, struct lp5521_chip, green_work); + int ret; + + ret = lp5521_configure(chip->client); + if (ret) { + dev_dbg(&chip->client->dev, + "could not configure lp5521, %d\n", ret); + return; + } + + ret = lp5521_write(chip->client, LP5521_REG_G_PWM, chip->green); + if (ret) + dev_dbg(&chip->client->dev, + "could not set brightness, %d\n", ret); +} + +static void lp5521_green_set(struct led_classdev *led, + enum led_brightness value) +{ + struct lp5521_chip *chip = + container_of(led, struct lp5521_chip, ledg); + + chip->green = value; + schedule_work(&chip->green_work); +} + +static void lp5521_blue_work(struct work_struct *work) +{ + struct lp5521_chip *chip = + container_of(work, struct lp5521_chip, blue_work); + int ret; + + ret = lp5521_configure(chip->client); + if (ret) { + dev_dbg(&chip->client->dev, + "could not configure lp5521, %d\n", ret); + return; + } + + ret = lp5521_write(chip->client, LP5521_REG_B_PWM, chip->blue); + if (ret) + dev_dbg(&chip->client->dev, + "could not set brightness, %d\n", ret); +} + +static void lp5521_blue_set(struct led_classdev *led, + enum led_brightness value) +{ + struct lp5521_chip *chip = + container_of(led, struct lp5521_chip, ledb); + + chip->blue = value; + schedule_work(&chip->blue_work); +} + +/*--------------------------------------------------------------*/ +/* Probe, Attach, Remove */ +/*--------------------------------------------------------------*/ + +static int __devinit lp5521_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lp5521_platform_data *pdata = client->dev.platform_data; + struct lp5521_chip *chip; + char name[16]; + int ret = 0; + + if (!pdata) { + dev_err(&client->dev, "platform_data is missing\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + strncpy(client->name, LP5521_DRIVER_NAME, I2C_NAME_SIZE); + i2c_set_clientdata(client, chip); + + mutex_init(&chip->lock); + + INIT_WORK(&chip->red_work, lp5521_red_work); + INIT_WORK(&chip->green_work, lp5521_green_work); + INIT_WORK(&chip->blue_work, lp5521_blue_work); + + ret = lp5521_configure(client); + if (ret < 0) { + dev_err(&client->dev, "lp5521 error configuring chip \n"); + goto fail1; + } + + /* Set default values */ + chip->mode = pdata->mode; + chip->red = pdata->red_present; + chip->green = pdata->green_present; + chip->blue = pdata->blue_present; + + chip->ledr.brightness_set = lp5521_red_set; + chip->ledr.default_trigger = NULL; + snprintf(name, sizeof(name), "%s::red", pdata->label); + chip->ledr.name = name; + ret = led_classdev_register(&client->dev, &chip->ledr); + if (ret < 0) { + dev_dbg(&client->dev, "failed to register led %s, %d\n", + chip->ledb.name, ret); + goto fail1; + } + + chip->ledg.brightness_set = lp5521_green_set; + chip->ledg.default_trigger = NULL; + snprintf(name, sizeof(name), "%s::green", pdata->label); + chip->ledg.name = name; + ret = led_classdev_register(&client->dev, &chip->ledg); + if (ret < 0) { + dev_dbg(&client->dev, "failed to register led %s, %d\n", + chip->ledb.name, ret); + goto fail2; + } + + chip->ledb.brightness_set = lp5521_blue_set; + chip->ledb.default_trigger = NULL; + snprintf(name, sizeof(name), "%s::blue", pdata->label); + chip->ledb.name = name; + ret = led_classdev_register(&client->dev, &chip->ledb); + if (ret < 0) { + dev_dbg(&client->dev, "failed to register led %s, %d\n", + chip->ledb.name, ret); + goto fail3; + } + + ret = lp5521_register_sysfs(client); + if (ret) { + dev_err(&client->dev, + "lp5521 registering sysfs failed \n"); + goto fail4; + } + + return 0; + +fail4: + led_classdev_unregister(&chip->ledb); +fail3: + led_classdev_unregister(&chip->ledg); +fail2: + led_classdev_unregister(&chip->ledr); +fail1: + i2c_set_clientdata(client, NULL); + kfree(chip); + + return ret; +} + +static int __exit lp5521_remove(struct i2c_client *client) +{ + struct lp5521_chip *chip = i2c_get_clientdata(client); + + lp5521_unregister_sysfs(client); + i2c_set_clientdata(client, NULL); + + led_classdev_unregister(&chip->ledb); + led_classdev_unregister(&chip->ledg); + led_classdev_unregister(&chip->ledr); + + kfree(chip); + + return 0; +} + +static const struct i2c_device_id lp5521_id[] = { + {LP5521_DRIVER_NAME, 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, lp5521_id); + +static struct i2c_driver lp5521_driver = { + .driver = { + .name = LP5521_DRIVER_NAME, + }, + .probe = lp5521_probe, + .remove = __exit_p(lp5521_remove), + .id_table = lp5521_id, +}; + +static int __init lp5521_init(void) +{ + return i2c_add_driver(&lp5521_driver); +} + +module_init(lp5521_init); + +static void __exit lp5521_exit(void) +{ + i2c_del_driver(&lp5521_driver); +} + +module_exit(lp5521_exit); + +MODULE_AUTHOR("Mathias Nyman <mathias.nyman@xxxxxxxxx>"); +MODULE_DESCRIPTION("lp5521 LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index da3fa8dcdf5..a8bf77af0ee 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -27,6 +27,7 @@ struct led_pwm_data { struct led_classdev cdev; struct pwm_device *pwm; unsigned int active_low; + unsigned int lth_brightness; unsigned int period; }; @@ -42,7 +43,10 @@ static void led_pwm_set(struct led_classdev *led_cdev, pwm_config(led_dat->pwm, 0, period); pwm_disable(led_dat->pwm); } else { - pwm_config(led_dat->pwm, brightness * period / max, period); + brightness = led_dat->lth_brightness + (brightness * + (led_dat->period - led_dat->lth_brightness) / max); + pwm_config(led_dat->pwm, brightness, led_dat->period); + pwm_enable(led_dat->pwm); } } @@ -78,6 +82,8 @@ static int led_pwm_probe(struct platform_device *pdev) led_dat->cdev.default_trigger = cur_led->default_trigger; led_dat->active_low = cur_led->active_low; led_dat->period = cur_led->pwm_period_ns; + led_dat->lth_brightness = cur_led->lth_brightness * + (cur_led->pwm_period_ns / cur_led->max_brightness); led_dat->cdev.brightness_set = led_pwm_set; led_dat->cdev.brightness = LED_OFF; led_dat->cdev.max_brightness = cur_led->max_brightness; diff --git a/drivers/leds/ledtrig-sleep.c b/drivers/leds/ledtrig-sleep.c new file mode 100644 index 00000000000..f1640421215 --- /dev/null +++ b/drivers/leds/ledtrig-sleep.c @@ -0,0 +1,80 @@ +/* drivers/leds/ledtrig-sleep.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/earlysuspend.h> +#include <linux/leds.h> +#include <linux/suspend.h> + +static int ledtrig_sleep_pm_callback(struct notifier_block *nfb, + unsigned long action, + void *ignored); + +DEFINE_LED_TRIGGER(ledtrig_sleep) +static struct notifier_block ledtrig_sleep_pm_notifier = { + .notifier_call = ledtrig_sleep_pm_callback, + .priority = 0, +}; + +static void ledtrig_sleep_early_suspend(struct early_suspend *h) +{ + led_trigger_event(ledtrig_sleep, LED_FULL); +} + +static void ledtrig_sleep_early_resume(struct early_suspend *h) +{ + led_trigger_event(ledtrig_sleep, LED_OFF); +} + +static struct early_suspend ledtrig_sleep_early_suspend_handler = { + .suspend = ledtrig_sleep_early_suspend, + .resume = ledtrig_sleep_early_resume, +}; + +static int ledtrig_sleep_pm_callback(struct notifier_block *nfb, + unsigned long action, + void *ignored) +{ + switch (action) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + led_trigger_event(ledtrig_sleep, LED_OFF); + return NOTIFY_OK; + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + led_trigger_event(ledtrig_sleep, LED_FULL); + return NOTIFY_OK; + } + + return NOTIFY_DONE; +} + +static int __init ledtrig_sleep_init(void) +{ + led_trigger_register_simple("sleep", &ledtrig_sleep); + register_pm_notifier(&ledtrig_sleep_pm_notifier); + register_early_suspend(&ledtrig_sleep_early_suspend_handler); + return 0; +} + +static void __exit ledtrig_sleep_exit(void) +{ + unregister_early_suspend(&ledtrig_sleep_early_suspend_handler); + unregister_pm_notifier(&ledtrig_sleep_pm_notifier); + led_trigger_unregister_simple(ledtrig_sleep); +} + +module_init(ledtrig_sleep_init); +module_exit(ledtrig_sleep_exit); + |