diff options
Diffstat (limited to 'drivers/leds')
-rw-r--r-- | drivers/leds/Kconfig | 29 | ||||
-rw-r--r-- | drivers/leds/Makefile | 3 | ||||
-rw-r--r-- | drivers/leds/led-class.c | 21 | ||||
-rw-r--r-- | drivers/leds/leds-da9052.c | 214 | ||||
-rw-r--r-- | drivers/leds/leds-lm3530.c | 100 | ||||
-rw-r--r-- | drivers/leds/leds-lm3533.c | 785 | ||||
-rw-r--r-- | drivers/leds/leds-lp5521.c | 12 | ||||
-rw-r--r-- | drivers/leds/leds-mc13783.c | 2 | ||||
-rw-r--r-- | drivers/leds/leds-pca955x.c | 95 | ||||
-rw-r--r-- | drivers/leds/ledtrig-backlight.c | 4 | ||||
-rw-r--r-- | drivers/leds/ledtrig-gpio.c | 4 | ||||
-rw-r--r-- | drivers/leds/ledtrig-heartbeat.c | 32 | ||||
-rw-r--r-- | drivers/leds/ledtrig-timer.c | 54 | ||||
-rw-r--r-- | drivers/leds/ledtrig-transient.c | 237 |
14 files changed, 1461 insertions, 131 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index ff4b8cfda585..04cb8c88d74b 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -50,6 +50,19 @@ config LEDS_LM3530 controlled manually or using PWM input or using ambient light automatically. +config LEDS_LM3533 + tristate "LED support for LM3533" + depends on LEDS_CLASS + depends on MFD_LM3533 + help + This option enables support for the LEDs on National Semiconductor / + TI LM3533 Lighting Power chips. + + The LEDs can be controlled directly, through PWM input, or by the + ambient-light-sensor interface. The chip supports + hardware-accelerated blinking with maximum on and off periods of 9.8 + and 77 seconds respectively. + config LEDS_LOCOMO tristate "LED Support for Locomo device" depends on LEDS_CLASS @@ -259,6 +272,14 @@ config LEDS_DA903X This option enables support for on-chip LED drivers found on Dialog Semiconductor DA9030/DA9034 PMICs. +config LEDS_DA9052 + tristate "Dialog DA9052/DA9053 LEDS" + depends on LEDS_CLASS + depends on PMIC_DA9052 + help + This option enables support for on-chip LED drivers found + on Dialog Semiconductor DA9052-BC and DA9053-AA/Bx PMICs. + config LEDS_DAC124S085 tristate "LED Support for DAC124S085 SPI DAC" depends on LEDS_CLASS @@ -471,4 +492,12 @@ config LEDS_TRIGGER_DEFAULT_ON comment "iptables trigger is under Netfilter config (LED target)" depends on LEDS_TRIGGERS +config LEDS_TRIGGER_TRANSIENT + tristate "LED Transient Trigger" + depends on LEDS_TRIGGERS + help + This allows one time activation of a transient state on + GPIO/PWM based hadrware. + If unsure, say Y. + endif # NEW_LEDS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 890481cb09f6..f8958cd6cf6e 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_LM3533) += leds-lm3533.o obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o @@ -31,6 +32,7 @@ obj-$(CONFIG_LEDS_FSG) += leds-fsg.o obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o obj-$(CONFIG_LEDS_PCA9633) += leds-pca9633.o obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o +obj-$(CONFIG_LEDS_DA9052) += leds-da9052.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o obj-$(CONFIG_LEDS_PWM) += leds-pwm.o @@ -56,3 +58,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_TRANSIENT) += ledtrig-transient.o diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 5bff8439dc68..8ee92c81aec2 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -44,23 +44,18 @@ static ssize_t led_brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); + unsigned long state; ssize_t ret = -EINVAL; - char *after; - unsigned long state = simple_strtoul(buf, &after, 10); - size_t count = after - buf; - if (isspace(*after)) - count++; + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; - if (count == size) { - ret = count; + if (state == LED_OFF) + led_trigger_remove(led_cdev); + led_set_brightness(led_cdev, state); - if (state == LED_OFF) - led_trigger_remove(led_cdev); - led_set_brightness(led_cdev, state); - } - - return ret; + return size; } static ssize_t led_max_brightness_show(struct device *dev, diff --git a/drivers/leds/leds-da9052.c b/drivers/leds/leds-da9052.c new file mode 100644 index 000000000000..58a5244c437e --- /dev/null +++ b/drivers/leds/leds-da9052.c @@ -0,0 +1,214 @@ +/* + * LED Driver for Dialog DA9052 PMICs. + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen <dchen@diasemi.com> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/workqueue.h> +#include <linux/slab.h> + +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/pdata.h> + +#define DA9052_OPENDRAIN_OUTPUT 2 +#define DA9052_SET_HIGH_LVL_OUTPUT (1 << 3) +#define DA9052_MASK_UPPER_NIBBLE 0xF0 +#define DA9052_MASK_LOWER_NIBBLE 0x0F +#define DA9052_NIBBLE_SHIFT 4 +#define DA9052_MAX_BRIGHTNESS 0x5f + +struct da9052_led { + struct led_classdev cdev; + struct work_struct work; + struct da9052 *da9052; + unsigned char led_index; + unsigned char id; + int brightness; +}; + +static unsigned char led_reg[] = { + DA9052_LED_CONT_4_REG, + DA9052_LED_CONT_5_REG, +}; + +static int da9052_set_led_brightness(struct da9052_led *led) +{ + u8 val; + int error; + + val = (led->brightness & 0x7f) | DA9052_LED_CONT_DIM; + + error = da9052_reg_write(led->da9052, led_reg[led->led_index], val); + if (error < 0) + dev_err(led->da9052->dev, "Failed to set led brightness, %d\n", + error); + return error; +} + +static void da9052_led_work(struct work_struct *work) +{ + struct da9052_led *led = container_of(work, struct da9052_led, work); + + da9052_set_led_brightness(led); +} + +static void da9052_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct da9052_led *led; + + led = container_of(led_cdev, struct da9052_led, cdev); + led->brightness = value; + schedule_work(&led->work); +} + +static int da9052_configure_leds(struct da9052 *da9052) +{ + int error; + unsigned char register_value = DA9052_OPENDRAIN_OUTPUT + | DA9052_SET_HIGH_LVL_OUTPUT; + + error = da9052_reg_update(da9052, DA9052_GPIO_14_15_REG, + DA9052_MASK_LOWER_NIBBLE, + register_value); + + if (error < 0) { + dev_err(da9052->dev, "Failed to write GPIO 14-15 reg, %d\n", + error); + return error; + } + + error = da9052_reg_update(da9052, DA9052_GPIO_14_15_REG, + DA9052_MASK_UPPER_NIBBLE, + register_value << DA9052_NIBBLE_SHIFT); + if (error < 0) + dev_err(da9052->dev, "Failed to write GPIO 14-15 reg, %d\n", + error); + + return error; +} + +static int __devinit da9052_led_probe(struct platform_device *pdev) +{ + struct da9052_pdata *pdata; + struct da9052 *da9052; + struct led_platform_data *pled; + struct da9052_led *led = NULL; + int error = -ENODEV; + int i; + + da9052 = dev_get_drvdata(pdev->dev.parent); + pdata = da9052->dev->platform_data; + if (pdata == NULL) { + dev_err(&pdev->dev, "No platform data\n"); + goto err; + } + + pled = pdata->pled; + if (pled == NULL) { + dev_err(&pdev->dev, "No platform data for LED\n"); + goto err; + } + + led = devm_kzalloc(&pdev->dev, + sizeof(struct da9052_led) * pled->num_leds, + GFP_KERNEL); + if (led == NULL) { + dev_err(&pdev->dev, "Failed to alloc memory\n"); + error = -ENOMEM; + goto err; + } + + for (i = 0; i < pled->num_leds; i++) { + led[i].cdev.name = pled->leds[i].name; + led[i].cdev.brightness_set = da9052_led_set; + led[i].cdev.brightness = LED_OFF; + led[i].cdev.max_brightness = DA9052_MAX_BRIGHTNESS; + led[i].brightness = LED_OFF; + led[i].led_index = pled->leds[i].flags; + led[i].da9052 = dev_get_drvdata(pdev->dev.parent); + INIT_WORK(&led[i].work, da9052_led_work); + + error = led_classdev_register(pdev->dev.parent, &led[i].cdev); + if (error) { + dev_err(&pdev->dev, "Failed to register led %d\n", + led[i].led_index); + goto err_register; + } + + error = da9052_set_led_brightness(&led[i]); + if (error) { + dev_err(&pdev->dev, "Unable to init led %d\n", + led[i].led_index); + continue; + } + } + error = da9052_configure_leds(led->da9052); + if (error) { + dev_err(&pdev->dev, "Failed to configure GPIO LED%d\n", error); + goto err_register; + } + + platform_set_drvdata(pdev, led); + + return 0; + +err_register: + for (i = i - 1; i >= 0; i--) { + led_classdev_unregister(&led[i].cdev); + cancel_work_sync(&led[i].work); + } +err: + return error; +} + +static int __devexit da9052_led_remove(struct platform_device *pdev) +{ + struct da9052_led *led = platform_get_drvdata(pdev); + struct da9052_pdata *pdata; + struct da9052 *da9052; + struct led_platform_data *pled; + int i; + + da9052 = dev_get_drvdata(pdev->dev.parent); + pdata = da9052->dev->platform_data; + pled = pdata->pled; + + for (i = 0; i < pled->num_leds; i++) { + led[i].brightness = 0; + da9052_set_led_brightness(&led[i]); + led_classdev_unregister(&led[i].cdev); + cancel_work_sync(&led[i].work); + } + + return 0; +} + +static struct platform_driver da9052_led_driver = { + .driver = { + .name = "da9052-leds", + .owner = THIS_MODULE, + }, + .probe = da9052_led_probe, + .remove = __devexit_p(da9052_led_remove), +}; + +module_platform_driver(da9052_led_driver); + +MODULE_AUTHOR("Dialog Semiconductor Ltd <dchen@diasemi.com>"); +MODULE_DESCRIPTION("LED driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-lm3530.c b/drivers/leds/leds-lm3530.c index 968fd5fef4fc..84ba6de8039c 100644 --- a/drivers/leds/leds-lm3530.c +++ b/drivers/leds/leds-lm3530.c @@ -113,6 +113,18 @@ struct lm3530_data { bool enable; }; +/* + * struct lm3530_als_data + * @config : value of ALS configuration register + * @imp_sel : value of ALS resistor select register + * @zone : values of ALS ZB(Zone Boundary) registers + */ +struct lm3530_als_data { + u8 config; + u8 imp_sel; + u8 zones[LM3530_ALS_ZB_MAX]; +}; + static const u8 lm3530_reg[LM3530_REG_MAX] = { LM3530_GEN_CONFIG, LM3530_ALS_CONFIG, @@ -141,29 +153,65 @@ static int lm3530_get_mode_from_str(const char *str) return -1; } +static void lm3530_als_configure(struct lm3530_platform_data *pdata, + struct lm3530_als_data *als) +{ + int i; + u32 als_vmin, als_vmax, als_vstep; + + if (pdata->als_vmax == 0) { + pdata->als_vmin = 0; + pdata->als_vmax = LM3530_ALS_WINDOW_mV; + } + + als_vmin = pdata->als_vmin; + als_vmax = pdata->als_vmax; + + if ((als_vmax - als_vmin) > LM3530_ALS_WINDOW_mV) + pdata->als_vmax = als_vmax = als_vmin + LM3530_ALS_WINDOW_mV; + + /* n zone boundary makes n+1 zones */ + als_vstep = (als_vmax - als_vmin) / (LM3530_ALS_ZB_MAX + 1); + + for (i = 0; i < LM3530_ALS_ZB_MAX; i++) + als->zones[i] = (((als_vmin + LM3530_ALS_OFFSET_mV) + + als_vstep + (i * als_vstep)) * LED_FULL) / 1000; + + als->config = + (pdata->als_avrg_time << LM3530_ALS_AVG_TIME_SHIFT) | + (LM3530_ENABLE_ALS) | + (pdata->als_input_mode << LM3530_ALS_SEL_SHIFT); + + als->imp_sel = + (pdata->als1_resistor_sel << LM3530_ALS1_IMP_SHIFT) | + (pdata->als2_resistor_sel << LM3530_ALS2_IMP_SHIFT); +} + 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]; - u8 zones[LM3530_ALS_ZB_MAX]; - u32 als_vmin, als_vmax, als_vstep; struct lm3530_platform_data *pdata = drvdata->pdata; struct i2c_client *client = drvdata->client; struct lm3530_pwm_data *pwm = &pdata->pwm_data; + struct lm3530_als_data als; + + memset(&als, 0, sizeof(struct lm3530_als_data)); gen_config = (pdata->brt_ramp_law << LM3530_RAMP_LAW_SHIFT) | ((pdata->max_current & 7) << LM3530_MAX_CURR_SHIFT); switch (drvdata->mode) { case LM3530_BL_MODE_MANUAL: + gen_config |= LM3530_ENABLE_I2C; + break; case LM3530_BL_MODE_ALS: gen_config |= LM3530_ENABLE_I2C; + lm3530_als_configure(pdata, &als); break; case LM3530_BL_MODE_PWM: gen_config |= LM3530_ENABLE_PWM | LM3530_ENABLE_PWM_SIMPLE | @@ -171,38 +219,6 @@ static int lm3530_init_registers(struct lm3530_data *drvdata) break; } - if (drvdata->mode == LM3530_BL_MODE_ALS) { - if (pdata->als_vmax == 0) { - pdata->als_vmin = 0; - pdata->als_vmax = LM3530_ALS_WINDOW_mV; - } - - als_vmin = pdata->als_vmin; - als_vmax = pdata->als_vmax; - - if ((als_vmax - als_vmin) > LM3530_ALS_WINDOW_mV) - pdata->als_vmax = als_vmax = - als_vmin + LM3530_ALS_WINDOW_mV; - - /* n zone boundary makes n+1 zones */ - als_vstep = (als_vmax - als_vmin) / (LM3530_ALS_ZB_MAX + 1); - - for (i = 0; i < LM3530_ALS_ZB_MAX; i++) - zones[i] = (((als_vmin + LM3530_ALS_OFFSET_mV) + - als_vstep + (i * als_vstep)) * LED_FULL) - / 1000; - - als_config = - (pdata->als_avrg_time << LM3530_ALS_AVG_TIME_SHIFT) | - (LM3530_ENABLE_ALS) | - (pdata->als_input_mode << LM3530_ALS_SEL_SHIFT); - - als_imp_sel = - (pdata->als1_resistor_sel << LM3530_ALS1_IMP_SHIFT) | - (pdata->als2_resistor_sel << LM3530_ALS2_IMP_SHIFT); - - } - brt_ramp = (pdata->brt_ramp_fall << LM3530_BRT_RAMP_FALL_SHIFT) | (pdata->brt_ramp_rise << LM3530_BRT_RAMP_RISE_SHIFT); @@ -215,14 +231,14 @@ static int lm3530_init_registers(struct lm3530_data *drvdata) brightness = drvdata->led_dev.max_brightness; reg_val[0] = gen_config; /* LM3530_GEN_CONFIG */ - reg_val[1] = als_config; /* LM3530_ALS_CONFIG */ + reg_val[1] = als.config; /* LM3530_ALS_CONFIG */ reg_val[2] = brt_ramp; /* LM3530_BRT_RAMP_RATE */ - reg_val[3] = als_imp_sel; /* LM3530_ALS_IMP_SELECT */ + reg_val[3] = als.imp_sel; /* LM3530_ALS_IMP_SELECT */ reg_val[4] = brightness; /* LM3530_BRT_CTRL_REG */ - reg_val[5] = zones[0]; /* LM3530_ALS_ZB0_REG */ - reg_val[6] = zones[1]; /* LM3530_ALS_ZB1_REG */ - reg_val[7] = zones[2]; /* LM3530_ALS_ZB2_REG */ - reg_val[8] = zones[3]; /* LM3530_ALS_ZB3_REG */ + reg_val[5] = als.zones[0]; /* LM3530_ALS_ZB0_REG */ + reg_val[6] = als.zones[1]; /* LM3530_ALS_ZB1_REG */ + reg_val[7] = als.zones[2]; /* LM3530_ALS_ZB2_REG */ + reg_val[8] = als.zones[3]; /* LM3530_ALS_ZB3_REG */ reg_val[9] = LM3530_DEF_ZT_0; /* LM3530_ALS_Z0T_REG */ reg_val[10] = LM3530_DEF_ZT_1; /* LM3530_ALS_Z1T_REG */ reg_val[11] = LM3530_DEF_ZT_2; /* LM3530_ALS_Z2T_REG */ diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c new file mode 100644 index 000000000000..f56b6e7ffdac --- /dev/null +++ b/drivers/leds/leds-lm3533.c @@ -0,0 +1,785 @@ +/* + * leds-lm3533.c -- LM3533 LED driver + * + * Copyright (C) 2011-2012 Texas Instruments + * + * Author: Johan Hovold <jhovold@gmail.com> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/leds.h> +#include <linux/mfd/core.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#include <linux/mfd/lm3533.h> + + +#define LM3533_LVCTRLBANK_MIN 2 +#define LM3533_LVCTRLBANK_MAX 5 +#define LM3533_LVCTRLBANK_COUNT 4 +#define LM3533_RISEFALLTIME_MAX 7 +#define LM3533_ALS_CHANNEL_LV_MIN 1 +#define LM3533_ALS_CHANNEL_LV_MAX 2 + +#define LM3533_REG_CTRLBANK_BCONF_BASE 0x1b +#define LM3533_REG_PATTERN_ENABLE 0x28 +#define LM3533_REG_PATTERN_LOW_TIME_BASE 0x71 +#define LM3533_REG_PATTERN_HIGH_TIME_BASE 0x72 +#define LM3533_REG_PATTERN_RISETIME_BASE 0x74 +#define LM3533_REG_PATTERN_FALLTIME_BASE 0x75 + +#define LM3533_REG_PATTERN_STEP 0x10 + +#define LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK 0x04 +#define LM3533_REG_CTRLBANK_BCONF_ALS_EN_MASK 0x02 +#define LM3533_REG_CTRLBANK_BCONF_ALS_CHANNEL_MASK 0x01 + +#define LM3533_LED_FLAG_PATTERN_ENABLE 1 + + +struct lm3533_led { + struct lm3533 *lm3533; + struct lm3533_ctrlbank cb; + struct led_classdev cdev; + int id; + + struct mutex mutex; + unsigned long flags; + + struct work_struct work; + u8 new_brightness; +}; + + +static inline struct lm3533_led *to_lm3533_led(struct led_classdev *cdev) +{ + return container_of(cdev, struct lm3533_led, cdev); +} + +static inline int lm3533_led_get_ctrlbank_id(struct lm3533_led *led) +{ + return led->id + 2; +} + +static inline u8 lm3533_led_get_lv_reg(struct lm3533_led *led, u8 base) +{ + return base + led->id; +} + +static inline u8 lm3533_led_get_pattern(struct lm3533_led *led) +{ + return led->id; +} + +static inline u8 lm3533_led_get_pattern_reg(struct lm3533_led *led, + u8 base) +{ + return base + lm3533_led_get_pattern(led) * LM3533_REG_PATTERN_STEP; +} + +static int lm3533_led_pattern_enable(struct lm3533_led *led, int enable) +{ + u8 mask; + u8 val; + int pattern; + int state; + int ret = 0; + + dev_dbg(led->cdev.dev, "%s - %d\n", __func__, enable); + + mutex_lock(&led->mutex); + + state = test_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags); + if ((enable && state) || (!enable && !state)) + goto out; + + pattern = lm3533_led_get_pattern(led); + mask = 1 << (2 * pattern); + + if (enable) + val = mask; + else + val = 0; + + ret = lm3533_update(led->lm3533, LM3533_REG_PATTERN_ENABLE, val, mask); + if (ret) { + dev_err(led->cdev.dev, "failed to enable pattern %d (%d)\n", + pattern, enable); + goto out; + } + + __change_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags); +out: + mutex_unlock(&led->mutex); + + return ret; +} + +static void lm3533_led_work(struct work_struct *work) +{ + struct lm3533_led *led = container_of(work, struct lm3533_led, work); + + dev_dbg(led->cdev.dev, "%s - %u\n", __func__, led->new_brightness); + + if (led->new_brightness == 0) + lm3533_led_pattern_enable(led, 0); /* disable blink */ + + lm3533_ctrlbank_set_brightness(&led->cb, led->new_brightness); +} + +static void lm3533_led_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct lm3533_led *led = to_lm3533_led(cdev); + + dev_dbg(led->cdev.dev, "%s - %d\n", __func__, value); + + led->new_brightness = value; + schedule_work(&led->work); +} + +static enum led_brightness lm3533_led_get(struct led_classdev *cdev) +{ + struct lm3533_led *led = to_lm3533_led(cdev); + u8 val; + int ret; + + ret = lm3533_ctrlbank_get_brightness(&led->cb, &val); + if (ret) + return ret; + + dev_dbg(led->cdev.dev, "%s - %u\n", __func__, val); + + return val; +} + +/* Pattern generator defines (delays in us). */ +#define LM3533_LED_DELAY1_VMIN 0x00 +#define LM3533_LED_DELAY2_VMIN 0x3d +#define LM3533_LED_DELAY3_VMIN 0x80 + +#define LM3533_LED_DELAY1_VMAX (LM3533_LED_DELAY2_VMIN - 1) +#define LM3533_LED_DELAY2_VMAX (LM3533_LED_DELAY3_VMIN - 1) +#define LM3533_LED_DELAY3_VMAX 0xff + +#define LM3533_LED_DELAY1_TMIN 16384U +#define LM3533_LED_DELAY2_TMIN 1130496U +#define LM3533_LED_DELAY3_TMIN 10305536U + +#define LM3533_LED_DELAY1_TMAX 999424U +#define LM3533_LED_DELAY2_TMAX 9781248U +#define LM3533_LED_DELAY3_TMAX 76890112U + +/* t_step = (t_max - t_min) / (v_max - v_min) */ +#define LM3533_LED_DELAY1_TSTEP 16384 +#define LM3533_LED_DELAY2_TSTEP 131072 +#define LM3533_LED_DELAY3_TSTEP 524288 + +/* Delay limits for hardware accelerated blinking (in ms). */ +#define LM3533_LED_DELAY_ON_MAX \ + ((LM3533_LED_DELAY2_TMAX + LM3533_LED_DELAY2_TSTEP / 2) / 1000) +#define LM3533_LED_DELAY_OFF_MAX \ + ((LM3533_LED_DELAY3_TMAX + LM3533_LED_DELAY3_TSTEP / 2) / 1000) + +/* + * Returns linear map of *t from [t_min,t_max] to [v_min,v_max] with a step + * size of t_step, where + * + * t_step = (t_max - t_min) / (v_max - v_min) + * + * and updates *t to reflect the mapped value. + */ +static u8 time_to_val(unsigned *t, unsigned t_min, unsigned t_step, + u8 v_min, u8 v_max) +{ + unsigned val; + + val = (*t + t_step / 2 - t_min) / t_step + v_min; + + *t = t_step * (val - v_min) + t_min; + + return (u8)val; +} + +/* + * Returns time code corresponding to *delay (in ms) and updates *delay to + * reflect actual hardware delay. + * + * Hardware supports 256 discrete delay times, divided into three groups with + * the following ranges and step-sizes: + * + * [ 16, 999] [0x00, 0x3e] step 16 ms + * [ 1130, 9781] [0x3d, 0x7f] step 131 ms + * [10306, 76890] [0x80, 0xff] step 524 ms + * + * Note that delay group 3 is only available for delay_off. + */ +static u8 lm3533_led_get_hw_delay(unsigned *delay) +{ + unsigned t; + u8 val; + + t = *delay * 1000; + + if (t >= (LM3533_LED_DELAY2_TMAX + LM3533_LED_DELAY3_TMIN) / 2) { + t = clamp(t, LM3533_LED_DELAY3_TMIN, LM3533_LED_DELAY3_TMAX); + val = time_to_val(&t, LM3533_LED_DELAY3_TMIN, + LM3533_LED_DELAY3_TSTEP, + LM3533_LED_DELAY3_VMIN, + LM3533_LED_DELAY3_VMAX); + } else if (t >= (LM3533_LED_DELAY1_TMAX + LM3533_LED_DELAY2_TMIN) / 2) { + t = clamp(t, LM3533_LED_DELAY2_TMIN, LM3533_LED_DELAY2_TMAX); + val = time_to_val(&t, LM3533_LED_DELAY2_TMIN, + LM3533_LED_DELAY2_TSTEP, + LM3533_LED_DELAY2_VMIN, + LM3533_LED_DELAY2_VMAX); + } else { + t = clamp(t, LM3533_LED_DELAY1_TMIN, LM3533_LED_DELAY1_TMAX); + val = time_to_val(&t, LM3533_LED_DELAY1_TMIN, + LM3533_LED_DELAY1_TSTEP, + LM3533_LED_DELAY1_VMIN, + LM3533_LED_DELAY1_VMAX); + } + + *delay = (t + 500) / 1000; + + return val; +} + +/* + * Set delay register base to *delay (in ms) and update *delay to reflect + * actual hardware delay used. + */ +static u8 lm3533_led_delay_set(struct lm3533_led *led, u8 base, + unsigned long *delay) +{ + unsigned t; + u8 val; + u8 reg; + int ret; + + t = (unsigned)*delay; + + /* Delay group 3 is only available for low time (delay off). */ + if (base != LM3533_REG_PATTERN_LOW_TIME_BASE) + t = min(t, LM3533_LED_DELAY2_TMAX / 1000); + + val = lm3533_led_get_hw_delay(&t); + + dev_dbg(led->cdev.dev, "%s - %lu: %u (0x%02x)\n", __func__, + *delay, t, val); + reg = lm3533_led_get_pattern_reg(led, base); + ret = lm3533_write(led->lm3533, reg, val); + if (ret) + dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg); + + *delay = t; + + return ret; +} + +static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t) +{ + return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t); +} + +static int lm3533_led_delay_off_set(struct lm3533_led *led, unsigned long *t) +{ + return lm3533_led_delay_set(led, LM3533_REG_PATTERN_LOW_TIME_BASE, t); +} + +static int lm3533_led_blink_set(struct led_classdev *cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct lm3533_led *led = to_lm3533_led(cdev); + int ret; + + dev_dbg(led->cdev.dev, "%s - on = %lu, off = %lu\n", __func__, + *delay_on, *delay_off); + + if (*delay_on > LM3533_LED_DELAY_ON_MAX || + *delay_off > LM3533_LED_DELAY_OFF_MAX) + return -EINVAL; + + if (*delay_on == 0 && *delay_off == 0) { + *delay_on = 500; + *delay_off = 500; + } + + ret = lm3533_led_delay_on_set(led, delay_on); + if (ret) + return ret; + + ret = lm3533_led_delay_off_set(led, delay_off); + if (ret) + return ret; + + return lm3533_led_pattern_enable(led, 1); +} + +static ssize_t show_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", led->id); +} + +/* + * Pattern generator rise/fall times: + * + * 0 - 2048 us (default) + * 1 - 262 ms + * 2 - 524 ms + * 3 - 1.049 s + * 4 - 2.097 s + * 5 - 4.194 s + * 6 - 8.389 s + * 7 - 16.78 s + */ +static ssize_t show_risefalltime(struct device *dev, + struct device_attribute *attr, + char *buf, u8 base) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + ssize_t ret; + u8 reg; + u8 val; + + reg = lm3533_led_get_pattern_reg(led, base); + ret = lm3533_read(led->lm3533, reg, &val); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%x\n", val); +} + +static ssize_t show_risetime(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return show_risefalltime(dev, attr, buf, + LM3533_REG_PATTERN_RISETIME_BASE); +} + +static ssize_t show_falltime(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return show_risefalltime(dev, attr, buf, + LM3533_REG_PATTERN_FALLTIME_BASE); +} + +static ssize_t store_risefalltime(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, u8 base) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + u8 val; + u8 reg; + int ret; + + if (kstrtou8(buf, 0, &val) || val > LM3533_RISEFALLTIME_MAX) + return -EINVAL; + + reg = lm3533_led_get_pattern_reg(led, base); + ret = lm3533_write(led->lm3533, reg, val); + if (ret) + return ret; + + return len; +} + +static ssize_t store_risetime(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + return store_risefalltime(dev, attr, buf, len, + LM3533_REG_PATTERN_RISETIME_BASE); +} + +static ssize_t store_falltime(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + return store_risefalltime(dev, attr, buf, len, + LM3533_REG_PATTERN_FALLTIME_BASE); +} + +static ssize_t show_als_channel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + unsigned channel; + u8 reg; + u8 val; + int ret; + + reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE); + ret = lm3533_read(led->lm3533, reg, &val); + if (ret) + return ret; + + channel = (val & LM3533_REG_CTRLBANK_BCONF_ALS_CHANNEL_MASK) + 1; + + return scnprintf(buf, PAGE_SIZE, "%u\n", channel); +} + +static ssize_t store_als_channel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + unsigned channel; + u8 reg; + u8 val; + u8 mask; + int ret; + + if (kstrtouint(buf, 0, &channel)) + return -EINVAL; + + if (channel < LM3533_ALS_CHANNEL_LV_MIN || + channel > LM3533_ALS_CHANNEL_LV_MAX) + return -EINVAL; + + reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE); + mask = LM3533_REG_CTRLBANK_BCONF_ALS_CHANNEL_MASK; + val = channel - 1; + + ret = lm3533_update(led->lm3533, reg, val, mask); + if (ret) + return ret; + + return len; +} + +static ssize_t show_als_en(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + bool enable; + u8 reg; + u8 val; + int ret; + + reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE); + ret = lm3533_read(led->lm3533, reg, &val); + if (ret) + return ret; + + enable = val & LM3533_REG_CTRLBANK_BCONF_ALS_EN_MASK; + + return scnprintf(buf, PAGE_SIZE, "%d\n", enable); +} + +static ssize_t store_als_en(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + unsigned enable; + u8 reg; + u8 mask; + u8 val; + int ret; + + if (kstrtouint(buf, 0, &enable)) + return -EINVAL; + + reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE); + mask = LM3533_REG_CTRLBANK_BCONF_ALS_EN_MASK; + + if (enable) + val = mask; + else + val = 0; + + ret = lm3533_update(led->lm3533, reg, val, mask); + if (ret) + return ret; + + return len; +} + +static ssize_t show_linear(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + u8 reg; + u8 val; + int linear; + int ret; + + reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE); + ret = lm3533_read(led->lm3533, reg, &val); + if (ret) + return ret; + + if (val & LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK) + linear = 1; + else + linear = 0; + + return scnprintf(buf, PAGE_SIZE, "%x\n", linear); +} + +static ssize_t store_linear(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + unsigned long linear; + u8 reg; + u8 mask; + u8 val; + int ret; + + if (kstrtoul(buf, 0, &linear)) + return -EINVAL; + + reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE); + mask = LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK; + + if (linear) + val = mask; + else + val = 0; + + ret = lm3533_update(led->lm3533, reg, val, mask); + if (ret) + return ret; + + return len; +} + +static ssize_t show_pwm(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + u8 val; + int ret; + + ret = lm3533_ctrlbank_get_pwm(&led->cb, &val); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t store_pwm(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + u8 val; + int ret; + + if (kstrtou8(buf, 0, &val)) + return -EINVAL; + + ret = lm3533_ctrlbank_set_pwm(&led->cb, val); + if (ret) + return ret; + + return len; +} + +static LM3533_ATTR_RW(als_channel); +static LM3533_ATTR_RW(als_en); +static LM3533_ATTR_RW(falltime); +static LM3533_ATTR_RO(id); +static LM3533_ATTR_RW(linear); +static LM3533_ATTR_RW(pwm); +static LM3533_ATTR_RW(risetime); + +static struct attribute *lm3533_led_attributes[] = { + &dev_attr_als_channel.attr, + &dev_attr_als_en.attr, + &dev_attr_falltime.attr, + &dev_attr_id.attr, + &dev_attr_linear.attr, + &dev_attr_pwm.attr, + &dev_attr_risetime.attr, + NULL, +}; + +static umode_t lm3533_led_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + umode_t mode = attr->mode; + + if (attr == &dev_attr_als_channel.attr || + attr == &dev_attr_als_en.attr) { + if (!led->lm3533->have_als) + mode = 0; + } + + return mode; +}; + +static struct attribute_group lm3533_led_attribute_group = { + .is_visible = lm3533_led_attr_is_visible, + .attrs = lm3533_led_attributes +}; + +static int __devinit lm3533_led_setup(struct lm3533_led *led, + struct lm3533_led_platform_data *pdata) +{ + int ret; + + ret = lm3533_ctrlbank_set_max_current(&led->cb, pdata->max_current); + if (ret) + return ret; + + return lm3533_ctrlbank_set_pwm(&led->cb, pdata->pwm); +} + +static int __devinit lm3533_led_probe(struct platform_device *pdev) +{ + struct lm3533 *lm3533; + struct lm3533_led_platform_data *pdata; + struct lm3533_led *led; + int ret; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + lm3533 = dev_get_drvdata(pdev->dev.parent); + if (!lm3533) + return -EINVAL; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data\n"); + return -EINVAL; + } + + if (pdev->id < 0 || pdev->id >= LM3533_LVCTRLBANK_COUNT) { + dev_err(&pdev->dev, "illegal LED id %d\n", pdev->id); + return -EINVAL; + } + + led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->lm3533 = lm3533; + led->cdev.name = pdata->name; + led->cdev.default_trigger = pdata->default_trigger; + led->cdev.brightness_set = lm3533_led_set; + led->cdev.brightness_get = lm3533_led_get; + led->cdev.blink_set = lm3533_led_blink_set; + led->cdev.brightness = LED_OFF; + led->id = pdev->id; + + mutex_init(&led->mutex); + INIT_WORK(&led->work, lm3533_led_work); + + /* The class framework makes a callback to get brightness during + * registration so use parent device (for error reporting) until + * registered. + */ + led->cb.lm3533 = lm3533; + led->cb.id = lm3533_led_get_ctrlbank_id(led); + led->cb.dev = lm3533->dev; + + platform_set_drvdata(pdev, led); + + ret = led_classdev_register(pdev->dev.parent, &led->cdev); + if (ret) { + dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id); + return ret; + } + + led->cb.dev = led->cdev.dev; + + ret = sysfs_create_group(&led->cdev.dev->kobj, + &lm3533_led_attribute_group); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create sysfs attributes\n"); + goto err_unregister; + } + + ret = lm3533_led_setup(led, pdata); + if (ret) + goto err_sysfs_remove; + + ret = lm3533_ctrlbank_enable(&led->cb); + if (ret) + goto err_sysfs_remove; + + return 0; + +err_sysfs_remove: + sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group); +err_unregister: + led_classdev_unregister(&led->cdev); + flush_work_sync(&led->work); + + return ret; +} + +static int __devexit lm3533_led_remove(struct platform_device *pdev) +{ + struct lm3533_led *led = platform_get_drvdata(pdev); + + dev_dbg(&pdev->dev, "%s\n", __func__); + + lm3533_ctrlbank_disable(&led->cb); + sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group); + led_classdev_unregister(&led->cdev); + flush_work_sync(&led->work); + + return 0; +} + +static void lm3533_led_shutdown(struct platform_device *pdev) +{ + + struct lm3533_led *led = platform_get_drvdata(pdev); + + dev_dbg(&pdev->dev, "%s\n", __func__); + + lm3533_ctrlbank_disable(&led->cb); + lm3533_led_set(&led->cdev, LED_OFF); /* disable blink */ + flush_work_sync(&led->work); +} + +static struct platform_driver lm3533_led_driver = { + .driver = { + .name = "lm3533-leds", + .owner = THIS_MODULE, + }, + .probe = lm3533_led_probe, + .remove = __devexit_p(lm3533_led_remove), + .shutdown = lm3533_led_shutdown, +}; +module_platform_driver(lm3533_led_driver); + +MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>"); +MODULE_DESCRIPTION("LM3533 LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lm3533-leds"); diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index 410a723b8691..23815624f35e 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -193,9 +193,14 @@ static int lp5521_load_program(struct lp5521_engine *eng, const u8 *pattern) /* move current engine to direct mode and remember the state */ ret = lp5521_set_engine_mode(eng, LP5521_CMD_DIRECT); + if (ret) + return ret; + /* Mode change requires min 500 us delay. 1 - 2 ms with margin */ usleep_range(1000, 2000); - ret |= lp5521_read(client, LP5521_REG_OP_MODE, &mode); + ret = lp5521_read(client, LP5521_REG_OP_MODE, &mode); + if (ret) + return ret; /* For loading, all the engines to load mode */ lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); @@ -211,8 +216,7 @@ static int lp5521_load_program(struct lp5521_engine *eng, const u8 *pattern) LP5521_PROG_MEM_SIZE, pattern); - ret |= lp5521_write(client, LP5521_REG_OP_MODE, mode); - return ret; + return lp5521_write(client, LP5521_REG_OP_MODE, mode); } static int lp5521_set_led_current(struct lp5521_chip *chip, int led, u8 curr) @@ -785,7 +789,7 @@ static int __devinit lp5521_probe(struct i2c_client *client, * LP5521_REG_ENABLE register will not have any effect - strange! */ ret = lp5521_read(client, LP5521_REG_R_CURRENT, &buf); - if (buf != LP5521_REG_R_CURR_DEFAULT) { + if (ret || buf != LP5521_REG_R_CURR_DEFAULT) { dev_err(&client->dev, "error in resetting chip\n"); goto fail2; } diff --git a/drivers/leds/leds-mc13783.c b/drivers/leds/leds-mc13783.c index 8bc491541550..4cc6a2e3df34 100644 --- a/drivers/leds/leds-mc13783.c +++ b/drivers/leds/leds-mc13783.c @@ -280,7 +280,7 @@ static int __devinit mc13783_led_probe(struct platform_device *pdev) return -EINVAL; } - led = kzalloc(sizeof(*led) * pdata->num_leds, GFP_KERNEL); + led = kcalloc(pdata->num_leds, sizeof(*led), GFP_KERNEL); if (led == NULL) { dev_err(&pdev->dev, "failed to alloc memory\n"); return -ENOMEM; diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index dcc3bc3d38db..5f462dbf0dbb 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -101,11 +101,16 @@ static const struct i2c_device_id pca955x_id[] = { }; MODULE_DEVICE_TABLE(i2c, pca955x_id); -struct pca955x_led { +struct pca955x { + struct mutex lock; + struct pca955x_led *leds; struct pca955x_chipdef *chipdef; struct i2c_client *client; +}; + +struct pca955x_led { + struct pca955x *pca955x; struct work_struct work; - spinlock_t lock; enum led_brightness brightness; struct led_classdev led_cdev; int led_num; /* 0 .. 15 potentially */ @@ -140,7 +145,7 @@ static inline u8 pca955x_ledsel(u8 oldval, int led_num, int state) */ static void pca955x_write_psc(struct i2c_client *client, int n, u8 val) { - struct pca955x_led *pca955x = i2c_get_clientdata(client); + struct pca955x *pca955x = i2c_get_clientdata(client); i2c_smbus_write_byte_data(client, pca95xx_num_input_regs(pca955x->chipdef->bits) + 2*n, @@ -156,7 +161,7 @@ static void pca955x_write_psc(struct i2c_client *client, int n, u8 val) */ static void pca955x_write_pwm(struct i2c_client *client, int n, u8 val) { - struct pca955x_led *pca955x = i2c_get_clientdata(client); + struct pca955x *pca955x = i2c_get_clientdata(client); i2c_smbus_write_byte_data(client, pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + 2*n, @@ -169,7 +174,7 @@ static void pca955x_write_pwm(struct i2c_client *client, int n, u8 val) */ static void pca955x_write_ls(struct i2c_client *client, int n, u8 val) { - struct pca955x_led *pca955x = i2c_get_clientdata(client); + struct pca955x *pca955x = i2c_get_clientdata(client); i2c_smbus_write_byte_data(client, pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n, @@ -182,7 +187,7 @@ static void pca955x_write_ls(struct i2c_client *client, int n, u8 val) */ static u8 pca955x_read_ls(struct i2c_client *client, int n) { - struct pca955x_led *pca955x = i2c_get_clientdata(client); + struct pca955x *pca955x = i2c_get_clientdata(client); return (u8) i2c_smbus_read_byte_data(client, pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n); @@ -190,18 +195,23 @@ static u8 pca955x_read_ls(struct i2c_client *client, int n) static void pca955x_led_work(struct work_struct *work) { - struct pca955x_led *pca955x; + struct pca955x_led *pca955x_led; + struct pca955x *pca955x; u8 ls; int chip_ls; /* which LSx to use (0-3 potentially) */ int ls_led; /* which set of bits within LSx to use (0-3) */ - pca955x = container_of(work, struct pca955x_led, work); - chip_ls = pca955x->led_num / 4; - ls_led = pca955x->led_num % 4; + pca955x_led = container_of(work, struct pca955x_led, work); + pca955x = pca955x_led->pca955x; + + chip_ls = pca955x_led->led_num / 4; + ls_led = pca955x_led->led_num % 4; + + mutex_lock(&pca955x->lock); ls = pca955x_read_ls(pca955x->client, chip_ls); - switch (pca955x->brightness) { + switch (pca955x_led->brightness) { case LED_FULL: ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_ON); break; @@ -219,12 +229,15 @@ static void pca955x_led_work(struct work_struct *work) * OFF, HALF, or FULL. But, this is probably better than * just turning off for all other values. */ - pca955x_write_pwm(pca955x->client, 1, 255-pca955x->brightness); + pca955x_write_pwm(pca955x->client, 1, + 255 - pca955x_led->brightness); ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK1); break; } pca955x_write_ls(pca955x->client, chip_ls, ls); + + mutex_unlock(&pca955x->lock); } static void pca955x_led_set(struct led_classdev *led_cdev, enum led_brightness value) @@ -233,7 +246,6 @@ static void pca955x_led_set(struct led_classdev *led_cdev, enum led_brightness v pca955x = container_of(led_cdev, struct pca955x_led, led_cdev); - spin_lock(&pca955x->lock); pca955x->brightness = value; /* @@ -241,14 +253,13 @@ static void pca955x_led_set(struct led_classdev *led_cdev, enum led_brightness v * can sleep. */ schedule_work(&pca955x->work); - - spin_unlock(&pca955x->lock); } static int __devinit pca955x_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct pca955x_led *pca955x; + struct pca955x *pca955x; + struct pca955x_led *pca955x_led; struct pca955x_chipdef *chip; struct i2c_adapter *adapter; struct led_platform_data *pdata; @@ -282,39 +293,48 @@ static int __devinit pca955x_probe(struct i2c_client *client, } } - pca955x = kzalloc(sizeof(*pca955x) * chip->bits, GFP_KERNEL); + pca955x = kzalloc(sizeof(*pca955x), GFP_KERNEL); if (!pca955x) return -ENOMEM; + pca955x->leds = kzalloc(sizeof(*pca955x_led) * chip->bits, GFP_KERNEL); + if (!pca955x->leds) { + err = -ENOMEM; + goto exit_nomem; + } + i2c_set_clientdata(client, pca955x); + mutex_init(&pca955x->lock); + pca955x->client = client; + pca955x->chipdef = chip; + for (i = 0; i < chip->bits; i++) { - pca955x[i].chipdef = chip; - pca955x[i].client = client; - pca955x[i].led_num = i; + pca955x_led = &pca955x->leds[i]; + pca955x_led->led_num = i; + pca955x_led->pca955x = pca955x; /* Platform data can specify LED names and default triggers */ if (pdata) { if (pdata->leds[i].name) - snprintf(pca955x[i].name, - sizeof(pca955x[i].name), "pca955x:%s", - pdata->leds[i].name); + snprintf(pca955x_led->name, + sizeof(pca955x_led->name), "pca955x:%s", + pdata->leds[i].name); if (pdata->leds[i].default_trigger) - pca955x[i].led_cdev.default_trigger = + pca955x_led->led_cdev.default_trigger = pdata->leds[i].default_trigger; } else { - snprintf(pca955x[i].name, sizeof(pca955x[i].name), + snprintf(pca955x_led->name, sizeof(pca955x_led->name), "pca955x:%d", i); } - spin_lock_init(&pca955x[i].lock); - - pca955x[i].led_cdev.name = pca955x[i].name; - pca955x[i].led_cdev.brightness_set = pca955x_led_set; + pca955x_led->led_cdev.name = pca955x_led->name; + pca955x_led->led_cdev.brightness_set = pca955x_led_set; - INIT_WORK(&pca955x[i].work, pca955x_led_work); + INIT_WORK(&pca955x_led->work, pca955x_led_work); - err = led_classdev_register(&client->dev, &pca955x[i].led_cdev); + err = led_classdev_register(&client->dev, + &pca955x_led->led_cdev); if (err < 0) goto exit; } @@ -337,10 +357,12 @@ static int __devinit pca955x_probe(struct i2c_client *client, exit: while (i--) { - led_classdev_unregister(&pca955x[i].led_cdev); - cancel_work_sync(&pca955x[i].work); + led_classdev_unregister(&pca955x->leds[i].led_cdev); + cancel_work_sync(&pca955x->leds[i].work); } + kfree(pca955x->leds); +exit_nomem: kfree(pca955x); return err; @@ -348,14 +370,15 @@ exit: static int __devexit pca955x_remove(struct i2c_client *client) { - struct pca955x_led *pca955x = i2c_get_clientdata(client); + struct pca955x *pca955x = i2c_get_clientdata(client); int i; for (i = 0; i < pca955x->chipdef->bits; i++) { - led_classdev_unregister(&pca955x[i].led_cdev); - cancel_work_sync(&pca955x[i].work); + led_classdev_unregister(&pca955x->leds[i].led_cdev); + cancel_work_sync(&pca955x->leds[i].work); } + kfree(pca955x->leds); kfree(pca955x); return 0; diff --git a/drivers/leds/ledtrig-backlight.c b/drivers/leds/ledtrig-backlight.c index 2b513a2ad7de..e2726867c5d4 100644 --- a/drivers/leds/ledtrig-backlight.c +++ b/drivers/leds/ledtrig-backlight.c @@ -120,6 +120,7 @@ static void bl_trig_activate(struct led_classdev *led) ret = fb_register_client(&n->notifier); if (ret) dev_err(led->dev, "unable to register backlight trigger\n"); + led->activated = true; return; @@ -133,10 +134,11 @@ static void bl_trig_deactivate(struct led_classdev *led) struct bl_trig_notifier *n = (struct bl_trig_notifier *) led->trigger_data; - if (n) { + if (led->activated) { device_remove_file(led->dev, &dev_attr_inverted); fb_unregister_client(&n->notifier); kfree(n); + led->activated = false; } } diff --git a/drivers/leds/ledtrig-gpio.c b/drivers/leds/ledtrig-gpio.c index ecc4bf3f37a9..f057c101b896 100644 --- a/drivers/leds/ledtrig-gpio.c +++ b/drivers/leds/ledtrig-gpio.c @@ -200,6 +200,7 @@ static void gpio_trig_activate(struct led_classdev *led) gpio_data->led = led; led->trigger_data = gpio_data; INIT_WORK(&gpio_data->work, gpio_trig_work); + led->activated = true; return; @@ -217,7 +218,7 @@ static void gpio_trig_deactivate(struct led_classdev *led) { struct gpio_trig_data *gpio_data = led->trigger_data; - if (gpio_data) { + if (led->activated) { device_remove_file(led->dev, &dev_attr_gpio); device_remove_file(led->dev, &dev_attr_inverted); device_remove_file(led->dev, &dev_attr_desired_brightness); @@ -225,6 +226,7 @@ static void gpio_trig_deactivate(struct led_classdev *led) if (gpio_data->gpio != 0) free_irq(gpio_to_irq(gpio_data->gpio), led); kfree(gpio_data); + led->activated = false; } } diff --git a/drivers/leds/ledtrig-heartbeat.c b/drivers/leds/ledtrig-heartbeat.c index 759c0bba4a8f..41dc76db4311 100644 --- a/drivers/leds/ledtrig-heartbeat.c +++ b/drivers/leds/ledtrig-heartbeat.c @@ -18,6 +18,7 @@ #include <linux/timer.h> #include <linux/sched.h> #include <linux/leds.h> +#include <linux/reboot.h> #include "leds.h" struct heartbeat_trig_data { @@ -83,15 +84,17 @@ static void heartbeat_trig_activate(struct led_classdev *led_cdev) led_heartbeat_function, (unsigned long) led_cdev); heartbeat_data->phase = 0; led_heartbeat_function(heartbeat_data->timer.data); + led_cdev->activated = true; } static void heartbeat_trig_deactivate(struct led_classdev *led_cdev) { struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data; - if (heartbeat_data) { + if (led_cdev->activated) { del_timer_sync(&heartbeat_data->timer); kfree(heartbeat_data); + led_cdev->activated = false; } } @@ -101,13 +104,38 @@ static struct led_trigger heartbeat_led_trigger = { .deactivate = heartbeat_trig_deactivate, }; +static int heartbeat_reboot_notifier(struct notifier_block *nb, + unsigned long code, void *unused) +{ + led_trigger_unregister(&heartbeat_led_trigger); + return NOTIFY_DONE; +} + +static struct notifier_block heartbeat_reboot_nb = { + .notifier_call = heartbeat_reboot_notifier, +}; + +static struct notifier_block heartbeat_panic_nb = { + .notifier_call = heartbeat_reboot_notifier, +}; + static int __init heartbeat_trig_init(void) { - return led_trigger_register(&heartbeat_led_trigger); + int rc = led_trigger_register(&heartbeat_led_trigger); + + if (!rc) { + atomic_notifier_chain_register(&panic_notifier_list, + &heartbeat_panic_nb); + register_reboot_notifier(&heartbeat_reboot_nb); + } + return rc; } static void __exit heartbeat_trig_exit(void) { + unregister_reboot_notifier(&heartbeat_reboot_nb); + atomic_notifier_chain_unregister(&panic_notifier_list, + &heartbeat_panic_nb); led_trigger_unregister(&heartbeat_led_trigger); } diff --git a/drivers/leds/ledtrig-timer.c b/drivers/leds/ledtrig-timer.c index 328c64c0841c..9010f7abaf2c 100644 --- a/drivers/leds/ledtrig-timer.c +++ b/drivers/leds/ledtrig-timer.c @@ -31,21 +31,17 @@ static ssize_t led_delay_on_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); - int ret = -EINVAL; - char *after; - unsigned long state = simple_strtoul(buf, &after, 10); - size_t count = after - buf; - - if (isspace(*after)) - count++; - - if (count == size) { - led_blink_set(led_cdev, &state, &led_cdev->blink_delay_off); - led_cdev->blink_delay_on = state; - ret = count; - } + unsigned long state; + ssize_t ret = -EINVAL; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; - return ret; + led_blink_set(led_cdev, &state, &led_cdev->blink_delay_off); + led_cdev->blink_delay_on = state; + + return size; } static ssize_t led_delay_off_show(struct device *dev, @@ -60,21 +56,17 @@ static ssize_t led_delay_off_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); - int ret = -EINVAL; - char *after; - unsigned long state = simple_strtoul(buf, &after, 10); - size_t count = after - buf; - - if (isspace(*after)) - count++; - - if (count == size) { - led_blink_set(led_cdev, &led_cdev->blink_delay_on, &state); - led_cdev->blink_delay_off = state; - ret = count; - } + unsigned long state; + ssize_t ret = -EINVAL; - return ret; + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + led_blink_set(led_cdev, &led_cdev->blink_delay_on, &state); + led_cdev->blink_delay_off = state; + + return size; } static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store); @@ -95,8 +87,7 @@ static void timer_trig_activate(struct led_classdev *led_cdev) led_blink_set(led_cdev, &led_cdev->blink_delay_on, &led_cdev->blink_delay_off); - - led_cdev->trigger_data = (void *)1; + led_cdev->activated = true; return; @@ -106,9 +97,10 @@ err_out_delayon: static void timer_trig_deactivate(struct led_classdev *led_cdev) { - if (led_cdev->trigger_data) { + if (led_cdev->activated) { device_remove_file(led_cdev->dev, &dev_attr_delay_on); device_remove_file(led_cdev->dev, &dev_attr_delay_off); + led_cdev->activated = false; } /* Stop blinking */ diff --git a/drivers/leds/ledtrig-transient.c b/drivers/leds/ledtrig-transient.c new file mode 100644 index 000000000000..83179f435e1e --- /dev/null +++ b/drivers/leds/ledtrig-transient.c @@ -0,0 +1,237 @@ +/* + * LED Kernel Transient Trigger + * + * Copyright (C) 2012 Shuah Khan <shuahkhan@gmail.com> + * + * Based on Richard Purdie's ledtrig-timer.c and Atsushi Nemoto's + * ledtrig-heartbeat.c + * Design and use-case input from Jonas Bonn <jonas@southpole.se> and + * Neil Brown <neilb@suse.de> + * + * 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. + * + */ +/* + * Transient trigger allows one shot timer activation. Please refer to + * Documentation/leds/ledtrig-transient.txt for details +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/leds.h> +#include "leds.h" + +struct transient_trig_data { + int activate; + int state; + int restore_state; + unsigned long duration; + struct timer_list timer; +}; + +static void transient_timer_function(unsigned long data) +{ + struct led_classdev *led_cdev = (struct led_classdev *) data; + struct transient_trig_data *transient_data = led_cdev->trigger_data; + + transient_data->activate = 0; + led_set_brightness(led_cdev, transient_data->restore_state); +} + +static ssize_t transient_activate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct transient_trig_data *transient_data = led_cdev->trigger_data; + + return sprintf(buf, "%d\n", transient_data->activate); +} + +static ssize_t transient_activate_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct transient_trig_data *transient_data = led_cdev->trigger_data; + unsigned long state; + ssize_t ret; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + if (state != 1 && state != 0) + return -EINVAL; + + /* cancel the running timer */ + if (state == 0 && transient_data->activate == 1) { + del_timer(&transient_data->timer); + transient_data->activate = state; + led_set_brightness(led_cdev, transient_data->restore_state); + return size; + } + + /* start timer if there is no active timer */ + if (state == 1 && transient_data->activate == 0 && + transient_data->duration != 0) { + transient_data->activate = state; + led_set_brightness(led_cdev, transient_data->state); + transient_data->restore_state = + (transient_data->state == LED_FULL) ? LED_OFF : LED_FULL; + mod_timer(&transient_data->timer, + jiffies + transient_data->duration); + } + + /* state == 0 && transient_data->activate == 0 + timer is not active - just return */ + /* state == 1 && transient_data->activate == 1 + timer is already active - just return */ + + return size; +} + +static ssize_t transient_duration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct transient_trig_data *transient_data = led_cdev->trigger_data; + + return sprintf(buf, "%lu\n", transient_data->duration); +} + +static ssize_t transient_duration_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct transient_trig_data *transient_data = led_cdev->trigger_data; + unsigned long state; + ssize_t ret; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + transient_data->duration = state; + return size; +} + +static ssize_t transient_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct transient_trig_data *transient_data = led_cdev->trigger_data; + int state; + + state = (transient_data->state == LED_FULL) ? 1 : 0; + return sprintf(buf, "%d\n", state); +} + +static ssize_t transient_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct transient_trig_data *transient_data = led_cdev->trigger_data; + unsigned long state; + ssize_t ret; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + if (state != 1 && state != 0) + return -EINVAL; + + transient_data->state = (state == 1) ? LED_FULL : LED_OFF; + return size; +} + +static DEVICE_ATTR(activate, 0644, transient_activate_show, + transient_activate_store); +static DEVICE_ATTR(duration, 0644, transient_duration_show, + transient_duration_store); +static DEVICE_ATTR(state, 0644, transient_state_show, transient_state_store); + +static void transient_trig_activate(struct led_classdev *led_cdev) +{ + int rc; + struct transient_trig_data *tdata; + + tdata = kzalloc(sizeof(struct transient_trig_data), GFP_KERNEL); + if (!tdata) { + dev_err(led_cdev->dev, + "unable to allocate transient trigger\n"); + return; + } + led_cdev->trigger_data = tdata; + + rc = device_create_file(led_cdev->dev, &dev_attr_activate); + if (rc) + goto err_out; + + rc = device_create_file(led_cdev->dev, &dev_attr_duration); + if (rc) + goto err_out_duration; + + rc = device_create_file(led_cdev->dev, &dev_attr_state); + if (rc) + goto err_out_state; + + setup_timer(&tdata->timer, transient_timer_function, + (unsigned long) led_cdev); + led_cdev->activated = true; + + return; + +err_out_state: + device_remove_file(led_cdev->dev, &dev_attr_duration); +err_out_duration: + device_remove_file(led_cdev->dev, &dev_attr_activate); +err_out: + dev_err(led_cdev->dev, "unable to register transient trigger\n"); + led_cdev->trigger_data = NULL; + kfree(tdata); +} + +static void transient_trig_deactivate(struct led_classdev *led_cdev) +{ + struct transient_trig_data *transient_data = led_cdev->trigger_data; + + if (led_cdev->activated) { + del_timer_sync(&transient_data->timer); + led_set_brightness(led_cdev, transient_data->restore_state); + device_remove_file(led_cdev->dev, &dev_attr_activate); + device_remove_file(led_cdev->dev, &dev_attr_duration); + device_remove_file(led_cdev->dev, &dev_attr_state); + led_cdev->trigger_data = NULL; + led_cdev->activated = false; + kfree(transient_data); + } +} + +static struct led_trigger transient_trigger = { + .name = "transient", + .activate = transient_trig_activate, + .deactivate = transient_trig_deactivate, +}; + +static int __init transient_trig_init(void) +{ + return led_trigger_register(&transient_trigger); +} + +static void __exit transient_trig_exit(void) +{ + led_trigger_unregister(&transient_trigger); +} + +module_init(transient_trig_init); +module_exit(transient_trig_exit); + +MODULE_AUTHOR("Shuah Khan <shuahkhan@gmail.com>"); +MODULE_DESCRIPTION("Transient LED trigger"); +MODULE_LICENSE("GPL"); |