diff options
Diffstat (limited to 'drivers/leds')
-rw-r--r-- | drivers/leds/Kconfig | 8 | ||||
-rw-r--r-- | drivers/leds/Makefile | 1 | ||||
-rw-r--r-- | drivers/leds/leds-ab5500.c | 811 | ||||
-rw-r--r-- | drivers/leds/leds-lm3530.c | 25 | ||||
-rw-r--r-- | drivers/leds/leds-lp5521.c | 8 | ||||
-rw-r--r-- | drivers/leds/leds-pwm.c | 8 |
6 files changed, 858 insertions, 3 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 9ca28fced2b9..26bab3988408 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -50,6 +50,14 @@ config LEDS_LM3530 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 LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 1fc6875a8b20..c7b4880a63f2 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -10,6 +10,7 @@ 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 diff --git a/drivers/leds/leds-ab5500.c b/drivers/leds/leds-ab5500.c new file mode 100644 index 000000000000..294551b1962f --- /dev/null +++ b/drivers/leds/leds-ab5500.c @@ -0,0 +1,811 @@ +/* + * leds-ab5500.c - driver for High Voltage (HV) LED in ST-Ericsson AB5500 chip + * + * Copyright (C) 2011 ST-Ericsson SA. + * + * License Terms: GNU General Public License v2 + * + * Author: Shreshtha Kumar SAHU <shreshthakumar.sahu@stericsson.com> + */ + +/* + * Driver for HVLED in ST-Ericsson AB5500 analog baseband controller + * + * This chip can drive upto 3 leds, of upto 40mA of led sink current. + * These leds can be programmed to blink between two intensities with + * fading delay of half, one or two seconds. + * + * Leds can be controlled via sysfs entries in + * "/sys/class/leds/< red | green | blue >" + * + * For each led, + * + * Modes of operation: + * - manual: echo 0 > fade_auto (default, no auto blinking) + * - auto: echo 1 > fade_auto + * + * Soft scaling delay between two intensities: + * - 1/2 sec: echo 1 > fade_delay + * - 1 sec: echo 2 > fade_delay + * - 2 sec: echo 3 > fade_delay + * + * Possible sequence of operation: + * - continuous glow: set brightness (brt) + * - blink between LED_OFF and LED_FULL: + * set fade delay -> set fade auto + * - blink between previous two brightness (only for LED-1): + * set brt1 -> set brt2 -> set fade auto + * + * Delay can be set in any step, its affect will be seen on switching mode. + * + * Note: Blink/Fade feature is supported in AB5500 v2 onwards + * + */ + +#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> + +#include <mach/hardware.h> + +#define AB5500LED_NAME "ab5500-leds" +#define AB5500_LED_MAX 0x03 + +/* Register offsets */ +#define AB5500_LED_REG_ENABLE 0x03 +#define AB5500_LED_FADE_CTRL 0x0D + +/* LED-0 Register Addr. Offsets */ +#define AB5500_LED0_PWM_DUTY 0x01 +#define AB5500_LED0_PWMFREQ 0x02 +#define AB5500_LED0_SINKCTL 0x0A +#define AB5500_LED0_FADE_HI 0x11 +#define AB5500_LED0_FADE_LO 0x17 + +/* LED-1 Register Addr. Offsets */ +#define AB5500_LED1_PWM_DUTY 0x05 +#define AB5500_LED1_PWMFREQ 0x06 +#define AB5500_LED1_SINKCTL 0x0B +#define AB5500_LED1_FADE_HI 0x13 +#define AB5500_LED1_FADE_LO 0x19 + +/* LED-2 Register Addr. Offsets */ +#define AB5500_LED2_PWM_DUTY 0x08 +#define AB5500_LED2_PWMFREQ 0x09 +#define AB5500_LED2_SINKCTL 0x0C +#define AB5500_LED2_FADE_HI 0x15 +#define AB5500_LED2_FADE_LO 0x1B + +/* led-0/1/2 enable bit */ +#define AB5500_LED_ENABLE_MASK 0x04 + +/* led intensity */ +#define AB5500_LED_INTENSITY_OFF 0x0 +#define AB5500_LED_INTENSITY_MAX 0x3FF +#define AB5500_LED_INTENSITY_STEP (AB5500_LED_INTENSITY_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 MAX */ +#define AB5500_LED_SINKCURR_SHIFT 4 + +/* fade Control shift and masks */ +#define AB5500_FADE_DELAY_SHIFT 0x00 +#define AB5500_FADE_MODE_MASK 0x80 +#define AB5500_FADE_DELAY_MASK 0x03 +#define AB5500_FADE_START_MASK 0x04 +#define AB5500_FADE_ON_MASK 0x70 +#define AB5500_LED_FADE_ENABLE(ledid) (0x40 >> (ledid)) + +struct ab5500_led { + u8 id; + u8 max_current; + u16 brt_val; + u16 fade_hi; + u16 fade_lo; + bool led_on; + 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]; + bool hw_fade; + bool fade_auto; + enum ab5500_fade_delay fade_delay; +}; + +static u8 ab5500_led_pwmduty_reg[AB5500_LED_MAX] = { + AB5500_LED0_PWM_DUTY, + AB5500_LED1_PWM_DUTY, + AB5500_LED2_PWM_DUTY, +}; + +static u8 ab5500_led_pwmfreq_reg[AB5500_LED_MAX] = { + AB5500_LED0_PWMFREQ, + AB5500_LED1_PWMFREQ, + AB5500_LED2_PWMFREQ, +}; + +static u8 ab5500_led_sinkctl_reg[AB5500_LED_MAX] = { + AB5500_LED0_SINKCTL, + AB5500_LED1_SINKCTL, + AB5500_LED2_SINKCTL +}; + +static u8 ab5500_led_fade_hi_reg[AB5500_LED_MAX] = { + AB5500_LED0_FADE_HI, + AB5500_LED1_FADE_HI, + AB5500_LED2_FADE_HI, +}; + +static u8 ab5500_led_fade_lo_reg[AB5500_LED_MAX] = { + AB5500_LED0_FADE_LO, + AB5500_LED1_FADE_LO, + AB5500_LED2_FADE_LO, +}; + +#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_enable(struct ab5500_hvleds *hvleds, + unsigned int led_id) +{ + int ret; + + ret = abx500_mask_and_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id], + AB5500_LED_ENABLE_MASK, + AB5500_LED_ENABLE_MASK); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_pwmduty_reg[led_id], ret); + + return ret; + +} + +static int ab5500_led_start_manual(struct ab5500_hvleds *hvleds) +{ + int ret; + + mutex_lock(&hvleds->lock); + + ret = abx500_mask_and_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + AB5500_LED_FADE_CTRL, AB5500_FADE_START_MASK, + AB5500_FADE_START_MASK); + if (ret < 0) + dev_err(hvleds->dev, "update reg 0x%x failed - %d\n", + AB5500_LED_FADE_CTRL, ret); + + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_disable(struct ab5500_hvleds *hvleds, + unsigned int led_id) +{ + int ret; + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id] - 1, 0); + ret |= abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id], 0); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_pwmduty_reg[led_id], ret); + + return ret; +} + +static int ab5500_led_pwmduty_write(struct ab5500_hvleds *hvleds, + unsigned int led_id, u16 val) +{ + int ret; + u8 val_lsb = val & 0xFF; + u8 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; + + if (val > AB5500_LED_SINKCURR_MAX) + val = AB5500_LED_SINKCURR_MAX; + + val = (val << AB5500_LED_SINKCURR_SHIFT); + + dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val=%d\n", + ab5500_led_sinkctl_reg[led_id], val); + + mutex_lock(&hvleds->lock); + + 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_fade_write(struct ab5500_hvleds *hvleds, + unsigned int led_id, bool on, u16 val) +{ + int ret; + int val_lsb = val & 0xFF; + int val_msb = (val & 0x300) >> 8; + u8 *fade_reg; + + if (on) + fade_reg = ab5500_led_fade_hi_reg; + else + fade_reg = ab5500_led_fade_lo_reg; + + dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val = %d\n" + "reg[%d] w val = %d\n", + fade_reg[led_id] - 1, val_lsb, + fade_reg[led_id], val_msb); + + mutex_lock(&hvleds->lock); + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + fade_reg[led_id] - 1, val_lsb); + ret |= abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + fade_reg[led_id], val_msb); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + fade_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_INTENSITY_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->led_on == true) { + ab5500_led_pwmduty_write(hvleds, led->id, led->brt_val); + if (hvleds->hw_fade && led->brt_val) { + ab5500_led_enable(hvleds, led->id); + ab5500_led_start_manual(hvleds); + } + } +} + +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; +} + +static ssize_t ab5500_led_store_fade_auto(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int ret; + u8 fade_ctrl = 0; + unsigned long fade_auto; + 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, &fade_auto)) + return -EINVAL; + + if (fade_auto > 1) { + dev_err(hvleds->dev, "invalid mode\n"); + return -EINVAL; + } + + mutex_lock(&hvleds->lock); + + ret = abx500_get_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + AB5500_LED_FADE_CTRL, &fade_ctrl); + if (ret < 0) { + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + AB5500_LED_FADE_CTRL, ret); + goto unlock_and_return; + } + + /* manual mode */ + if (fade_auto == false) { + fade_ctrl &= ~(AB5500_LED_FADE_ENABLE(led->id)); + if (!(fade_ctrl & AB5500_FADE_ON_MASK)) + fade_ctrl = 0; + + ret = ab5500_led_disable(hvleds, led->id); + if (ret < 0) + goto unlock_and_return; + } else { + /* set led auto enable bit */ + fade_ctrl |= AB5500_FADE_MODE_MASK; + fade_ctrl |= AB5500_LED_FADE_ENABLE(led->id); + + /* set fade delay */ + fade_ctrl &= ~AB5500_FADE_DELAY_MASK; + fade_ctrl |= hvleds->fade_delay << AB5500_FADE_DELAY_SHIFT; + + /* set fade start manual */ + fade_ctrl |= AB5500_FADE_START_MASK; + + /* enble corresponding led */ + ret = ab5500_led_enable(hvleds, led->id); + if (ret < 0) + goto unlock_and_return; + + } + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + AB5500_LED_FADE_CTRL, fade_ctrl); + if (ret < 0) { + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + AB5500_LED_FADE_CTRL, ret); + goto unlock_and_return; + } + + hvleds->fade_auto = fade_auto; + + ret = len; + +unlock_and_return: + mutex_unlock(&hvleds->lock); + + return ret; +} + +static ssize_t ab5500_led_show_fade_auto(struct device *dev, + struct device_attribute *attr, char *buf) +{ + 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); + + return sprintf(buf, "%d\n", hvleds->fade_auto); +} + +static ssize_t ab5500_led_store_fade_delay(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + unsigned long fade_delay; + 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, &fade_delay)) + return -EINVAL; + + if (fade_delay > AB5500_FADE_DELAY_TWOSEC) { + dev_err(hvleds->dev, "invalid mode\n"); + return -EINVAL; + } + + hvleds->fade_delay = fade_delay; + + return len; +} + +/* led class device attributes */ +static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, + ab5500_led_show_current, ab5500_led_store_current); +static DEVICE_ATTR(fade_auto, S_IRUGO | S_IWUGO, + ab5500_led_show_fade_auto, ab5500_led_store_fade_auto); +static DEVICE_ATTR(fade_delay, S_IRUGO | S_IWUGO, + NULL, ab5500_led_store_fade_delay); + +static int ab5500_led_init_registers(struct ab5500_hvleds *hvleds) +{ + int ret = 0; + unsigned int led_id; + + /* fade - manual : dur mid : pwm duty mid */ + if (!hvleds->hw_fade) { + 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++) { + if (hvleds->leds[led_id].led_on == false) + continue; + + ret = ab5500_led_sinkctl_write( + hvleds, led_id, + hvleds->leds[led_id].max_current); + if (ret < 0) + return ret; + + if (hvleds->hw_fade) { + ret = ab5500_led_pwmfreq_write( + hvleds, led_id, + AB5500_LED_PWMFREQ_MAX / 2); + if (ret < 0) + return ret; + + /* fade high intensity */ + ret = ab5500_led_fade_write( + hvleds, led_id, true, + hvleds->leds[led_id].fade_hi); + if (ret < 0) + return ret; + + /* fade low intensity */ + ret = ab5500_led_fade_write( + hvleds, led_id, false, + hvleds->leds[led_id].fade_lo); + if (ret < 0) + return ret; + } + + /* init led off */ + ret |= ab5500_led_pwmduty_write( + hvleds, led_id, AB5500_LED_INTENSITY_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 ret = 0; + struct ab5500_led_conf *pled; + struct ab5500_led *led; + + hvleds->dev = dev; + hvleds->pdata = pdata; + + if (abx500_get_chip_id(dev) == AB5500_2_0) + hvleds->hw_fade = true; + else + hvleds->hw_fade = false; + + 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->led_on = pled->led_on; + led->led_cdev.name = pled->name; + led->led_cdev.brightness_set = ab5500_led_brightness_set; + + /* Provide interface only for enabled LEDs */ + if (led->led_on == false) + continue; + + if (hvleds->hw_fade) { + led->fade_hi = (pled->fade_hi & LED_FULL); + led->fade_hi *= AB5500_LED_INTENSITY_STEP; + led->fade_lo = (pled->fade_lo & LED_FULL); + led->fade_lo *= AB5500_LED_INTENSITY_STEP; + } + + ret = led_classdev_register(dev, &led->led_cdev); + if (ret < 0) { + dev_err(dev, "Register led class failed: %d\n", ret); + goto bailout1; + } + + ret = device_create_file(led->led_cdev.dev, + &dev_attr_led_current); + if (ret < 0) { + dev_err(dev, "sysfs device creation failed: %d\n", ret); + goto bailout2; + } + + if (hvleds->hw_fade) { + ret = device_create_file(led->led_cdev.dev, + &dev_attr_fade_auto); + if (ret < 0) { + dev_err(dev, "sysfs device " + "creation failed: %d\n", ret); + goto bailout3; + } + + ret = device_create_file(led->led_cdev.dev, + &dev_attr_fade_delay); + if (ret < 0) { + dev_err(dev, "sysfs device " + "creation failed: %d\n", ret); + goto bailout4; + } + } + } + + return ret; + for (; i_led >= 0; i_led--) { + if (hvleds->leds[i_led].led_on == false) + continue; + + if (hvleds->hw_fade) { + device_remove_file(hvleds->leds[i_led].led_cdev.dev, + &dev_attr_fade_delay); +bailout4: + device_remove_file(hvleds->leds[i_led].led_cdev.dev, + &dev_attr_fade_auto); + } +bailout3: + device_remove_file(hvleds->leds[i_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 ret; +} + +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 ret = 0, i; + + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data required\n"); + ret = -ENODEV; + goto err_out; + } + + hvleds = kzalloc(sizeof(struct ab5500_hvleds), GFP_KERNEL); + if (hvleds == NULL) { + ret = -ENOMEM; + goto err_out; + } + + mutex_init(&hvleds->lock); + + /* init leds data and register led_classdev */ + ret = ab5500_led_register_leds(&pdev->dev, pdata, hvleds); + if (ret < 0) { + dev_err(&pdev->dev, "leds registration failed\n"); + goto err_out; + } + + /* init device registers and set initial led current */ + ret = ab5500_led_init_registers(hvleds); + if (ret < 0) { + dev_err(&pdev->dev, "reg init failed: %d\n", ret); + goto err_reg_init; + } + + if (hvleds->hw_fade) + dev_info(&pdev->dev, "v2 enabled\n"); + else + dev_info(&pdev->dev, "v1 enabled\n"); + + return ret; + +err_reg_init: + for (i = 0; i < AB5500_HVLEDS_MAX; i++) { + struct ab5500_led *led = &hvleds->leds[i]; + + if (led->led_on == false) + continue; + + device_remove_file(led->led_cdev.dev, &dev_attr_led_current); + if (hvleds->hw_fade) { + device_remove_file(led->led_cdev.dev, + &dev_attr_fade_auto); + device_remove_file(led->led_cdev.dev, + &dev_attr_fade_delay); + } + led_classdev_unregister(&led->led_cdev); + cancel_work_sync(&led->led_work); + } +err_out: + kfree(hvleds); + return ret; +} + +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]; + + if (led->led_on == false) + continue; + + device_remove_file(led->led_cdev.dev, &dev_attr_led_current); + if (hvleds->hw_fade) { + device_remove_file(led->led_cdev.dev, + &dev_attr_fade_auto); + device_remove_file(led->led_cdev.dev, + &dev_attr_fade_delay); + } + led_classdev_unregister(&led->led_cdev); + 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 index e59c166a0ce2..e0c034fc2261 100644 --- a/drivers/leds/leds-lm3530.c +++ b/drivers/leds/leds-lm3530.c @@ -19,6 +19,7 @@ #include <linux/types.h> #include <linux/regulator/consumer.h> #include <linux/module.h> +#include <linux/gpio.h> #define LM3530_LED_DEV "lcd-backlight" #define LM3530_NAME "lm3530-led" @@ -99,6 +100,7 @@ static struct lm3530_mode_map mode_map[] = { * @mode: mode of operation - manual, ALS, PWM * @regulator: regulator * @brighness: previous brightness value + * @hw_en_gpio: GPIO line for LM3530 HWEN * @enable: regulator is enabled */ struct lm3530_data { @@ -108,6 +110,7 @@ struct lm3530_data { enum lm3530_mode mode; struct regulator *regulator; enum led_brightness brightness; + int hw_en_gpio; bool enable; }; @@ -150,7 +153,7 @@ static int lm3530_init_registers(struct lm3530_data *drvdata) u8 als_imp_sel = 0; u8 brightness; u8 reg_val[LM3530_REG_MAX]; - u8 zones[LM3530_ALS_ZB_MAX]; + u8 zones[LM3530_ALS_ZB_MAX] = {0}; u32 als_vmin, als_vmax, als_vstep; struct lm3530_platform_data *pltfm = drvdata->pdata; struct i2c_client *client = drvdata->client; @@ -224,6 +227,8 @@ static int lm3530_init_registers(struct lm3530_data *drvdata) reg_val[14] = LM3530_DEF_ZT_4; /* LM3530_ALS_Z4T_REG */ if (!drvdata->enable) { + if (drvdata->hw_en_gpio != LM3530_NO_HWEN_GPIO) + gpio_set_value(drvdata->hw_en_gpio, 1); ret = regulator_enable(drvdata->regulator); if (ret) { dev_err(&drvdata->client->dev, @@ -276,6 +281,8 @@ static void lm3530_brightness_set(struct led_classdev *led_cdev, if (err) dev_err(&drvdata->client->dev, "Disable regulator failed\n"); + if (drvdata->hw_en_gpio != LM3530_NO_HWEN_GPIO) + gpio_set_value(drvdata->hw_en_gpio, 0); drvdata->enable = false; } break; @@ -377,12 +384,22 @@ static int __devinit lm3530_probe(struct i2c_client *client, drvdata->client = client; drvdata->pdata = pdata; drvdata->brightness = LED_OFF; + drvdata->hw_en_gpio = pdata->hw_en_gpio; drvdata->enable = false; drvdata->led_dev.name = LM3530_LED_DEV; drvdata->led_dev.brightness_set = lm3530_brightness_set; i2c_set_clientdata(client, drvdata); + if (gpio_is_valid(drvdata->hw_en_gpio)) { + err = gpio_request_one(drvdata->hw_en_gpio, GPIOF_OUT_INIT_HIGH, + "lm3530_hw_en"); + if (err < 0) { + dev_err(&client->dev, "lm3530 hw_en gpio failed: %d\n", err); + goto err_gpio_request; + } + } + drvdata->regulator = regulator_get(&client->dev, "vin"); if (IS_ERR(drvdata->regulator)) { dev_err(&client->dev, "regulator get failed\n"); @@ -422,6 +439,10 @@ err_class_register: err_reg_init: regulator_put(drvdata->regulator); err_regulator_get: + if (gpio_is_valid(drvdata->hw_en_gpio)) + gpio_free(drvdata->hw_en_gpio); +err_gpio_request: + i2c_set_clientdata(client, NULL); kfree(drvdata); err_out: return err; @@ -436,6 +457,8 @@ static int __devexit lm3530_remove(struct i2c_client *client) if (drvdata->enable) regulator_disable(drvdata->regulator); regulator_put(drvdata->regulator); + if (gpio_is_valid(drvdata->hw_en_gpio)) + gpio_free(drvdata->hw_en_gpio); led_classdev_unregister(&drvdata->led_dev); kfree(drvdata); return 0; diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index d62a7982a5e6..0c13a08766bb 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -361,7 +361,12 @@ static int lp5521_do_store_load(struct lp5521_engine *engine, 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); - if (ret != 2) + /* + * Execution of a %n directive does not always + * increment the assignment count returned at + * completion of execution.so ret need not be 2 + */ + if ((ret != 1) && (ret != 2)) goto fail; ret = sscanf(c, "%2x", &cmd); if (ret != 1) @@ -695,6 +700,7 @@ static int __devinit lp5521_probe(struct i2c_client *client, lp5521_read(client, LP5521_REG_R_CURRENT, &buf); if (buf != LP5521_REG_R_CURR_DEFAULT) { dev_err(&client->dev, "error in reseting chip\n"); + ret = -EIO; goto fail2; } usleep_range(10000, 20000); diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index 3ed92f34bd44..4d086e87bc1f 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); } } @@ -79,6 +83,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; |