From ff45262a85dbf1bc74463c5dcea1d71a406d4d8e Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Mon, 18 Feb 2013 21:10:14 -0800 Subject: leds: add new LP5562 LED driver LP5562 can drive up to 4 channels, RGB and White. LEDs can be controlled directly via the led class control interface. LP55xx common driver LP5562 is one of LP55xx family device, so LP55xx common code are used. On the other hand, chip specific configuration is defined in the structure 'lp55xx_device_config' LED pattern data LP5562 has also internal program memory which is used for running various LED patterns. LP5562 driver supports the firmware interface and the predefined pattern data as well. LP5562 device attributes: 'led_pattern' and 'engine_mux' A 'led_pattern' is an index code which runs the predefined pattern data. And 'engine_mux' is updated with the firmware interface is activated. Detailed description has been updated in the documentation files, 'leds-lp55xx.txt' and 'leds-lp5562.txt'. Changes on the header file LP5562 configurable definitions are added. Pattern RGB data is fixed as constant value. (No side effect on other devices, LP5521 or LP5523.) (cooloney@gmail.com: remove redundant mutex_unlock(). Reported by Dan Carpenter ) Signed-off-by: Milo(Woogyom) Kim Signed-off-by: Bryan Wu diff --git a/Documentation/leds/00-INDEX b/Documentation/leds/00-INDEX index 5246090..1ecd159 100644 --- a/Documentation/leds/00-INDEX +++ b/Documentation/leds/00-INDEX @@ -6,6 +6,8 @@ leds-lp5521.txt - notes on how to use the leds-lp5521 driver. leds-lp5523.txt - notes on how to use the leds-lp5523 driver. +leds-lp5562.txt + - notes on how to use the leds-lp5562 driver. leds-lp55xx.txt - description about lp55xx common driver. leds-lm3556.txt diff --git a/Documentation/leds/leds-lp5562.txt b/Documentation/leds/leds-lp5562.txt new file mode 100644 index 0000000..9606100 --- /dev/null +++ b/Documentation/leds/leds-lp5562.txt @@ -0,0 +1,135 @@ +Kernel driver for LP5562 +======================== + +* TI LP5562 LED Driver + +Author: Milo(Woogyom) Kim + +Description + + LP5562 can drive up to 4 channels. R/G/B and White. + LEDs can be controlled directly via the led class control interface. + + All four channels can be also controlled using the engine micro programs. + LP5562 has the internal program memory for running various LED patterns. + For the details, please refer to 'firmware' section in leds-lp55xx.txt + +Device attribute: engine_mux + + 3 Engines are allocated in LP5562, but the number of channel is 4. + Therefore each channel should be mapped to the engine number. + Value : RGB or W + + This attribute is used for programming LED data with the firmware interface. + Unlike the LP5521/LP5523/55231, LP5562 has unique feature for the engine mux, + so additional sysfs is required. + + LED Map + Red ... Engine 1 (fixed) + Green ... Engine 2 (fixed) + Blue ... Engine 3 (fixed) + White ... Engine 1 or 2 or 3 (selective) + +How to load the program data using engine_mux + + Before loading the LP5562 program data, engine_mux should be written between + the engine selection and loading the firmware. + Engine mux has two different mode, RGB and W. + RGB is used for loading RGB program data, W is used for W program data. + + For example, run blinking green channel pattern, + echo 2 > /sys/bus/i2c/devices/xxxx/select_engine # 2 is for green channel + echo "RGB" > /sys/bus/i2c/devices/xxxx/engine_mux # engine mux for RGB + echo 1 > /sys/class/firmware/lp5562/loading + echo "4000600040FF6000" > /sys/class/firmware/lp5562/data + echo 0 > /sys/class/firmware/lp5562/loading + echo 1 > /sys/bus/i2c/devices/xxxx/run_engine + + To run a blinking white pattern, + echo 1 or 2 or 3 > /sys/bus/i2c/devices/xxxx/select_engine + echo "W" > /sys/bus/i2c/devices/xxxx/engine_mux + echo 1 > /sys/class/firmware/lp5562/loading + echo "4000600040FF6000" > /sys/class/firmware/lp5562/data + echo 0 > /sys/class/firmware/lp5562/loading + echo 1 > /sys/bus/i2c/devices/xxxx/run_engine + +How to load the predefined patterns + + Please refer to 'leds-lp55xx.txt" + +Setting Current of Each Channel + + Like LP5521 and LP5523/55231, LP5562 provides LED current settings. + The 'led_current' and 'max_current' are used. + +(Example of Platform data) + +To configure the platform specific data, lp55xx_platform_data structure is used. + +static struct lp55xx_led_config lp5562_led_config[] = { + { + .name = "R", + .chan_nr = 0, + .led_current = 20, + .max_current = 40, + }, + { + .name = "G", + .chan_nr = 1, + .led_current = 20, + .max_current = 40, + }, + { + .name = "B", + .chan_nr = 2, + .led_current = 20, + .max_current = 40, + }, + { + .name = "W", + .chan_nr = 3, + .led_current = 20, + .max_current = 40, + }, +}; + +static int lp5562_setup(void) +{ + /* setup HW resources */ +} + +static void lp5562_release(void) +{ + /* Release HW resources */ +} + +static void lp5562_enable(bool state) +{ + /* Control of chip enable signal */ +} + +static struct lp55xx_platform_data lp5562_platform_data = { + .led_config = lp5562_led_config, + .num_channels = ARRAY_SIZE(lp5562_led_config), + .setup_resources = lp5562_setup, + .release_resources = lp5562_release, + .enable = lp5562_enable, +}; + +If the current is set to 0 in the platform data, that channel is +disabled and it is not visible in the sysfs. + +The 'update_config' : CONFIG register (ADDR 08h) +This value is platform-specific data. +If update_config is not defined, the CONFIG register is set with +'LP5562_PWRSAVE_EN | LP5562_CLK_AUTO'. +(Enable auto-powersave, set automatic clock source selection) + +#define LP5562_CONFIGS (LP5562_PWM_HF | LP5562_PWRSAVE_EN | \ + LP5562_CLK_SRC_EXT) + +static struct lp55xx_platform_data lp5562_pdata = { + .led_config = lp5562_led_config, + .num_channels = ARRAY_SIZE(lp5562_led_config), + .update_config = LP5562_CONFIGS, +}; diff --git a/Documentation/leds/leds-lp55xx.txt b/Documentation/leds/leds-lp55xx.txt index ced4186..eec8fa2 100644 --- a/Documentation/leds/leds-lp55xx.txt +++ b/Documentation/leds/leds-lp55xx.txt @@ -5,7 +5,7 @@ Authors: Milo(Woogyom) Kim Description ----------- -LP5521, LP5523/55231 have common features as below. +LP5521, LP5523/55231 and LP5562 have common features as below. Register access via the I2C Device initialization/deinitialization @@ -116,3 +116,47 @@ To support this, 'run_engine' and 'firmware_cb' are configurable in each driver. run_engine : Control the selected engine firmware_cb : The callback function after loading the firmware is done. Chip specific commands for loading and updating program memory. + +( Predefined pattern data ) + +Without the firmware interface, LP55xx driver provides another method for +loading a LED pattern. That is 'predefined' pattern. +A predefined pattern is defined in the platform data and load it(or them) +via the sysfs if needed. +To use the predefined pattern concept, 'patterns' and 'num_patterns' should be +configured. + + Example of predefined pattern data: + + /* mode_1: blinking data */ + static const u8 mode_1[] = { + 0x40, 0x00, 0x60, 0x00, 0x40, 0xFF, 0x60, 0x00, + }; + + /* mode_2: always on */ + static const u8 mode_2[] = { 0x40, 0xFF, }; + + struct lp55xx_predef_pattern board_led_patterns[] = { + { + .r = mode_1, + .size_r = ARRAY_SIZE(mode_1), + }, + { + .b = mode_2, + .size_b = ARRAY_SIZE(mode_2), + }, + } + + struct lp55xx_platform_data lp5562_pdata = { + ... + .patterns = board_led_patterns, + .num_patterns = ARRAY_SIZE(board_led_patterns), + }; + +Then, mode_1 and mode_2 can be run via through the sysfs. + + echo 1 > /sys/bus/i2c/devices/xxxx/led_pattern # red blinking LED pattern + echo 2 > /sys/bus/i2c/devices/xxxx/led_pattern # blue LED always on + +To stop running pattern, + echo 0 > /sys/bus/i2c/devices/xxxx/led_pattern diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index ec50824..c7f7550 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -194,8 +194,8 @@ config LEDS_LP3944 module will be called leds-lp3944. config LEDS_LP55XX_COMMON - tristate "Common Driver for TI/National LP5521 and LP5523/55231" - depends on LEDS_LP5521 || LEDS_LP5523 + tristate "Common Driver for TI/National LP5521, LP5523/55231 and LP5562" + depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 select FW_LOADER help This option supports common operations for LP5521 and LP5523/55231 @@ -222,6 +222,16 @@ config LEDS_LP5523 Driver provides direct control via LED class and interface for programming the engines. +config LEDS_LP5562 + tristate "LED Support for TI LP5562 LED driver chip" + depends on LEDS_CLASS && I2C + select LEDS_LP55XX_COMMON + help + If you say yes here you get support for TI LP5562 LED driver. + It is 4 channels chip with programmable engines. + Driver provides direct control via LED class and interface for + programming the engines. + config LEDS_LP8788 tristate "LED support for the TI LP8788 PMIC" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 215e7e3..ab8f5c5 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o +obj-$(CONFIG_LEDS_LP5562) += leds-lp5562.o obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o diff --git a/drivers/leds/leds-lp5562.c b/drivers/leds/leds-lp5562.c new file mode 100644 index 0000000..f8b9277 --- /dev/null +++ b/drivers/leds/leds-lp5562.c @@ -0,0 +1,593 @@ +/* + * LP5562 LED driver + * + * Copyright (C) 2013 Texas Instruments + * + * Author: Milo(Woogyom) Kim + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "leds-lp55xx-common.h" + +#define LP5562_PROGRAM_LENGTH 32 +#define LP5562_MAX_LEDS 4 + +/* ENABLE Register 00h */ +#define LP5562_REG_ENABLE 0x00 +#define LP5562_EXEC_ENG1_M 0x30 +#define LP5562_EXEC_ENG2_M 0x0C +#define LP5562_EXEC_ENG3_M 0x03 +#define LP5562_EXEC_M 0x3F +#define LP5562_MASTER_ENABLE 0x40 /* Chip master enable */ +#define LP5562_LOGARITHMIC_PWM 0x80 /* Logarithmic PWM adjustment */ +#define LP5562_EXEC_RUN 0x2A +#define LP5562_ENABLE_DEFAULT \ + (LP5562_MASTER_ENABLE | LP5562_LOGARITHMIC_PWM) +#define LP5562_ENABLE_RUN_PROGRAM \ + (LP5562_ENABLE_DEFAULT | LP5562_EXEC_RUN) + +/* OPMODE Register 01h */ +#define LP5562_REG_OP_MODE 0x01 +#define LP5562_MODE_ENG1_M 0x30 +#define LP5562_MODE_ENG2_M 0x0C +#define LP5562_MODE_ENG3_M 0x03 +#define LP5562_LOAD_ENG1 0x10 +#define LP5562_LOAD_ENG2 0x04 +#define LP5562_LOAD_ENG3 0x01 +#define LP5562_RUN_ENG1 0x20 +#define LP5562_RUN_ENG2 0x08 +#define LP5562_RUN_ENG3 0x02 +#define LP5562_ENG1_IS_LOADING(mode) \ + ((mode & LP5562_MODE_ENG1_M) == LP5562_LOAD_ENG1) +#define LP5562_ENG2_IS_LOADING(mode) \ + ((mode & LP5562_MODE_ENG2_M) == LP5562_LOAD_ENG2) +#define LP5562_ENG3_IS_LOADING(mode) \ + ((mode & LP5562_MODE_ENG3_M) == LP5562_LOAD_ENG3) + +/* BRIGHTNESS Registers */ +#define LP5562_REG_R_PWM 0x04 +#define LP5562_REG_G_PWM 0x03 +#define LP5562_REG_B_PWM 0x02 +#define LP5562_REG_W_PWM 0x0E + +/* CURRENT Registers */ +#define LP5562_REG_R_CURRENT 0x07 +#define LP5562_REG_G_CURRENT 0x06 +#define LP5562_REG_B_CURRENT 0x05 +#define LP5562_REG_W_CURRENT 0x0F + +/* CONFIG Register 08h */ +#define LP5562_REG_CONFIG 0x08 +#define LP5562_DEFAULT_CFG \ + (LP5562_PWM_HF | LP5562_PWRSAVE_EN | LP5562_CLK_INT) + +/* RESET Register 0Dh */ +#define LP5562_REG_RESET 0x0D +#define LP5562_RESET 0xFF + +/* PROGRAM ENGINE Registers */ +#define LP5562_REG_PROG_MEM_ENG1 0x10 +#define LP5562_REG_PROG_MEM_ENG2 0x30 +#define LP5562_REG_PROG_MEM_ENG3 0x50 + +/* LEDMAP Register 70h */ +#define LP5562_REG_ENG_SEL 0x70 +#define LP5562_ENG_SEL_PWM 0 +#define LP5562_ENG_FOR_RGB_M 0x3F +#define LP5562_ENG_SEL_RGB 0x1B /* R:ENG1, G:ENG2, B:ENG3 */ +#define LP5562_ENG_FOR_W_M 0xC0 +#define LP5562_ENG1_FOR_W 0x40 /* W:ENG1 */ +#define LP5562_ENG2_FOR_W 0x80 /* W:ENG2 */ +#define LP5562_ENG3_FOR_W 0xC0 /* W:ENG3 */ + +/* Program Commands */ +#define LP5562_CMD_DISABLE 0x00 +#define LP5562_CMD_LOAD 0x15 +#define LP5562_CMD_RUN 0x2A +#define LP5562_CMD_DIRECT 0x3F +#define LP5562_PATTERN_OFF 0 + +static inline void lp5562_wait_opmode_done(void) +{ + /* operation mode change needs to be longer than 153 us */ + usleep_range(200, 300); +} + +static inline void lp5562_wait_enable_done(void) +{ + /* it takes more 488 us to update ENABLE register */ + usleep_range(500, 600); +} + +static void lp5562_set_led_current(struct lp55xx_led *led, u8 led_current) +{ + u8 addr[] = { + LP5562_REG_R_CURRENT, + LP5562_REG_G_CURRENT, + LP5562_REG_B_CURRENT, + LP5562_REG_W_CURRENT, + }; + + led->led_current = led_current; + lp55xx_write(led->chip, addr[led->chan_nr], led_current); +} + +static void lp5562_load_engine(struct lp55xx_chip *chip) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + u8 mask[] = { + [LP55XX_ENGINE_1] = LP5562_MODE_ENG1_M, + [LP55XX_ENGINE_2] = LP5562_MODE_ENG2_M, + [LP55XX_ENGINE_3] = LP5562_MODE_ENG3_M, + }; + + u8 val[] = { + [LP55XX_ENGINE_1] = LP5562_LOAD_ENG1, + [LP55XX_ENGINE_2] = LP5562_LOAD_ENG2, + [LP55XX_ENGINE_3] = LP5562_LOAD_ENG3, + }; + + lp55xx_update_bits(chip, LP5562_REG_OP_MODE, mask[idx], val[idx]); + + lp5562_wait_opmode_done(); +} + +static void lp5562_stop_engine(struct lp55xx_chip *chip) +{ + lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DISABLE); + lp5562_wait_opmode_done(); +} + +static void lp5562_run_engine(struct lp55xx_chip *chip, bool start) +{ + int ret; + u8 mode; + u8 exec; + + /* stop engine */ + if (!start) { + lp55xx_write(chip, LP5562_REG_ENABLE, LP5562_ENABLE_DEFAULT); + lp5562_wait_enable_done(); + lp5562_stop_engine(chip); + lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_PWM); + lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DIRECT); + lp5562_wait_opmode_done(); + return; + } + + /* + * To run the engine, + * operation mode and enable register should updated at the same time + */ + + ret = lp55xx_read(chip, LP5562_REG_OP_MODE, &mode); + if (ret) + return; + + ret = lp55xx_read(chip, LP5562_REG_ENABLE, &exec); + if (ret) + return; + + /* change operation mode to RUN only when each engine is loading */ + if (LP5562_ENG1_IS_LOADING(mode)) { + mode = (mode & ~LP5562_MODE_ENG1_M) | LP5562_RUN_ENG1; + exec = (exec & ~LP5562_EXEC_ENG1_M) | LP5562_RUN_ENG1; + } + + if (LP5562_ENG2_IS_LOADING(mode)) { + mode = (mode & ~LP5562_MODE_ENG2_M) | LP5562_RUN_ENG2; + exec = (exec & ~LP5562_EXEC_ENG2_M) | LP5562_RUN_ENG2; + } + + if (LP5562_ENG3_IS_LOADING(mode)) { + mode = (mode & ~LP5562_MODE_ENG3_M) | LP5562_RUN_ENG3; + exec = (exec & ~LP5562_EXEC_ENG3_M) | LP5562_RUN_ENG3; + } + + lp55xx_write(chip, LP5562_REG_OP_MODE, mode); + lp5562_wait_opmode_done(); + + lp55xx_update_bits(chip, LP5562_REG_ENABLE, LP5562_EXEC_M, exec); + lp5562_wait_enable_done(); +} + +static int lp5562_update_firmware(struct lp55xx_chip *chip, + const u8 *data, size_t size) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + u8 pattern[LP5562_PROGRAM_LENGTH] = {0}; + u8 addr[] = { + [LP55XX_ENGINE_1] = LP5562_REG_PROG_MEM_ENG1, + [LP55XX_ENGINE_2] = LP5562_REG_PROG_MEM_ENG2, + [LP55XX_ENGINE_3] = LP5562_REG_PROG_MEM_ENG3, + }; + unsigned cmd; + char c[3]; + int program_size; + int nrchars; + int offset = 0; + int ret; + int i; + + /* clear program memory before updating */ + for (i = 0; i < LP5562_PROGRAM_LENGTH; i++) + lp55xx_write(chip, addr[idx] + i, 0); + + i = 0; + while ((offset < size - 1) && (i < LP5562_PROGRAM_LENGTH)) { + /* separate sscanfs because length is working only for %s */ + ret = sscanf(data + offset, "%2s%n ", c, &nrchars); + if (ret != 1) + goto err; + + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto err; + + pattern[i] = (u8)cmd; + offset += nrchars; + i++; + } + + /* Each instruction is 16bit long. Check that length is even */ + if (i % 2) + goto err; + + program_size = i; + for (i = 0; i < program_size; i++) + lp55xx_write(chip, addr[idx] + i, pattern[i]); + + return 0; + +err: + dev_err(&chip->cl->dev, "wrong pattern format\n"); + return -EINVAL; +} + +static void lp5562_firmware_loaded(struct lp55xx_chip *chip) +{ + const struct firmware *fw = chip->fw; + + if (fw->size > LP5562_PROGRAM_LENGTH) { + dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n", + fw->size); + return; + } + + /* + * Program momery sequence + * 1) set engine mode to "LOAD" + * 2) write firmware data into program memory + */ + + lp5562_load_engine(chip); + lp5562_update_firmware(chip, fw->data, fw->size); +} + +static int lp5562_post_init_device(struct lp55xx_chip *chip) +{ + int ret; + u8 update_cfg = chip->pdata->update_config ? : LP5562_DEFAULT_CFG; + + /* Set all PWMs to direct control mode */ + ret = lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DIRECT); + if (ret) + return ret; + + lp5562_wait_opmode_done(); + + ret = lp55xx_write(chip, LP5562_REG_CONFIG, update_cfg); + if (ret) + return ret; + + /* Initialize all channels PWM to zero -> leds off */ + lp55xx_write(chip, LP5562_REG_R_PWM, 0); + lp55xx_write(chip, LP5562_REG_G_PWM, 0); + lp55xx_write(chip, LP5562_REG_B_PWM, 0); + lp55xx_write(chip, LP5562_REG_W_PWM, 0); + + /* Set LED map as register PWM by default */ + lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_PWM); + + return 0; +} + +static void lp5562_led_brightness_work(struct work_struct *work) +{ + struct lp55xx_led *led = container_of(work, struct lp55xx_led, + brightness_work); + struct lp55xx_chip *chip = led->chip; + u8 addr[] = { + LP5562_REG_R_PWM, + LP5562_REG_G_PWM, + LP5562_REG_B_PWM, + LP5562_REG_W_PWM, + }; + + mutex_lock(&chip->lock); + lp55xx_write(chip, addr[led->chan_nr], led->brightness); + mutex_unlock(&chip->lock); +} + +static void lp5562_write_program_memory(struct lp55xx_chip *chip, + u8 base, const u8 *rgb, int size) +{ + int i; + + if (!rgb || size <= 0) + return; + + for (i = 0; i < size; i++) + lp55xx_write(chip, base + i, *(rgb + i)); + + lp55xx_write(chip, base + i, 0); + lp55xx_write(chip, base + i + 1, 0); +} + +/* check the size of program count */ +static inline bool _is_pc_overflow(struct lp55xx_predef_pattern *ptn) +{ + return (ptn->size_r >= LP5562_PROGRAM_LENGTH || + ptn->size_g >= LP5562_PROGRAM_LENGTH || + ptn->size_b >= LP5562_PROGRAM_LENGTH); +} + +static int lp5562_run_predef_led_pattern(struct lp55xx_chip *chip, int mode) +{ + struct lp55xx_predef_pattern *ptn; + int i; + + if (mode == LP5562_PATTERN_OFF) { + lp5562_run_engine(chip, false); + return 0; + } + + ptn = chip->pdata->patterns + (mode - 1); + if (!ptn || _is_pc_overflow(ptn)) { + dev_err(&chip->cl->dev, "invalid pattern data\n"); + return -EINVAL; + } + + lp5562_stop_engine(chip); + + /* Set LED map as RGB */ + lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_RGB); + + /* Load engines */ + for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) { + chip->engine_idx = i; + lp5562_load_engine(chip); + } + + /* Clear program registers */ + lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG1, 0); + lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG1 + 1, 0); + lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG2, 0); + lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG2 + 1, 0); + lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG3, 0); + lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG3 + 1, 0); + + /* Program engines */ + lp5562_write_program_memory(chip, LP5562_REG_PROG_MEM_ENG1, + ptn->r, ptn->size_r); + lp5562_write_program_memory(chip, LP5562_REG_PROG_MEM_ENG2, + ptn->g, ptn->size_g); + lp5562_write_program_memory(chip, LP5562_REG_PROG_MEM_ENG3, + ptn->b, ptn->size_b); + + /* Run engines */ + lp5562_run_engine(chip, true); + + return 0; +} + +static ssize_t lp5562_store_pattern(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + struct lp55xx_predef_pattern *ptn = chip->pdata->patterns; + int num_patterns = chip->pdata->num_patterns; + unsigned long mode; + int ret; + + ret = kstrtoul(buf, 0, &mode); + if (ret) + return ret; + + if (mode > num_patterns || !ptn) + return -EINVAL; + + mutex_lock(&chip->lock); + ret = lp5562_run_predef_led_pattern(chip, mode); + mutex_unlock(&chip->lock); + + if (ret) + return ret; + + return len; +} + +static ssize_t lp5562_store_engine_mux(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + u8 mask; + u8 val; + + /* LED map + * R ... Engine 1 (fixed) + * G ... Engine 2 (fixed) + * B ... Engine 3 (fixed) + * W ... Engine 1 or 2 or 3 + */ + + if (sysfs_streq(buf, "RGB")) { + mask = LP5562_ENG_FOR_RGB_M; + val = LP5562_ENG_SEL_RGB; + } else if (sysfs_streq(buf, "W")) { + enum lp55xx_engine_index idx = chip->engine_idx; + + mask = LP5562_ENG_FOR_W_M; + switch (idx) { + case LP55XX_ENGINE_1: + val = LP5562_ENG1_FOR_W; + break; + case LP55XX_ENGINE_2: + val = LP5562_ENG2_FOR_W; + break; + case LP55XX_ENGINE_3: + val = LP5562_ENG3_FOR_W; + break; + default: + return -EINVAL; + } + + } else { + dev_err(dev, "choose RGB or W\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + lp55xx_update_bits(chip, LP5562_REG_ENG_SEL, mask, val); + mutex_unlock(&chip->lock); + + return len; +} + +static DEVICE_ATTR(led_pattern, S_IWUSR, NULL, lp5562_store_pattern); +static DEVICE_ATTR(engine_mux, S_IWUSR, NULL, lp5562_store_engine_mux); + +static struct attribute *lp5562_attributes[] = { + &dev_attr_led_pattern.attr, + &dev_attr_engine_mux.attr, + NULL, +}; + +static const struct attribute_group lp5562_group = { + .attrs = lp5562_attributes, +}; + +/* Chip specific configurations */ +static struct lp55xx_device_config lp5562_cfg = { + .max_channel = LP5562_MAX_LEDS, + .reset = { + .addr = LP5562_REG_RESET, + .val = LP5562_RESET, + }, + .enable = { + .addr = LP5562_REG_ENABLE, + .val = LP5562_ENABLE_DEFAULT, + }, + .post_init_device = lp5562_post_init_device, + .set_led_current = lp5562_set_led_current, + .brightness_work_fn = lp5562_led_brightness_work, + .run_engine = lp5562_run_engine, + .firmware_cb = lp5562_firmware_loaded, + .dev_attr_group = &lp5562_group, +}; + +static int lp5562_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct lp55xx_chip *chip; + struct lp55xx_led *led; + struct lp55xx_platform_data *pdata = client->dev.platform_data; + + if (!pdata) { + dev_err(&client->dev, "no platform data\n"); + return -EINVAL; + } + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + led = devm_kzalloc(&client->dev, + sizeof(*led) * pdata->num_channels, GFP_KERNEL); + if (!led) + return -ENOMEM; + + chip->cl = client; + chip->pdata = pdata; + chip->cfg = &lp5562_cfg; + + mutex_init(&chip->lock); + + i2c_set_clientdata(client, led); + + ret = lp55xx_init_device(chip); + if (ret) + goto err_init; + + ret = lp55xx_register_leds(led, chip); + if (ret) + goto err_register_leds; + + ret = lp55xx_register_sysfs(chip); + if (ret) { + dev_err(&client->dev, "registering sysfs failed\n"); + goto err_register_sysfs; + } + + return 0; + +err_register_sysfs: + lp55xx_unregister_leds(led, chip); +err_register_leds: + lp55xx_deinit_device(chip); +err_init: + return ret; +} + +static int lp5562_remove(struct i2c_client *client) +{ + struct lp55xx_led *led = i2c_get_clientdata(client); + struct lp55xx_chip *chip = led->chip; + + lp5562_stop_engine(chip); + + lp55xx_unregister_sysfs(chip); + lp55xx_unregister_leds(led, chip); + lp55xx_deinit_device(chip); + + return 0; +} + +static const struct i2c_device_id lp5562_id[] = { + { "lp5562", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp5562_id); + +static struct i2c_driver lp5562_driver = { + .driver = { + .name = "lp5562", + }, + .probe = lp5562_probe, + .remove = lp5562_remove, + .id_table = lp5562_id, +}; + +module_i2c_driver(lp5562_driver); + +MODULE_DESCRIPTION("Texas Instruments LP5562 LED Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c index d9eb841..8a388a4 100644 --- a/drivers/leds/leds-lp55xx-common.c +++ b/drivers/leds/leds-lp55xx-common.c @@ -1,5 +1,5 @@ /* - * LP5521/LP5523/LP55231 Common Driver + * LP5521/LP5523/LP55231/LP5562 Common Driver * * Copyright 2012 Texas Instruments * diff --git a/include/linux/platform_data/leds-lp55xx.h b/include/linux/platform_data/leds-lp55xx.h index 1509570..1f1041e 100644 --- a/include/linux/platform_data/leds-lp55xx.h +++ b/include/linux/platform_data/leds-lp55xx.h @@ -32,6 +32,13 @@ #define LP5521_CLK_INT 1 /* Internal clock */ #define LP5521_CLK_AUTO 2 /* Automatic clock selection */ +/* Bits in LP5562 CONFIG register */ +#define LP5562_PWM_HF LP5521_PWM_HF +#define LP5562_PWRSAVE_EN LP5521_PWRSAVE_EN +#define LP5562_CLK_SRC_EXT LP5521_CLK_SRC_EXT +#define LP5562_CLK_INT LP5521_CLK_INT +#define LP5562_CLK_AUTO LP5521_CLK_AUTO + struct lp55xx_led_config { const char *name; u8 chan_nr; @@ -40,9 +47,9 @@ struct lp55xx_led_config { }; struct lp55xx_predef_pattern { - u8 *r; - u8 *g; - u8 *b; + const u8 *r; + const u8 *g; + const u8 *b; u8 size_r; u8 size_g; u8 size_b; -- cgit v0.10.2 From f07fb52107c881f35eaff09fe990a4dfd0f7e62a Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Wed, 20 Feb 2013 00:36:01 -0800 Subject: leds: move LED trigger drivers into new subdirectory For better driver management, new subdirectory, 'trigger' is created. All LED trigger drivers are moved into this directory. Internal header, 'leds.h' is included in each LED trigger drivers. Fix the location of header file, "leds.h" -> "../leds.h" in driver files. One exception is here, 'ledtrig-timer.c'. There is no need to include 'leds.h'. so '#include "leds.h"' line was removed. Signed-off-by: Milo(Woogyom) Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index c7f7550..d44806d 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -479,106 +479,7 @@ config LEDS_BLINKM This option enables support for the BlinkM RGB LED connected through I2C. Say Y to enable support for the BlinkM LED. -config LEDS_TRIGGERS - bool "LED Trigger support" - depends on LEDS_CLASS - help - This option enables trigger support for the leds class. - These triggers allow kernel events to drive the LEDs and can - be configured via sysfs. If unsure, say Y. - comment "LED Triggers" - -config LEDS_TRIGGER_TIMER - tristate "LED Timer Trigger" - depends on LEDS_TRIGGERS - help - This allows LEDs to be controlled by a programmable timer - via sysfs. Some LED hardware can be programmed to start - blinking the LED without any further software interaction. - For more details read Documentation/leds/leds-class.txt. - - If unsure, say Y. - -config LEDS_TRIGGER_ONESHOT - tristate "LED One-shot Trigger" - depends on LEDS_TRIGGERS - help - This allows LEDs to blink in one-shot pulses with parameters - controlled via sysfs. It's useful to notify the user on - sporadic events, when there are no clear begin and end trap points, - or on dense events, where this blinks the LED at constant rate if - rearmed continuously. - - It also shows how to use the led_blink_set_oneshot() function. - - If unsure, say Y. - -config LEDS_TRIGGER_IDE_DISK - bool "LED IDE Disk Trigger" - depends on IDE_GD_ATA - depends on LEDS_TRIGGERS - help - This allows LEDs to be controlled by IDE disk activity. - If unsure, say Y. - -config LEDS_TRIGGER_HEARTBEAT - tristate "LED Heartbeat Trigger" - depends on LEDS_TRIGGERS - help - This allows LEDs to be controlled by a CPU load average. - The flash frequency is a hyperbolic function of the 1-minute - load average. - If unsure, say Y. - -config LEDS_TRIGGER_BACKLIGHT - tristate "LED backlight Trigger" - depends on LEDS_TRIGGERS - help - This allows LEDs to be controlled as a backlight device: they - turn off and on when the display is blanked and unblanked. - - If unsure, say N. - -config LEDS_TRIGGER_CPU - bool "LED CPU Trigger" - depends on LEDS_TRIGGERS - help - This allows LEDs to be controlled by active CPUs. This shows - the active CPUs across an array of LEDs so you can see which - CPUs are active on the system at any given moment. - - If unsure, say N. - -config LEDS_TRIGGER_GPIO - tristate "LED GPIO Trigger" - depends on LEDS_TRIGGERS - depends on GPIOLIB - help - This allows LEDs to be controlled by gpio events. It's good - when using gpios as switches and triggering the needed LEDs - from there. One use case is n810's keypad LEDs that could - be triggered by this trigger when user slides up to show - keypad. - - If unsure, say N. - -config LEDS_TRIGGER_DEFAULT_ON - tristate "LED Default ON Trigger" - depends on LEDS_TRIGGERS - help - This allows LEDs to be initialised in the ON state. - If unsure, say Y. - -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 hardware. - If unsure, say Y. +source "drivers/leds/trigger/Kconfig" endif # NEW_LEDS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index ab8f5c5..ac28977 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -58,12 +58,4 @@ obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o # LED Triggers -obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o -obj-$(CONFIG_LEDS_TRIGGER_ONESHOT) += ledtrig-oneshot.o -obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o -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_CPU) += ledtrig-cpu.o -obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o -obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o +obj-$(CONFIG_LEDS_TRIGGERS) += trigger/ diff --git a/drivers/leds/ledtrig-backlight.c b/drivers/leds/ledtrig-backlight.c deleted file mode 100644 index 027a2b1..0000000 --- a/drivers/leds/ledtrig-backlight.c +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Backlight emulation LED trigger - * - * Copyright 2008 (C) Rodolfo Giometti - * Copyright 2008 (C) Eurotech S.p.A. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include "leds.h" - -#define BLANK 1 -#define UNBLANK 0 - -struct bl_trig_notifier { - struct led_classdev *led; - int brightness; - int old_status; - struct notifier_block notifier; - unsigned invert; -}; - -static int fb_notifier_callback(struct notifier_block *p, - unsigned long event, void *data) -{ - struct bl_trig_notifier *n = container_of(p, - struct bl_trig_notifier, notifier); - struct led_classdev *led = n->led; - struct fb_event *fb_event = data; - int *blank = fb_event->data; - int new_status = *blank ? BLANK : UNBLANK; - - switch (event) { - case FB_EVENT_BLANK: - if (new_status == n->old_status) - break; - - if ((n->old_status == UNBLANK) ^ n->invert) { - n->brightness = led->brightness; - __led_set_brightness(led, LED_OFF); - } else { - __led_set_brightness(led, n->brightness); - } - - n->old_status = new_status; - - break; - } - - return 0; -} - -static ssize_t bl_trig_invert_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led = dev_get_drvdata(dev); - struct bl_trig_notifier *n = led->trigger_data; - - return sprintf(buf, "%u\n", n->invert); -} - -static ssize_t bl_trig_invert_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t num) -{ - struct led_classdev *led = dev_get_drvdata(dev); - struct bl_trig_notifier *n = led->trigger_data; - unsigned long invert; - int ret; - - ret = kstrtoul(buf, 10, &invert); - if (ret < 0) - return ret; - - if (invert > 1) - return -EINVAL; - - n->invert = invert; - - /* After inverting, we need to update the LED. */ - if ((n->old_status == BLANK) ^ n->invert) - __led_set_brightness(led, LED_OFF); - else - __led_set_brightness(led, n->brightness); - - return num; -} -static DEVICE_ATTR(inverted, 0644, bl_trig_invert_show, bl_trig_invert_store); - -static void bl_trig_activate(struct led_classdev *led) -{ - int ret; - - struct bl_trig_notifier *n; - - n = kzalloc(sizeof(struct bl_trig_notifier), GFP_KERNEL); - led->trigger_data = n; - if (!n) { - dev_err(led->dev, "unable to allocate backlight trigger\n"); - return; - } - - ret = device_create_file(led->dev, &dev_attr_inverted); - if (ret) - goto err_invert; - - n->led = led; - n->brightness = led->brightness; - n->old_status = UNBLANK; - n->notifier.notifier_call = fb_notifier_callback; - - ret = fb_register_client(&n->notifier); - if (ret) - dev_err(led->dev, "unable to register backlight trigger\n"); - led->activated = true; - - return; - -err_invert: - led->trigger_data = NULL; - kfree(n); -} - -static void bl_trig_deactivate(struct led_classdev *led) -{ - struct bl_trig_notifier *n = - (struct bl_trig_notifier *) led->trigger_data; - - if (led->activated) { - device_remove_file(led->dev, &dev_attr_inverted); - fb_unregister_client(&n->notifier); - kfree(n); - led->activated = false; - } -} - -static struct led_trigger bl_led_trigger = { - .name = "backlight", - .activate = bl_trig_activate, - .deactivate = bl_trig_deactivate -}; - -static int __init bl_trig_init(void) -{ - return led_trigger_register(&bl_led_trigger); -} - -static void __exit bl_trig_exit(void) -{ - led_trigger_unregister(&bl_led_trigger); -} - -module_init(bl_trig_init); -module_exit(bl_trig_exit); - -MODULE_AUTHOR("Rodolfo Giometti "); -MODULE_DESCRIPTION("Backlight emulation LED trigger"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/ledtrig-cpu.c b/drivers/leds/ledtrig-cpu.c deleted file mode 100644 index 4239b39..0000000 --- a/drivers/leds/ledtrig-cpu.c +++ /dev/null @@ -1,142 +0,0 @@ -/* - * ledtrig-cpu.c - LED trigger based on CPU activity - * - * This LED trigger will be registered for each possible CPU and named as - * cpu0, cpu1, cpu2, cpu3, etc. - * - * It can be bound to any LED just like other triggers using either a - * board file or via sysfs interface. - * - * An API named ledtrig_cpu is exported for any user, who want to add CPU - * activity indication in their code - * - * Copyright 2011 Linus Walleij - * Copyright 2011 - 2012 Bryan Wu - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include "leds.h" - -#define MAX_NAME_LEN 8 - -struct led_trigger_cpu { - char name[MAX_NAME_LEN]; - struct led_trigger *_trig; -}; - -static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig); - -/** - * ledtrig_cpu - emit a CPU event as a trigger - * @evt: CPU event to be emitted - * - * Emit a CPU event on a CPU core, which will trigger a - * binded LED to turn on or turn off. - */ -void ledtrig_cpu(enum cpu_led_event ledevt) -{ - struct led_trigger_cpu *trig = &__get_cpu_var(cpu_trig); - - /* Locate the correct CPU LED */ - switch (ledevt) { - case CPU_LED_IDLE_END: - case CPU_LED_START: - /* Will turn the LED on, max brightness */ - led_trigger_event(trig->_trig, LED_FULL); - break; - - case CPU_LED_IDLE_START: - case CPU_LED_STOP: - case CPU_LED_HALTED: - /* Will turn the LED off */ - led_trigger_event(trig->_trig, LED_OFF); - break; - - default: - /* Will leave the LED as it is */ - break; - } -} -EXPORT_SYMBOL(ledtrig_cpu); - -static int ledtrig_cpu_syscore_suspend(void) -{ - ledtrig_cpu(CPU_LED_STOP); - return 0; -} - -static void ledtrig_cpu_syscore_resume(void) -{ - ledtrig_cpu(CPU_LED_START); -} - -static void ledtrig_cpu_syscore_shutdown(void) -{ - ledtrig_cpu(CPU_LED_HALTED); -} - -static struct syscore_ops ledtrig_cpu_syscore_ops = { - .shutdown = ledtrig_cpu_syscore_shutdown, - .suspend = ledtrig_cpu_syscore_suspend, - .resume = ledtrig_cpu_syscore_resume, -}; - -static int __init ledtrig_cpu_init(void) -{ - int cpu; - - /* Supports up to 9999 cpu cores */ - BUILD_BUG_ON(CONFIG_NR_CPUS > 9999); - - /* - * Registering CPU led trigger for each CPU core here - * ignores CPU hotplug, but after this CPU hotplug works - * fine with this trigger. - */ - for_each_possible_cpu(cpu) { - struct led_trigger_cpu *trig = &per_cpu(cpu_trig, cpu); - - snprintf(trig->name, MAX_NAME_LEN, "cpu%d", cpu); - - led_trigger_register_simple(trig->name, &trig->_trig); - } - - register_syscore_ops(&ledtrig_cpu_syscore_ops); - - pr_info("ledtrig-cpu: registered to indicate activity on CPUs\n"); - - return 0; -} -module_init(ledtrig_cpu_init); - -static void __exit ledtrig_cpu_exit(void) -{ - int cpu; - - for_each_possible_cpu(cpu) { - struct led_trigger_cpu *trig = &per_cpu(cpu_trig, cpu); - - led_trigger_unregister_simple(trig->_trig); - trig->_trig = NULL; - memset(trig->name, 0, MAX_NAME_LEN); - } - - unregister_syscore_ops(&ledtrig_cpu_syscore_ops); -} -module_exit(ledtrig_cpu_exit); - -MODULE_AUTHOR("Linus Walleij "); -MODULE_AUTHOR("Bryan Wu "); -MODULE_DESCRIPTION("CPU LED trigger"); -MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-default-on.c b/drivers/leds/ledtrig-default-on.c deleted file mode 100644 index eac1f1b..0000000 --- a/drivers/leds/ledtrig-default-on.c +++ /dev/null @@ -1,45 +0,0 @@ -/* - * LED Kernel Default ON Trigger - * - * Copyright 2008 Nick Forbes - * - * Based on Richard Purdie's ledtrig-timer.c. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include "leds.h" - -static void defon_trig_activate(struct led_classdev *led_cdev) -{ - __led_set_brightness(led_cdev, led_cdev->max_brightness); -} - -static struct led_trigger defon_led_trigger = { - .name = "default-on", - .activate = defon_trig_activate, -}; - -static int __init defon_trig_init(void) -{ - return led_trigger_register(&defon_led_trigger); -} - -static void __exit defon_trig_exit(void) -{ - led_trigger_unregister(&defon_led_trigger); -} - -module_init(defon_trig_init); -module_exit(defon_trig_exit); - -MODULE_AUTHOR("Nick Forbes "); -MODULE_DESCRIPTION("Default-ON LED trigger"); -MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-gpio.c b/drivers/leds/ledtrig-gpio.c deleted file mode 100644 index 72e3ebf..0000000 --- a/drivers/leds/ledtrig-gpio.c +++ /dev/null @@ -1,253 +0,0 @@ -/* - * ledtrig-gio.c - LED Trigger Based on GPIO events - * - * Copyright 2009 Felipe Balbi - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "leds.h" - -struct gpio_trig_data { - struct led_classdev *led; - struct work_struct work; - - unsigned desired_brightness; /* desired brightness when led is on */ - unsigned inverted; /* true when gpio is inverted */ - unsigned gpio; /* gpio that triggers the leds */ -}; - -static irqreturn_t gpio_trig_irq(int irq, void *_led) -{ - struct led_classdev *led = _led; - struct gpio_trig_data *gpio_data = led->trigger_data; - - /* just schedule_work since gpio_get_value can sleep */ - schedule_work(&gpio_data->work); - - return IRQ_HANDLED; -}; - -static void gpio_trig_work(struct work_struct *work) -{ - struct gpio_trig_data *gpio_data = container_of(work, - struct gpio_trig_data, work); - int tmp; - - if (!gpio_data->gpio) - return; - - tmp = gpio_get_value(gpio_data->gpio); - if (gpio_data->inverted) - tmp = !tmp; - - if (tmp) { - if (gpio_data->desired_brightness) - __led_set_brightness(gpio_data->led, - gpio_data->desired_brightness); - else - __led_set_brightness(gpio_data->led, LED_FULL); - } else { - __led_set_brightness(gpio_data->led, LED_OFF); - } -} - -static ssize_t gpio_trig_brightness_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led = dev_get_drvdata(dev); - struct gpio_trig_data *gpio_data = led->trigger_data; - - return sprintf(buf, "%u\n", gpio_data->desired_brightness); -} - -static ssize_t gpio_trig_brightness_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t n) -{ - struct led_classdev *led = dev_get_drvdata(dev); - struct gpio_trig_data *gpio_data = led->trigger_data; - unsigned desired_brightness; - int ret; - - ret = sscanf(buf, "%u", &desired_brightness); - if (ret < 1 || desired_brightness > 255) { - dev_err(dev, "invalid value\n"); - return -EINVAL; - } - - gpio_data->desired_brightness = desired_brightness; - - return n; -} -static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show, - gpio_trig_brightness_store); - -static ssize_t gpio_trig_inverted_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led = dev_get_drvdata(dev); - struct gpio_trig_data *gpio_data = led->trigger_data; - - return sprintf(buf, "%u\n", gpio_data->inverted); -} - -static ssize_t gpio_trig_inverted_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t n) -{ - struct led_classdev *led = dev_get_drvdata(dev); - struct gpio_trig_data *gpio_data = led->trigger_data; - unsigned long inverted; - int ret; - - ret = kstrtoul(buf, 10, &inverted); - if (ret < 0) - return ret; - - if (inverted > 1) - return -EINVAL; - - gpio_data->inverted = inverted; - - /* After inverting, we need to update the LED. */ - schedule_work(&gpio_data->work); - - return n; -} -static DEVICE_ATTR(inverted, 0644, gpio_trig_inverted_show, - gpio_trig_inverted_store); - -static ssize_t gpio_trig_gpio_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led = dev_get_drvdata(dev); - struct gpio_trig_data *gpio_data = led->trigger_data; - - return sprintf(buf, "%u\n", gpio_data->gpio); -} - -static ssize_t gpio_trig_gpio_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t n) -{ - struct led_classdev *led = dev_get_drvdata(dev); - struct gpio_trig_data *gpio_data = led->trigger_data; - unsigned gpio; - int ret; - - ret = sscanf(buf, "%u", &gpio); - if (ret < 1) { - dev_err(dev, "couldn't read gpio number\n"); - flush_work(&gpio_data->work); - return -EINVAL; - } - - if (gpio_data->gpio == gpio) - return n; - - if (!gpio) { - if (gpio_data->gpio != 0) - free_irq(gpio_to_irq(gpio_data->gpio), led); - gpio_data->gpio = 0; - return n; - } - - ret = request_irq(gpio_to_irq(gpio), gpio_trig_irq, - IRQF_SHARED | IRQF_TRIGGER_RISING - | IRQF_TRIGGER_FALLING, "ledtrig-gpio", led); - if (ret) { - dev_err(dev, "request_irq failed with error %d\n", ret); - } else { - if (gpio_data->gpio != 0) - free_irq(gpio_to_irq(gpio_data->gpio), led); - gpio_data->gpio = gpio; - } - - return ret ? ret : n; -} -static DEVICE_ATTR(gpio, 0644, gpio_trig_gpio_show, gpio_trig_gpio_store); - -static void gpio_trig_activate(struct led_classdev *led) -{ - struct gpio_trig_data *gpio_data; - int ret; - - gpio_data = kzalloc(sizeof(*gpio_data), GFP_KERNEL); - if (!gpio_data) - return; - - ret = device_create_file(led->dev, &dev_attr_gpio); - if (ret) - goto err_gpio; - - ret = device_create_file(led->dev, &dev_attr_inverted); - if (ret) - goto err_inverted; - - ret = device_create_file(led->dev, &dev_attr_desired_brightness); - if (ret) - goto err_brightness; - - gpio_data->led = led; - led->trigger_data = gpio_data; - INIT_WORK(&gpio_data->work, gpio_trig_work); - led->activated = true; - - return; - -err_brightness: - device_remove_file(led->dev, &dev_attr_inverted); - -err_inverted: - device_remove_file(led->dev, &dev_attr_gpio); - -err_gpio: - kfree(gpio_data); -} - -static void gpio_trig_deactivate(struct led_classdev *led) -{ - struct gpio_trig_data *gpio_data = led->trigger_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); - flush_work(&gpio_data->work); - if (gpio_data->gpio != 0) - free_irq(gpio_to_irq(gpio_data->gpio), led); - kfree(gpio_data); - led->activated = false; - } -} - -static struct led_trigger gpio_led_trigger = { - .name = "gpio", - .activate = gpio_trig_activate, - .deactivate = gpio_trig_deactivate, -}; - -static int __init gpio_trig_init(void) -{ - return led_trigger_register(&gpio_led_trigger); -} -module_init(gpio_trig_init); - -static void __exit gpio_trig_exit(void) -{ - led_trigger_unregister(&gpio_led_trigger); -} -module_exit(gpio_trig_exit); - -MODULE_AUTHOR("Felipe Balbi "); -MODULE_DESCRIPTION("GPIO LED trigger"); -MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-heartbeat.c b/drivers/leds/ledtrig-heartbeat.c deleted file mode 100644 index 1edc746..0000000 --- a/drivers/leds/ledtrig-heartbeat.c +++ /dev/null @@ -1,161 +0,0 @@ -/* - * LED Heartbeat Trigger - * - * Copyright (C) 2006 Atsushi Nemoto - * - * Based on Richard Purdie's ledtrig-timer.c and some arch's - * CONFIG_HEARTBEAT code. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include "leds.h" - -static int panic_heartbeats; - -struct heartbeat_trig_data { - unsigned int phase; - unsigned int period; - struct timer_list timer; -}; - -static void led_heartbeat_function(unsigned long data) -{ - struct led_classdev *led_cdev = (struct led_classdev *) data; - struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data; - unsigned long brightness = LED_OFF; - unsigned long delay = 0; - - if (unlikely(panic_heartbeats)) { - led_set_brightness(led_cdev, LED_OFF); - return; - } - - /* acts like an actual heart beat -- ie thump-thump-pause... */ - switch (heartbeat_data->phase) { - case 0: - /* - * The hyperbolic function below modifies the - * heartbeat period length in dependency of the - * current (1min) load. It goes through the points - * f(0)=1260, f(1)=860, f(5)=510, f(inf)->300. - */ - heartbeat_data->period = 300 + - (6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT)); - heartbeat_data->period = - msecs_to_jiffies(heartbeat_data->period); - delay = msecs_to_jiffies(70); - heartbeat_data->phase++; - brightness = led_cdev->max_brightness; - break; - case 1: - delay = heartbeat_data->period / 4 - msecs_to_jiffies(70); - heartbeat_data->phase++; - break; - case 2: - delay = msecs_to_jiffies(70); - heartbeat_data->phase++; - brightness = led_cdev->max_brightness; - break; - default: - delay = heartbeat_data->period - heartbeat_data->period / 4 - - msecs_to_jiffies(70); - heartbeat_data->phase = 0; - break; - } - - __led_set_brightness(led_cdev, brightness); - mod_timer(&heartbeat_data->timer, jiffies + delay); -} - -static void heartbeat_trig_activate(struct led_classdev *led_cdev) -{ - struct heartbeat_trig_data *heartbeat_data; - - heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL); - if (!heartbeat_data) - return; - - led_cdev->trigger_data = heartbeat_data; - setup_timer(&heartbeat_data->timer, - 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 (led_cdev->activated) { - del_timer_sync(&heartbeat_data->timer); - kfree(heartbeat_data); - led_cdev->activated = false; - } -} - -static struct led_trigger heartbeat_led_trigger = { - .name = "heartbeat", - .activate = heartbeat_trig_activate, - .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 int heartbeat_panic_notifier(struct notifier_block *nb, - unsigned long code, void *unused) -{ - panic_heartbeats = 1; - 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_panic_notifier, -}; - -static int __init heartbeat_trig_init(void) -{ - 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); -} - -module_init(heartbeat_trig_init); -module_exit(heartbeat_trig_exit); - -MODULE_AUTHOR("Atsushi Nemoto "); -MODULE_DESCRIPTION("Heartbeat LED trigger"); -MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-ide-disk.c b/drivers/leds/ledtrig-ide-disk.c deleted file mode 100644 index 2cd7c0c..0000000 --- a/drivers/leds/ledtrig-ide-disk.c +++ /dev/null @@ -1,47 +0,0 @@ -/* - * LED IDE-Disk Activity Trigger - * - * Copyright 2006 Openedhand Ltd. - * - * Author: Richard Purdie - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include - -#define BLINK_DELAY 30 - -DEFINE_LED_TRIGGER(ledtrig_ide); -static unsigned long ide_blink_delay = BLINK_DELAY; - -void ledtrig_ide_activity(void) -{ - led_trigger_blink_oneshot(ledtrig_ide, - &ide_blink_delay, &ide_blink_delay, 0); -} -EXPORT_SYMBOL(ledtrig_ide_activity); - -static int __init ledtrig_ide_init(void) -{ - led_trigger_register_simple("ide-disk", &ledtrig_ide); - return 0; -} - -static void __exit ledtrig_ide_exit(void) -{ - led_trigger_unregister_simple(ledtrig_ide); -} - -module_init(ledtrig_ide_init); -module_exit(ledtrig_ide_exit); - -MODULE_AUTHOR("Richard Purdie "); -MODULE_DESCRIPTION("LED IDE Disk Activity Trigger"); -MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-oneshot.c b/drivers/leds/ledtrig-oneshot.c deleted file mode 100644 index 2c029aa..0000000 --- a/drivers/leds/ledtrig-oneshot.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * One-shot LED Trigger - * - * Copyright 2012, Fabio Baltieri - * - * Based on ledtrig-timer.c by Richard Purdie - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include "leds.h" - -#define DEFAULT_DELAY 100 - -struct oneshot_trig_data { - unsigned int invert; -}; - -static ssize_t led_shot(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; - - led_blink_set_oneshot(led_cdev, - &led_cdev->blink_delay_on, &led_cdev->blink_delay_off, - oneshot_data->invert); - - /* content is ignored */ - return size; -} -static ssize_t led_invert_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; - - return sprintf(buf, "%u\n", oneshot_data->invert); -} - -static ssize_t led_invert_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; - unsigned long state; - int ret; - - ret = kstrtoul(buf, 0, &state); - if (ret) - return ret; - - oneshot_data->invert = !!state; - - if (oneshot_data->invert) - __led_set_brightness(led_cdev, LED_FULL); - else - __led_set_brightness(led_cdev, LED_OFF); - - return size; -} - -static ssize_t led_delay_on_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - - return sprintf(buf, "%lu\n", led_cdev->blink_delay_on); -} - -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); - unsigned long state; - int ret; - - ret = kstrtoul(buf, 0, &state); - if (ret) - return ret; - - led_cdev->blink_delay_on = state; - - return size; -} -static ssize_t led_delay_off_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - - return sprintf(buf, "%lu\n", led_cdev->blink_delay_off); -} - -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); - unsigned long state; - int ret; - - ret = kstrtoul(buf, 0, &state); - if (ret) - return ret; - - led_cdev->blink_delay_off = state; - - return size; -} - -static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store); -static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store); -static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store); -static DEVICE_ATTR(shot, 0200, NULL, led_shot); - -static void oneshot_trig_activate(struct led_classdev *led_cdev) -{ - struct oneshot_trig_data *oneshot_data; - int rc; - - oneshot_data = kzalloc(sizeof(*oneshot_data), GFP_KERNEL); - if (!oneshot_data) - return; - - led_cdev->trigger_data = oneshot_data; - - rc = device_create_file(led_cdev->dev, &dev_attr_delay_on); - if (rc) - goto err_out_trig_data; - rc = device_create_file(led_cdev->dev, &dev_attr_delay_off); - if (rc) - goto err_out_delayon; - rc = device_create_file(led_cdev->dev, &dev_attr_invert); - if (rc) - goto err_out_delayoff; - rc = device_create_file(led_cdev->dev, &dev_attr_shot); - if (rc) - goto err_out_invert; - - led_cdev->blink_delay_on = DEFAULT_DELAY; - led_cdev->blink_delay_off = DEFAULT_DELAY; - - led_cdev->activated = true; - - return; - -err_out_invert: - device_remove_file(led_cdev->dev, &dev_attr_invert); -err_out_delayoff: - device_remove_file(led_cdev->dev, &dev_attr_delay_off); -err_out_delayon: - device_remove_file(led_cdev->dev, &dev_attr_delay_on); -err_out_trig_data: - kfree(led_cdev->trigger_data); -} - -static void oneshot_trig_deactivate(struct led_classdev *led_cdev) -{ - struct oneshot_trig_data *oneshot_data = 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); - device_remove_file(led_cdev->dev, &dev_attr_invert); - device_remove_file(led_cdev->dev, &dev_attr_shot); - kfree(oneshot_data); - led_cdev->activated = false; - } - - /* Stop blinking */ - led_set_brightness(led_cdev, LED_OFF); -} - -static struct led_trigger oneshot_led_trigger = { - .name = "oneshot", - .activate = oneshot_trig_activate, - .deactivate = oneshot_trig_deactivate, -}; - -static int __init oneshot_trig_init(void) -{ - return led_trigger_register(&oneshot_led_trigger); -} - -static void __exit oneshot_trig_exit(void) -{ - led_trigger_unregister(&oneshot_led_trigger); -} - -module_init(oneshot_trig_init); -module_exit(oneshot_trig_exit); - -MODULE_AUTHOR("Fabio Baltieri "); -MODULE_DESCRIPTION("One-shot LED trigger"); -MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-timer.c b/drivers/leds/ledtrig-timer.c deleted file mode 100644 index f774d05..0000000 --- a/drivers/leds/ledtrig-timer.c +++ /dev/null @@ -1,131 +0,0 @@ -/* - * LED Kernel Timer Trigger - * - * Copyright 2005-2006 Openedhand Ltd. - * - * Author: Richard Purdie - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include "leds.h" - -static ssize_t led_delay_on_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - - return sprintf(buf, "%lu\n", led_cdev->blink_delay_on); -} - -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); - unsigned long state; - ssize_t ret = -EINVAL; - - ret = kstrtoul(buf, 10, &state); - if (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, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - - return sprintf(buf, "%lu\n", led_cdev->blink_delay_off); -} - -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); - unsigned long state; - ssize_t ret = -EINVAL; - - 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); -static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store); - -static void timer_trig_activate(struct led_classdev *led_cdev) -{ - int rc; - - led_cdev->trigger_data = NULL; - - rc = device_create_file(led_cdev->dev, &dev_attr_delay_on); - if (rc) - return; - rc = device_create_file(led_cdev->dev, &dev_attr_delay_off); - if (rc) - goto err_out_delayon; - - led_blink_set(led_cdev, &led_cdev->blink_delay_on, - &led_cdev->blink_delay_off); - led_cdev->activated = true; - - return; - -err_out_delayon: - device_remove_file(led_cdev->dev, &dev_attr_delay_on); -} - -static void timer_trig_deactivate(struct led_classdev *led_cdev) -{ - 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 */ - led_set_brightness(led_cdev, LED_OFF); -} - -static struct led_trigger timer_led_trigger = { - .name = "timer", - .activate = timer_trig_activate, - .deactivate = timer_trig_deactivate, -}; - -static int __init timer_trig_init(void) -{ - return led_trigger_register(&timer_led_trigger); -} - -static void __exit timer_trig_exit(void) -{ - led_trigger_unregister(&timer_led_trigger); -} - -module_init(timer_trig_init); -module_exit(timer_trig_exit); - -MODULE_AUTHOR("Richard Purdie "); -MODULE_DESCRIPTION("Timer LED trigger"); -MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-transient.c b/drivers/leds/ledtrig-transient.c deleted file mode 100644 index 398f104..0000000 --- a/drivers/leds/ledtrig-transient.c +++ /dev/null @@ -1,237 +0,0 @@ -/* - * LED Kernel Transient Trigger - * - * Copyright (C) 2012 Shuah Khan - * - * Based on Richard Purdie's ledtrig-timer.c and Atsushi Nemoto's - * ledtrig-heartbeat.c - * Design and use-case input from Jonas Bonn and - * Neil Brown - * - * 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 -#include -#include -#include -#include -#include -#include -#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 "); -MODULE_DESCRIPTION("Transient LED trigger"); -MODULE_LICENSE("GPL"); diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig new file mode 100644 index 0000000..eaa286d --- /dev/null +++ b/drivers/leds/trigger/Kconfig @@ -0,0 +1,103 @@ +menuconfig LEDS_TRIGGERS + bool "LED Trigger support" + depends on LEDS_CLASS + help + This option enables trigger support for the leds class. + These triggers allow kernel events to drive the LEDs and can + be configured via sysfs. If unsure, say Y. + +if LEDS_TRIGGERS + +config LEDS_TRIGGER_TIMER + tristate "LED Timer Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to be controlled by a programmable timer + via sysfs. Some LED hardware can be programmed to start + blinking the LED without any further software interaction. + For more details read Documentation/leds/leds-class.txt. + + If unsure, say Y. + +config LEDS_TRIGGER_ONESHOT + tristate "LED One-shot Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to blink in one-shot pulses with parameters + controlled via sysfs. It's useful to notify the user on + sporadic events, when there are no clear begin and end trap points, + or on dense events, where this blinks the LED at constant rate if + rearmed continuously. + + It also shows how to use the led_blink_set_oneshot() function. + + If unsure, say Y. + +config LEDS_TRIGGER_IDE_DISK + bool "LED IDE Disk Trigger" + depends on IDE_GD_ATA + depends on LEDS_TRIGGERS + help + This allows LEDs to be controlled by IDE disk activity. + If unsure, say Y. + +config LEDS_TRIGGER_HEARTBEAT + tristate "LED Heartbeat Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to be controlled by a CPU load average. + The flash frequency is a hyperbolic function of the 1-minute + load average. + If unsure, say Y. + +config LEDS_TRIGGER_BACKLIGHT + tristate "LED backlight Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to be controlled as a backlight device: they + turn off and on when the display is blanked and unblanked. + + If unsure, say N. + +config LEDS_TRIGGER_CPU + bool "LED CPU Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to be controlled by active CPUs. This shows + the active CPUs across an array of LEDs so you can see which + CPUs are active on the system at any given moment. + + If unsure, say N. + +config LEDS_TRIGGER_GPIO + tristate "LED GPIO Trigger" + depends on LEDS_TRIGGERS + depends on GPIOLIB + help + This allows LEDs to be controlled by gpio events. It's good + when using gpios as switches and triggering the needed LEDs + from there. One use case is n810's keypad LEDs that could + be triggered by this trigger when user slides up to show + keypad. + + If unsure, say N. + +config LEDS_TRIGGER_DEFAULT_ON + tristate "LED Default ON Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to be initialised in the ON state. + If unsure, say Y. + +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 hardware. + If unsure, say Y. + +endif # LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile new file mode 100644 index 0000000..554e46e --- /dev/null +++ b/drivers/leds/trigger/Makefile @@ -0,0 +1,9 @@ +obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o +obj-$(CONFIG_LEDS_TRIGGER_ONESHOT) += ledtrig-oneshot.o +obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o +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_CPU) += ledtrig-cpu.o +obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o +obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o diff --git a/drivers/leds/trigger/ledtrig-backlight.c b/drivers/leds/trigger/ledtrig-backlight.c new file mode 100644 index 0000000..3c9c88a --- /dev/null +++ b/drivers/leds/trigger/ledtrig-backlight.c @@ -0,0 +1,166 @@ +/* + * Backlight emulation LED trigger + * + * Copyright 2008 (C) Rodolfo Giometti + * Copyright 2008 (C) Eurotech S.p.A. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include "../leds.h" + +#define BLANK 1 +#define UNBLANK 0 + +struct bl_trig_notifier { + struct led_classdev *led; + int brightness; + int old_status; + struct notifier_block notifier; + unsigned invert; +}; + +static int fb_notifier_callback(struct notifier_block *p, + unsigned long event, void *data) +{ + struct bl_trig_notifier *n = container_of(p, + struct bl_trig_notifier, notifier); + struct led_classdev *led = n->led; + struct fb_event *fb_event = data; + int *blank = fb_event->data; + int new_status = *blank ? BLANK : UNBLANK; + + switch (event) { + case FB_EVENT_BLANK: + if (new_status == n->old_status) + break; + + if ((n->old_status == UNBLANK) ^ n->invert) { + n->brightness = led->brightness; + __led_set_brightness(led, LED_OFF); + } else { + __led_set_brightness(led, n->brightness); + } + + n->old_status = new_status; + + break; + } + + return 0; +} + +static ssize_t bl_trig_invert_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led = dev_get_drvdata(dev); + struct bl_trig_notifier *n = led->trigger_data; + + return sprintf(buf, "%u\n", n->invert); +} + +static ssize_t bl_trig_invert_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t num) +{ + struct led_classdev *led = dev_get_drvdata(dev); + struct bl_trig_notifier *n = led->trigger_data; + unsigned long invert; + int ret; + + ret = kstrtoul(buf, 10, &invert); + if (ret < 0) + return ret; + + if (invert > 1) + return -EINVAL; + + n->invert = invert; + + /* After inverting, we need to update the LED. */ + if ((n->old_status == BLANK) ^ n->invert) + __led_set_brightness(led, LED_OFF); + else + __led_set_brightness(led, n->brightness); + + return num; +} +static DEVICE_ATTR(inverted, 0644, bl_trig_invert_show, bl_trig_invert_store); + +static void bl_trig_activate(struct led_classdev *led) +{ + int ret; + + struct bl_trig_notifier *n; + + n = kzalloc(sizeof(struct bl_trig_notifier), GFP_KERNEL); + led->trigger_data = n; + if (!n) { + dev_err(led->dev, "unable to allocate backlight trigger\n"); + return; + } + + ret = device_create_file(led->dev, &dev_attr_inverted); + if (ret) + goto err_invert; + + n->led = led; + n->brightness = led->brightness; + n->old_status = UNBLANK; + n->notifier.notifier_call = fb_notifier_callback; + + ret = fb_register_client(&n->notifier); + if (ret) + dev_err(led->dev, "unable to register backlight trigger\n"); + led->activated = true; + + return; + +err_invert: + led->trigger_data = NULL; + kfree(n); +} + +static void bl_trig_deactivate(struct led_classdev *led) +{ + struct bl_trig_notifier *n = + (struct bl_trig_notifier *) led->trigger_data; + + if (led->activated) { + device_remove_file(led->dev, &dev_attr_inverted); + fb_unregister_client(&n->notifier); + kfree(n); + led->activated = false; + } +} + +static struct led_trigger bl_led_trigger = { + .name = "backlight", + .activate = bl_trig_activate, + .deactivate = bl_trig_deactivate +}; + +static int __init bl_trig_init(void) +{ + return led_trigger_register(&bl_led_trigger); +} + +static void __exit bl_trig_exit(void) +{ + led_trigger_unregister(&bl_led_trigger); +} + +module_init(bl_trig_init); +module_exit(bl_trig_exit); + +MODULE_AUTHOR("Rodolfo Giometti "); +MODULE_DESCRIPTION("Backlight emulation LED trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/trigger/ledtrig-cpu.c b/drivers/leds/trigger/ledtrig-cpu.c new file mode 100644 index 0000000..118335e --- /dev/null +++ b/drivers/leds/trigger/ledtrig-cpu.c @@ -0,0 +1,142 @@ +/* + * ledtrig-cpu.c - LED trigger based on CPU activity + * + * This LED trigger will be registered for each possible CPU and named as + * cpu0, cpu1, cpu2, cpu3, etc. + * + * It can be bound to any LED just like other triggers using either a + * board file or via sysfs interface. + * + * An API named ledtrig_cpu is exported for any user, who want to add CPU + * activity indication in their code + * + * Copyright 2011 Linus Walleij + * Copyright 2011 - 2012 Bryan Wu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../leds.h" + +#define MAX_NAME_LEN 8 + +struct led_trigger_cpu { + char name[MAX_NAME_LEN]; + struct led_trigger *_trig; +}; + +static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig); + +/** + * ledtrig_cpu - emit a CPU event as a trigger + * @evt: CPU event to be emitted + * + * Emit a CPU event on a CPU core, which will trigger a + * binded LED to turn on or turn off. + */ +void ledtrig_cpu(enum cpu_led_event ledevt) +{ + struct led_trigger_cpu *trig = &__get_cpu_var(cpu_trig); + + /* Locate the correct CPU LED */ + switch (ledevt) { + case CPU_LED_IDLE_END: + case CPU_LED_START: + /* Will turn the LED on, max brightness */ + led_trigger_event(trig->_trig, LED_FULL); + break; + + case CPU_LED_IDLE_START: + case CPU_LED_STOP: + case CPU_LED_HALTED: + /* Will turn the LED off */ + led_trigger_event(trig->_trig, LED_OFF); + break; + + default: + /* Will leave the LED as it is */ + break; + } +} +EXPORT_SYMBOL(ledtrig_cpu); + +static int ledtrig_cpu_syscore_suspend(void) +{ + ledtrig_cpu(CPU_LED_STOP); + return 0; +} + +static void ledtrig_cpu_syscore_resume(void) +{ + ledtrig_cpu(CPU_LED_START); +} + +static void ledtrig_cpu_syscore_shutdown(void) +{ + ledtrig_cpu(CPU_LED_HALTED); +} + +static struct syscore_ops ledtrig_cpu_syscore_ops = { + .shutdown = ledtrig_cpu_syscore_shutdown, + .suspend = ledtrig_cpu_syscore_suspend, + .resume = ledtrig_cpu_syscore_resume, +}; + +static int __init ledtrig_cpu_init(void) +{ + int cpu; + + /* Supports up to 9999 cpu cores */ + BUILD_BUG_ON(CONFIG_NR_CPUS > 9999); + + /* + * Registering CPU led trigger for each CPU core here + * ignores CPU hotplug, but after this CPU hotplug works + * fine with this trigger. + */ + for_each_possible_cpu(cpu) { + struct led_trigger_cpu *trig = &per_cpu(cpu_trig, cpu); + + snprintf(trig->name, MAX_NAME_LEN, "cpu%d", cpu); + + led_trigger_register_simple(trig->name, &trig->_trig); + } + + register_syscore_ops(&ledtrig_cpu_syscore_ops); + + pr_info("ledtrig-cpu: registered to indicate activity on CPUs\n"); + + return 0; +} +module_init(ledtrig_cpu_init); + +static void __exit ledtrig_cpu_exit(void) +{ + int cpu; + + for_each_possible_cpu(cpu) { + struct led_trigger_cpu *trig = &per_cpu(cpu_trig, cpu); + + led_trigger_unregister_simple(trig->_trig); + trig->_trig = NULL; + memset(trig->name, 0, MAX_NAME_LEN); + } + + unregister_syscore_ops(&ledtrig_cpu_syscore_ops); +} +module_exit(ledtrig_cpu_exit); + +MODULE_AUTHOR("Linus Walleij "); +MODULE_AUTHOR("Bryan Wu "); +MODULE_DESCRIPTION("CPU LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/trigger/ledtrig-default-on.c b/drivers/leds/trigger/ledtrig-default-on.c new file mode 100644 index 0000000..81a91be --- /dev/null +++ b/drivers/leds/trigger/ledtrig-default-on.c @@ -0,0 +1,45 @@ +/* + * LED Kernel Default ON Trigger + * + * Copyright 2008 Nick Forbes + * + * Based on Richard Purdie's ledtrig-timer.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include "../leds.h" + +static void defon_trig_activate(struct led_classdev *led_cdev) +{ + __led_set_brightness(led_cdev, led_cdev->max_brightness); +} + +static struct led_trigger defon_led_trigger = { + .name = "default-on", + .activate = defon_trig_activate, +}; + +static int __init defon_trig_init(void) +{ + return led_trigger_register(&defon_led_trigger); +} + +static void __exit defon_trig_exit(void) +{ + led_trigger_unregister(&defon_led_trigger); +} + +module_init(defon_trig_init); +module_exit(defon_trig_exit); + +MODULE_AUTHOR("Nick Forbes "); +MODULE_DESCRIPTION("Default-ON LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/trigger/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c new file mode 100644 index 0000000..35812e3 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-gpio.c @@ -0,0 +1,253 @@ +/* + * ledtrig-gio.c - LED Trigger Based on GPIO events + * + * Copyright 2009 Felipe Balbi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../leds.h" + +struct gpio_trig_data { + struct led_classdev *led; + struct work_struct work; + + unsigned desired_brightness; /* desired brightness when led is on */ + unsigned inverted; /* true when gpio is inverted */ + unsigned gpio; /* gpio that triggers the leds */ +}; + +static irqreturn_t gpio_trig_irq(int irq, void *_led) +{ + struct led_classdev *led = _led; + struct gpio_trig_data *gpio_data = led->trigger_data; + + /* just schedule_work since gpio_get_value can sleep */ + schedule_work(&gpio_data->work); + + return IRQ_HANDLED; +}; + +static void gpio_trig_work(struct work_struct *work) +{ + struct gpio_trig_data *gpio_data = container_of(work, + struct gpio_trig_data, work); + int tmp; + + if (!gpio_data->gpio) + return; + + tmp = gpio_get_value(gpio_data->gpio); + if (gpio_data->inverted) + tmp = !tmp; + + if (tmp) { + if (gpio_data->desired_brightness) + __led_set_brightness(gpio_data->led, + gpio_data->desired_brightness); + else + __led_set_brightness(gpio_data->led, LED_FULL); + } else { + __led_set_brightness(gpio_data->led, LED_OFF); + } +} + +static ssize_t gpio_trig_brightness_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led = dev_get_drvdata(dev); + struct gpio_trig_data *gpio_data = led->trigger_data; + + return sprintf(buf, "%u\n", gpio_data->desired_brightness); +} + +static ssize_t gpio_trig_brightness_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct led_classdev *led = dev_get_drvdata(dev); + struct gpio_trig_data *gpio_data = led->trigger_data; + unsigned desired_brightness; + int ret; + + ret = sscanf(buf, "%u", &desired_brightness); + if (ret < 1 || desired_brightness > 255) { + dev_err(dev, "invalid value\n"); + return -EINVAL; + } + + gpio_data->desired_brightness = desired_brightness; + + return n; +} +static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show, + gpio_trig_brightness_store); + +static ssize_t gpio_trig_inverted_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led = dev_get_drvdata(dev); + struct gpio_trig_data *gpio_data = led->trigger_data; + + return sprintf(buf, "%u\n", gpio_data->inverted); +} + +static ssize_t gpio_trig_inverted_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct led_classdev *led = dev_get_drvdata(dev); + struct gpio_trig_data *gpio_data = led->trigger_data; + unsigned long inverted; + int ret; + + ret = kstrtoul(buf, 10, &inverted); + if (ret < 0) + return ret; + + if (inverted > 1) + return -EINVAL; + + gpio_data->inverted = inverted; + + /* After inverting, we need to update the LED. */ + schedule_work(&gpio_data->work); + + return n; +} +static DEVICE_ATTR(inverted, 0644, gpio_trig_inverted_show, + gpio_trig_inverted_store); + +static ssize_t gpio_trig_gpio_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led = dev_get_drvdata(dev); + struct gpio_trig_data *gpio_data = led->trigger_data; + + return sprintf(buf, "%u\n", gpio_data->gpio); +} + +static ssize_t gpio_trig_gpio_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct led_classdev *led = dev_get_drvdata(dev); + struct gpio_trig_data *gpio_data = led->trigger_data; + unsigned gpio; + int ret; + + ret = sscanf(buf, "%u", &gpio); + if (ret < 1) { + dev_err(dev, "couldn't read gpio number\n"); + flush_work(&gpio_data->work); + return -EINVAL; + } + + if (gpio_data->gpio == gpio) + return n; + + if (!gpio) { + if (gpio_data->gpio != 0) + free_irq(gpio_to_irq(gpio_data->gpio), led); + gpio_data->gpio = 0; + return n; + } + + ret = request_irq(gpio_to_irq(gpio), gpio_trig_irq, + IRQF_SHARED | IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING, "ledtrig-gpio", led); + if (ret) { + dev_err(dev, "request_irq failed with error %d\n", ret); + } else { + if (gpio_data->gpio != 0) + free_irq(gpio_to_irq(gpio_data->gpio), led); + gpio_data->gpio = gpio; + } + + return ret ? ret : n; +} +static DEVICE_ATTR(gpio, 0644, gpio_trig_gpio_show, gpio_trig_gpio_store); + +static void gpio_trig_activate(struct led_classdev *led) +{ + struct gpio_trig_data *gpio_data; + int ret; + + gpio_data = kzalloc(sizeof(*gpio_data), GFP_KERNEL); + if (!gpio_data) + return; + + ret = device_create_file(led->dev, &dev_attr_gpio); + if (ret) + goto err_gpio; + + ret = device_create_file(led->dev, &dev_attr_inverted); + if (ret) + goto err_inverted; + + ret = device_create_file(led->dev, &dev_attr_desired_brightness); + if (ret) + goto err_brightness; + + gpio_data->led = led; + led->trigger_data = gpio_data; + INIT_WORK(&gpio_data->work, gpio_trig_work); + led->activated = true; + + return; + +err_brightness: + device_remove_file(led->dev, &dev_attr_inverted); + +err_inverted: + device_remove_file(led->dev, &dev_attr_gpio); + +err_gpio: + kfree(gpio_data); +} + +static void gpio_trig_deactivate(struct led_classdev *led) +{ + struct gpio_trig_data *gpio_data = led->trigger_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); + flush_work(&gpio_data->work); + if (gpio_data->gpio != 0) + free_irq(gpio_to_irq(gpio_data->gpio), led); + kfree(gpio_data); + led->activated = false; + } +} + +static struct led_trigger gpio_led_trigger = { + .name = "gpio", + .activate = gpio_trig_activate, + .deactivate = gpio_trig_deactivate, +}; + +static int __init gpio_trig_init(void) +{ + return led_trigger_register(&gpio_led_trigger); +} +module_init(gpio_trig_init); + +static void __exit gpio_trig_exit(void) +{ + led_trigger_unregister(&gpio_led_trigger); +} +module_exit(gpio_trig_exit); + +MODULE_AUTHOR("Felipe Balbi "); +MODULE_DESCRIPTION("GPIO LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/trigger/ledtrig-heartbeat.c b/drivers/leds/trigger/ledtrig-heartbeat.c new file mode 100644 index 0000000..5c8464a --- /dev/null +++ b/drivers/leds/trigger/ledtrig-heartbeat.c @@ -0,0 +1,161 @@ +/* + * LED Heartbeat Trigger + * + * Copyright (C) 2006 Atsushi Nemoto + * + * Based on Richard Purdie's ledtrig-timer.c and some arch's + * CONFIG_HEARTBEAT code. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../leds.h" + +static int panic_heartbeats; + +struct heartbeat_trig_data { + unsigned int phase; + unsigned int period; + struct timer_list timer; +}; + +static void led_heartbeat_function(unsigned long data) +{ + struct led_classdev *led_cdev = (struct led_classdev *) data; + struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data; + unsigned long brightness = LED_OFF; + unsigned long delay = 0; + + if (unlikely(panic_heartbeats)) { + led_set_brightness(led_cdev, LED_OFF); + return; + } + + /* acts like an actual heart beat -- ie thump-thump-pause... */ + switch (heartbeat_data->phase) { + case 0: + /* + * The hyperbolic function below modifies the + * heartbeat period length in dependency of the + * current (1min) load. It goes through the points + * f(0)=1260, f(1)=860, f(5)=510, f(inf)->300. + */ + heartbeat_data->period = 300 + + (6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT)); + heartbeat_data->period = + msecs_to_jiffies(heartbeat_data->period); + delay = msecs_to_jiffies(70); + heartbeat_data->phase++; + brightness = led_cdev->max_brightness; + break; + case 1: + delay = heartbeat_data->period / 4 - msecs_to_jiffies(70); + heartbeat_data->phase++; + break; + case 2: + delay = msecs_to_jiffies(70); + heartbeat_data->phase++; + brightness = led_cdev->max_brightness; + break; + default: + delay = heartbeat_data->period - heartbeat_data->period / 4 - + msecs_to_jiffies(70); + heartbeat_data->phase = 0; + break; + } + + __led_set_brightness(led_cdev, brightness); + mod_timer(&heartbeat_data->timer, jiffies + delay); +} + +static void heartbeat_trig_activate(struct led_classdev *led_cdev) +{ + struct heartbeat_trig_data *heartbeat_data; + + heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL); + if (!heartbeat_data) + return; + + led_cdev->trigger_data = heartbeat_data; + setup_timer(&heartbeat_data->timer, + 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 (led_cdev->activated) { + del_timer_sync(&heartbeat_data->timer); + kfree(heartbeat_data); + led_cdev->activated = false; + } +} + +static struct led_trigger heartbeat_led_trigger = { + .name = "heartbeat", + .activate = heartbeat_trig_activate, + .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 int heartbeat_panic_notifier(struct notifier_block *nb, + unsigned long code, void *unused) +{ + panic_heartbeats = 1; + 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_panic_notifier, +}; + +static int __init heartbeat_trig_init(void) +{ + 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); +} + +module_init(heartbeat_trig_init); +module_exit(heartbeat_trig_exit); + +MODULE_AUTHOR("Atsushi Nemoto "); +MODULE_DESCRIPTION("Heartbeat LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/trigger/ledtrig-ide-disk.c b/drivers/leds/trigger/ledtrig-ide-disk.c new file mode 100644 index 0000000..2cd7c0c --- /dev/null +++ b/drivers/leds/trigger/ledtrig-ide-disk.c @@ -0,0 +1,47 @@ +/* + * LED IDE-Disk Activity Trigger + * + * Copyright 2006 Openedhand Ltd. + * + * Author: Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include + +#define BLINK_DELAY 30 + +DEFINE_LED_TRIGGER(ledtrig_ide); +static unsigned long ide_blink_delay = BLINK_DELAY; + +void ledtrig_ide_activity(void) +{ + led_trigger_blink_oneshot(ledtrig_ide, + &ide_blink_delay, &ide_blink_delay, 0); +} +EXPORT_SYMBOL(ledtrig_ide_activity); + +static int __init ledtrig_ide_init(void) +{ + led_trigger_register_simple("ide-disk", &ledtrig_ide); + return 0; +} + +static void __exit ledtrig_ide_exit(void) +{ + led_trigger_unregister_simple(ledtrig_ide); +} + +module_init(ledtrig_ide_init); +module_exit(ledtrig_ide_exit); + +MODULE_AUTHOR("Richard Purdie "); +MODULE_DESCRIPTION("LED IDE Disk Activity Trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/trigger/ledtrig-oneshot.c b/drivers/leds/trigger/ledtrig-oneshot.c new file mode 100644 index 0000000..cb4c746 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-oneshot.c @@ -0,0 +1,204 @@ +/* + * One-shot LED Trigger + * + * Copyright 2012, Fabio Baltieri + * + * Based on ledtrig-timer.c by Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../leds.h" + +#define DEFAULT_DELAY 100 + +struct oneshot_trig_data { + unsigned int invert; +}; + +static ssize_t led_shot(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; + + led_blink_set_oneshot(led_cdev, + &led_cdev->blink_delay_on, &led_cdev->blink_delay_off, + oneshot_data->invert); + + /* content is ignored */ + return size; +} +static ssize_t led_invert_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; + + return sprintf(buf, "%u\n", oneshot_data->invert); +} + +static ssize_t led_invert_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; + unsigned long state; + int ret; + + ret = kstrtoul(buf, 0, &state); + if (ret) + return ret; + + oneshot_data->invert = !!state; + + if (oneshot_data->invert) + __led_set_brightness(led_cdev, LED_FULL); + else + __led_set_brightness(led_cdev, LED_OFF); + + return size; +} + +static ssize_t led_delay_on_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return sprintf(buf, "%lu\n", led_cdev->blink_delay_on); +} + +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); + unsigned long state; + int ret; + + ret = kstrtoul(buf, 0, &state); + if (ret) + return ret; + + led_cdev->blink_delay_on = state; + + return size; +} +static ssize_t led_delay_off_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return sprintf(buf, "%lu\n", led_cdev->blink_delay_off); +} + +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); + unsigned long state; + int ret; + + ret = kstrtoul(buf, 0, &state); + if (ret) + return ret; + + led_cdev->blink_delay_off = state; + + return size; +} + +static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store); +static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store); +static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store); +static DEVICE_ATTR(shot, 0200, NULL, led_shot); + +static void oneshot_trig_activate(struct led_classdev *led_cdev) +{ + struct oneshot_trig_data *oneshot_data; + int rc; + + oneshot_data = kzalloc(sizeof(*oneshot_data), GFP_KERNEL); + if (!oneshot_data) + return; + + led_cdev->trigger_data = oneshot_data; + + rc = device_create_file(led_cdev->dev, &dev_attr_delay_on); + if (rc) + goto err_out_trig_data; + rc = device_create_file(led_cdev->dev, &dev_attr_delay_off); + if (rc) + goto err_out_delayon; + rc = device_create_file(led_cdev->dev, &dev_attr_invert); + if (rc) + goto err_out_delayoff; + rc = device_create_file(led_cdev->dev, &dev_attr_shot); + if (rc) + goto err_out_invert; + + led_cdev->blink_delay_on = DEFAULT_DELAY; + led_cdev->blink_delay_off = DEFAULT_DELAY; + + led_cdev->activated = true; + + return; + +err_out_invert: + device_remove_file(led_cdev->dev, &dev_attr_invert); +err_out_delayoff: + device_remove_file(led_cdev->dev, &dev_attr_delay_off); +err_out_delayon: + device_remove_file(led_cdev->dev, &dev_attr_delay_on); +err_out_trig_data: + kfree(led_cdev->trigger_data); +} + +static void oneshot_trig_deactivate(struct led_classdev *led_cdev) +{ + struct oneshot_trig_data *oneshot_data = 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); + device_remove_file(led_cdev->dev, &dev_attr_invert); + device_remove_file(led_cdev->dev, &dev_attr_shot); + kfree(oneshot_data); + led_cdev->activated = false; + } + + /* Stop blinking */ + led_set_brightness(led_cdev, LED_OFF); +} + +static struct led_trigger oneshot_led_trigger = { + .name = "oneshot", + .activate = oneshot_trig_activate, + .deactivate = oneshot_trig_deactivate, +}; + +static int __init oneshot_trig_init(void) +{ + return led_trigger_register(&oneshot_led_trigger); +} + +static void __exit oneshot_trig_exit(void) +{ + led_trigger_unregister(&oneshot_led_trigger); +} + +module_init(oneshot_trig_init); +module_exit(oneshot_trig_exit); + +MODULE_AUTHOR("Fabio Baltieri "); +MODULE_DESCRIPTION("One-shot LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/trigger/ledtrig-timer.c b/drivers/leds/trigger/ledtrig-timer.c new file mode 100644 index 0000000..8d09327 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-timer.c @@ -0,0 +1,130 @@ +/* + * LED Kernel Timer Trigger + * + * Copyright 2005-2006 Openedhand Ltd. + * + * Author: Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include + +static ssize_t led_delay_on_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return sprintf(buf, "%lu\n", led_cdev->blink_delay_on); +} + +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); + unsigned long state; + ssize_t ret = -EINVAL; + + ret = kstrtoul(buf, 10, &state); + if (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, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return sprintf(buf, "%lu\n", led_cdev->blink_delay_off); +} + +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); + unsigned long state; + ssize_t ret = -EINVAL; + + 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); +static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store); + +static void timer_trig_activate(struct led_classdev *led_cdev) +{ + int rc; + + led_cdev->trigger_data = NULL; + + rc = device_create_file(led_cdev->dev, &dev_attr_delay_on); + if (rc) + return; + rc = device_create_file(led_cdev->dev, &dev_attr_delay_off); + if (rc) + goto err_out_delayon; + + led_blink_set(led_cdev, &led_cdev->blink_delay_on, + &led_cdev->blink_delay_off); + led_cdev->activated = true; + + return; + +err_out_delayon: + device_remove_file(led_cdev->dev, &dev_attr_delay_on); +} + +static void timer_trig_deactivate(struct led_classdev *led_cdev) +{ + 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 */ + led_set_brightness(led_cdev, LED_OFF); +} + +static struct led_trigger timer_led_trigger = { + .name = "timer", + .activate = timer_trig_activate, + .deactivate = timer_trig_deactivate, +}; + +static int __init timer_trig_init(void) +{ + return led_trigger_register(&timer_led_trigger); +} + +static void __exit timer_trig_exit(void) +{ + led_trigger_unregister(&timer_led_trigger); +} + +module_init(timer_trig_init); +module_exit(timer_trig_exit); + +MODULE_AUTHOR("Richard Purdie "); +MODULE_DESCRIPTION("Timer LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/trigger/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c new file mode 100644 index 0000000..e5abc00 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-transient.c @@ -0,0 +1,237 @@ +/* + * LED Kernel Transient Trigger + * + * Copyright (C) 2012 Shuah Khan + * + * Based on Richard Purdie's ledtrig-timer.c and Atsushi Nemoto's + * ledtrig-heartbeat.c + * Design and use-case input from Jonas Bonn and + * Neil Brown + * + * 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 +#include +#include +#include +#include +#include +#include +#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 "); +MODULE_DESCRIPTION("Transient LED trigger"); +MODULE_LICENSE("GPL"); -- cgit v0.10.2 From 6fd796279179608a4047099930a674c7379650cd Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Wed, 27 Feb 2013 19:54:10 -0800 Subject: leds: atmel-pwm: remove erroneous __exit annotation CONFIG_HOTPLUG was removed, so __devexit or __exit of remove() should not be used. Signed-off-by: Jingoo Han Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-atmel-pwm.c b/drivers/leds/leds-atmel-pwm.c index 3867735..8a39c5b 100644 --- a/drivers/leds/leds-atmel-pwm.c +++ b/drivers/leds/leds-atmel-pwm.c @@ -113,7 +113,7 @@ err: return status; } -static int __exit pwmled_remove(struct platform_device *pdev) +static int pwmled_remove(struct platform_device *pdev) { const struct gpio_led_platform_data *pdata; struct pwmled *leds; @@ -140,7 +140,7 @@ static struct platform_driver pwmled_driver = { }, /* REVISIT add suspend() and resume() methods */ .probe = pwmled_probe, - .remove = __exit_p(pwmled_remove), + .remove = pwmled_remove, }; module_platform_driver(pwmled_driver); -- cgit v0.10.2 From e9dd68cf148994bf2eb718c540a472170b1ad4d3 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Wed, 27 Feb 2013 19:55:14 -0800 Subject: leds: leds-bd2802: remove erroneous __exit annotation CONFIG_HOTPLUG was removed, so __devexit or __exit of remove() should not be used. Signed-off-by: Jingoo Han Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-bd2802.c b/drivers/leds/leds-bd2802.c index 8515170..9f9177d 100644 --- a/drivers/leds/leds-bd2802.c +++ b/drivers/leds/leds-bd2802.c @@ -732,7 +732,7 @@ failed_unregister_dev_file: return ret; } -static int __exit bd2802_remove(struct i2c_client *client) +static int bd2802_remove(struct i2c_client *client) { struct bd2802_led *led = i2c_get_clientdata(client); int i; @@ -804,7 +804,7 @@ static struct i2c_driver bd2802_i2c_driver = { .pm = BD2802_PM, }, .probe = bd2802_probe, - .remove = __exit_p(bd2802_remove), + .remove = bd2802_remove, .id_table = bd2802_id, }; -- cgit v0.10.2 From 84f6942cde279e812c59eed456d1f43a39ca6c40 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Thu, 7 Mar 2013 18:37:38 -0800 Subject: leds: leds-lt3593: set devm_gpio_request_one() flags param correctly The devm_gpio_request_one() flags parameter was set to: GPIOF_DIR_OUT | state GPIOF_DIR_OUT and GPIOF_DIR_IN are defined as below: GPIOF_DIR_OUT (0 << 0) GPIOF_DIR_IN (1 << 0) So, when 'state' is 1, the gpio pin can be set as input, instead of output. To prevent this problem, GPIOF_OUT_INIT flags should be used when using devm_gpio_request_one(). Signed-off-by: Jingoo Han Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lt3593.c b/drivers/leds/leds-lt3593.c index c9b9e1f..ca48a7d5 100644 --- a/drivers/leds/leds-lt3593.c +++ b/drivers/leds/leds-lt3593.c @@ -106,8 +106,9 @@ static int create_lt3593_led(const struct gpio_led *template, if (!template->retain_state_suspended) led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; - ret = devm_gpio_request_one(parent, template->gpio, - GPIOF_DIR_OUT | state, template->name); + ret = devm_gpio_request_one(parent, template->gpio, state ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, + template->name); if (ret < 0) return ret; -- cgit v0.10.2 From 9d04cbaadf563db3ba04426c317d3e84bc27bac1 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Thu, 7 Mar 2013 18:38:26 -0800 Subject: leds: leds-ns2: set devm_gpio_request_one() flags param correctly The devm_gpio_request_one() flags parameter was set to: GPIOF_DIR_OUT | gpio_get_value(template->cmd) GPIOF_DIR_OUT and GPIOF_DIR_IN are defined as below: GPIOF_DIR_OUT (0 << 0) GPIOF_DIR_IN (1 << 0) So, when 'gpio_get_value(template->cmd)' is 1, the gpio pin can be set as input, instead of output. To prevent this problem, GPIOF_OUT_INIT flags should be used when using devm_gpio_request_one(). Same goes for 'gpio_get_value(template->slow)' case. Signed-off-by: Jingoo Han Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-ns2.c b/drivers/leds/leds-ns2.c index d978171..e02b313 100644 --- a/drivers/leds/leds-ns2.c +++ b/drivers/leds/leds-ns2.c @@ -193,7 +193,8 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, enum ns2_led_modes mode; ret = devm_gpio_request_one(&pdev->dev, template->cmd, - GPIOF_DIR_OUT | gpio_get_value(template->cmd), + gpio_get_value(template->cmd) ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, template->name); if (ret) { dev_err(&pdev->dev, "%s: failed to setup command GPIO\n", @@ -202,7 +203,8 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, } ret = devm_gpio_request_one(&pdev->dev, template->slow, - GPIOF_DIR_OUT | gpio_get_value(template->slow), + gpio_get_value(template->slow) ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, template->name); if (ret) { dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n", -- cgit v0.10.2 From 901b74a3c4044a07c7149365aa9c4eaa6c71964b Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Thu, 7 Mar 2013 18:39:04 -0800 Subject: leds: renesas: set gpio_request_one() flags param correctly The gpio_request_one() flags parameter was set to: GPIOF_DIR_OUT | !!brightness GPIOF_DIR_OUT and GPIOF_DIR_IN are defined as below: GPIOF_DIR_OUT (0 << 0) GPIOF_DIR_IN (1 << 0) So, when '!!brightness' is 1, the gpio pin can be set as input, instead of output. To prevent this problem, GPIOF_OUT_INIT flags should be used when using gpio_request_one(). Signed-off-by: Jingoo Han Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-renesas-tpu.c b/drivers/leds/leds-renesas-tpu.c index d3c2b7e..9483f1c 100644 --- a/drivers/leds/leds-renesas-tpu.c +++ b/drivers/leds/leds-renesas-tpu.c @@ -205,7 +205,8 @@ static void r_tpu_set_pin(struct r_tpu_priv *p, enum r_tpu_pin new_state, gpio_free(cfg->pin_gpio_fn); if (new_state == R_TPU_PIN_GPIO) - gpio_request_one(cfg->pin_gpio, GPIOF_DIR_OUT | !!brightness, + gpio_request_one(cfg->pin_gpio, !!brightness ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, cfg->name); if (new_state == R_TPU_PIN_GPIO_FN) -- cgit v0.10.2 From d67eb8e66c53dd2d26d294b49969f0c3101a5368 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 5 Mar 2013 21:01:37 -0800 Subject: leds: wm8350: Complain if we fail to reenable DCDC Provide some trace, though the hardware is most likely non-functional if this happens. Signed-off-by: Mark Brown Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c index ed15157..8a181d5 100644 --- a/drivers/leds/leds-wm8350.c +++ b/drivers/leds/leds-wm8350.c @@ -129,7 +129,10 @@ static void wm8350_led_disable(struct wm8350_led *led) ret = regulator_disable(led->isink); if (ret != 0) { dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret); - regulator_enable(led->dcdc); + ret = regulator_enable(led->dcdc); + if (ret != 0) + dev_err(led->cdev.dev, "Failed to reenable DCDC: %d\n", + ret); return; } -- cgit v0.10.2 From fbd9df28faeda17b1a9d3e9ab976e969be98d379 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Thu, 14 Mar 2013 03:09:49 -0700 Subject: leds: tca6507: Use of_match_ptr() macro This eliminates having an #ifdef returning NULL for the case when OF is disabled. Signed-off-by: Sachin Kamat Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c index 070ba07..98fe021 100644 --- a/drivers/leds/leds-tca6507.c +++ b/drivers/leds/leds-tca6507.c @@ -85,6 +85,7 @@ #include #include #include +#include /* LED select registers determine the source that drives LED outputs */ #define TCA6507_LS_LED_OFF 0x0 /* Output HI-Z (off) */ @@ -724,7 +725,6 @@ tca6507_led_dt_init(struct i2c_client *client) return ERR_PTR(-ENODEV); } -#define of_tca6507_leds_match NULL #endif static int tca6507_probe(struct i2c_client *client, @@ -813,7 +813,7 @@ static struct i2c_driver tca6507_driver = { .driver = { .name = "leds-tca6507", .owner = THIS_MODULE, - .of_match_table = of_tca6507_leds_match, + .of_match_table = of_match_ptr(of_tca6507_leds_match), }, .probe = tca6507_probe, .remove = tca6507_remove, -- cgit v0.10.2 From 39f7e08af3fd9ca1cb94a8270354afb2ea5cfcd3 Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Thu, 14 Mar 2013 04:29:19 -0700 Subject: leds: trigger: use inline functions instead of macros Macros are used in case that an inline function doesn't work. Otherwise, use an empty inline function. (a) Case of !CONFIG_LEDS_TRIGGERS Following macros are replaced with inline functions. led_trigger_register_simple() led_trigger_unregister_simple() led_trigger_event() To make inline types, the structure, 'led_trigger' should be defined. This structure has no member at all. (b) Case of !CONFIG_LEDS_TRIGGER_IDE_DISK ledtrig_ide_activity() macro is replaced with an inline function as well. (c) DEFINE_LED_TRIGGER() and DEFINE_LED_TRIGGER_GLOBAL() Struct 'led_trigger' is defined both cases, with CONFIG_LEDS_TRIGGERS and without CONFIG_LEDS_TRIGGERS. Those macros are moved out of CONFIG_LED_TRIGGERS because of no-dependency on CONFIG_LEDS_TRIGGERS. (d) Fix build errors in mmc-core driver After replacing macros with inline functions, following build errors occur. (condition: CONFIG_LEDS_TRIGGERS is not set) drivers/mmc/core/core.c: In function 'mmc_request_done': drivers/mmc/core/core.c:164:25: error: 'struct mmc_host' has no member named 'led' drivers/mmc/core/core.c: In function 'mmc_start_request': drivers/mmc/core/core.c:254:24: error: 'struct mmc_host' has no member named 'led' make[3]: *** [drivers/mmc/core/core.o] Error 1 The reason of these errors is non-existent member variable, 'led'. It is only valid when CONFIG_LEDS_TRIGGERS is set. But now, it can be used without this dependency. To fix build errors, member 'led' is always used without its config option in 'include/linux/mmc/host.h'. Signed-off-by: Milo(Woogyom) Kim Signed-off-by: Bryan Wu diff --git a/include/linux/leds.h b/include/linux/leds.h index 0d9b5ee..2d8c0b4 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -142,6 +142,10 @@ extern void led_set_brightness(struct led_classdev *led_cdev, /* * LED Triggers */ +/* Registration functions for simple triggers */ +#define DEFINE_LED_TRIGGER(x) static struct led_trigger *x; +#define DEFINE_LED_TRIGGER_GLOBAL(x) struct led_trigger *x; + #ifdef CONFIG_LEDS_TRIGGERS #define TRIG_NAME_MAX 50 @@ -164,9 +168,6 @@ struct led_trigger { extern int led_trigger_register(struct led_trigger *trigger); extern void led_trigger_unregister(struct led_trigger *trigger); -/* Registration functions for simple triggers */ -#define DEFINE_LED_TRIGGER(x) static struct led_trigger *x; -#define DEFINE_LED_TRIGGER_GLOBAL(x) struct led_trigger *x; extern void led_trigger_register_simple(const char *name, struct led_trigger **trigger); extern void led_trigger_unregister_simple(struct led_trigger *trigger); @@ -199,20 +200,22 @@ extern void led_trigger_rename_static(const char *name, #else -/* Triggers aren't active - null macros */ -#define DEFINE_LED_TRIGGER(x) -#define DEFINE_LED_TRIGGER_GLOBAL(x) -#define led_trigger_register_simple(x, y) do {} while(0) -#define led_trigger_unregister_simple(x) do {} while(0) -#define led_trigger_event(x, y) do {} while(0) +/* Trigger has no members */ +struct led_trigger {}; -#endif +/* Trigger inline empty functions */ +static inline void led_trigger_register_simple(const char *name, + struct led_trigger **trigger) {} +static inline void led_trigger_unregister_simple(struct led_trigger *trigger) {} +static inline void led_trigger_event(struct led_trigger *trigger, + enum led_brightness event) {} +#endif /* CONFIG_LEDS_TRIGGERS */ /* Trigger specific functions */ #ifdef CONFIG_LEDS_TRIGGER_IDE_DISK extern void ledtrig_ide_activity(void); #else -#define ledtrig_ide_activity() do {} while(0) +static inline void ledtrig_ide_activity(void) {} #endif /* diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index d6f20cc..357e80e 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -341,9 +341,7 @@ struct mmc_host { mmc_pm_flag_t pm_flags; /* requested pm features */ -#ifdef CONFIG_LEDS_TRIGGERS struct led_trigger *led; /* activity led */ -#endif #ifdef CONFIG_REGULATOR bool regulator_enabled; /* regulator state */ -- cgit v0.10.2 From 48a1d032c954b9b06c3adbf35ef4735dd70ab757 Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Thu, 14 Mar 2013 04:29:24 -0700 Subject: leds: add camera LED triggers Some LED devices support flash/torch functionality through the LED subsystem. This patch enables direct LED trigger controls by the driver. Flash on/off and torch on/off can be done simply by other driver space. Two trigger APIs are added, ledtrig_flash_ctrl() and ledtrig_torch_ctrl(). Signed-off-by: Milo(Woogyom) Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index eaa286d..49794b4 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -100,4 +100,12 @@ config LEDS_TRIGGER_TRANSIENT GPIO/PWM based hardware. If unsure, say Y. +config LEDS_TRIGGER_CAMERA + tristate "LED Camera Flash/Torch Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to be controlled as a camera flash/torch device. + This enables direct flash/torch on/off by the driver, kernel space. + If unsure, say Y. + endif # LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile index 554e46e..1abf48d 100644 --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o obj-$(CONFIG_LEDS_TRIGGER_CPU) += ledtrig-cpu.o obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o +obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o diff --git a/drivers/leds/trigger/ledtrig-camera.c b/drivers/leds/trigger/ledtrig-camera.c new file mode 100644 index 0000000..9bd73a8 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-camera.c @@ -0,0 +1,57 @@ +/* + * Camera Flash and Torch On/Off Trigger + * + * based on ledtrig-ide-disk.c + * + * Copyright 2013 Texas Instruments + * + * Author: Milo(Woogyom) Kim + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include + +DEFINE_LED_TRIGGER(ledtrig_flash); +DEFINE_LED_TRIGGER(ledtrig_torch); + +void ledtrig_flash_ctrl(bool on) +{ + enum led_brightness brt = on ? LED_FULL : LED_OFF; + + led_trigger_event(ledtrig_flash, brt); +} +EXPORT_SYMBOL_GPL(ledtrig_flash_ctrl); + +void ledtrig_torch_ctrl(bool on) +{ + enum led_brightness brt = on ? LED_FULL : LED_OFF; + + led_trigger_event(ledtrig_torch, brt); +} +EXPORT_SYMBOL_GPL(ledtrig_torch_ctrl); + +static int __init ledtrig_camera_init(void) +{ + led_trigger_register_simple("flash", &ledtrig_flash); + led_trigger_register_simple("torch", &ledtrig_torch); + return 0; +} +module_init(ledtrig_camera_init); + +static void __exit ledtrig_camera_exit(void) +{ + led_trigger_unregister_simple(ledtrig_torch); + led_trigger_unregister_simple(ledtrig_flash); +} +module_exit(ledtrig_camera_exit); + +MODULE_DESCRIPTION("LED Trigger for Camera Flash/Torch Control"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/leds.h b/include/linux/leds.h index 2d8c0b4..0287ab2 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -218,6 +218,14 @@ extern void ledtrig_ide_activity(void); static inline void ledtrig_ide_activity(void) {} #endif +#if defined(CONFIG_LEDS_TRIGGER_CAMERA) || defined(CONFIG_LEDS_TRIGGER_CAMERA_MODULE) +extern void ledtrig_flash_ctrl(bool on); +extern void ledtrig_torch_ctrl(bool on); +#else +static inline void ledtrig_flash_ctrl(bool on) {} +static inline void ledtrig_torch_ctrl(bool on) {} +#endif + /* * Generic LED platform data for describing LED names and default triggers. */ -- cgit v0.10.2 From 313bf0b1a0eaeaac17ea8c4b748f16e28fce8b7a Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Thu, 14 Mar 2013 04:29:26 -0700 Subject: leds: lm355x, lm3642: support camera LED triggers for flash and torch LM355x and LM3642 support flash and torch functionality. (Camera driver) (LED trigger for camera) (LED driver) Turn on the flash ...> ledtrig_flash_ctrl(true) ...> LM355x or LM3642 brightness ctrl Flash/torch LEDs are controlled by other driver using LED camera trigger APIs, ledtrig_flash_ctrl()/ledtrig_torch_ctrl(). Then, actual device control is activated by each LED driver such like LM355x or LM3642. Signed-off-by: Milo(Woogyom) Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lm355x.c b/drivers/leds/leds-lm355x.c index 4117235..d81a8e7 100644 --- a/drivers/leds/leds-lm355x.c +++ b/drivers/leds/leds-lm355x.c @@ -477,6 +477,7 @@ static int lm355x_probe(struct i2c_client *client, chip->cdev_flash.name = "flash"; chip->cdev_flash.max_brightness = 16; chip->cdev_flash.brightness_set = lm355x_strobe_brightness_set; + chip->cdev_flash.default_trigger = "flash"; err = led_classdev_register((struct device *) &client->dev, &chip->cdev_flash); if (err < 0) @@ -486,6 +487,7 @@ static int lm355x_probe(struct i2c_client *client, chip->cdev_torch.name = "torch"; chip->cdev_torch.max_brightness = 8; chip->cdev_torch.brightness_set = lm355x_torch_brightness_set; + chip->cdev_torch.default_trigger = "torch"; err = led_classdev_register((struct device *) &client->dev, &chip->cdev_torch); if (err < 0) diff --git a/drivers/leds/leds-lm3642.c b/drivers/leds/leds-lm3642.c index 9f428d9..f361bbe 100644 --- a/drivers/leds/leds-lm3642.c +++ b/drivers/leds/leds-lm3642.c @@ -363,6 +363,7 @@ static int lm3642_probe(struct i2c_client *client, chip->cdev_flash.name = "flash"; chip->cdev_flash.max_brightness = 16; chip->cdev_flash.brightness_set = lm3642_strobe_brightness_set; + chip->cdev_flash.default_trigger = "flash"; err = led_classdev_register((struct device *) &client->dev, &chip->cdev_flash); if (err < 0) { @@ -380,6 +381,7 @@ static int lm3642_probe(struct i2c_client *client, chip->cdev_torch.name = "torch"; chip->cdev_torch.max_brightness = 8; chip->cdev_torch.brightness_set = lm3642_torch_brightness_set; + chip->cdev_torch.default_trigger = "torch"; err = led_classdev_register((struct device *) &client->dev, &chip->cdev_torch); if (err < 0) { -- cgit v0.10.2 From 24d321284745cbc593fba8115585329d48703704 Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Thu, 14 Mar 2013 17:19:36 -0700 Subject: leds: lp55xx: fix the sysfs read operation According to a sysfs documentation(Documentation/filesystem/sysfs.txt), scnprintf() should be used in a read operation method. It guarantees safe buffer size(PAGE_SIZE) which is allocated by the sysfs. Signed-off-by: Milo(Woogyom) Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index 1001347..7f10304 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -360,7 +360,8 @@ static ssize_t lp5521_selftest(struct device *dev, mutex_lock(&chip->lock); ret = lp5521_run_selftest(chip, buf); mutex_unlock(&chip->lock); - return sprintf(buf, "%s\n", ret ? "FAIL" : "OK"); + + return scnprintf(buf, PAGE_SIZE, "%s\n", ret ? "FAIL" : "OK"); } /* device attributes */ diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c index 8a388a4..715a602 100644 --- a/drivers/leds/leds-lp55xx-common.c +++ b/drivers/leds/leds-lp55xx-common.c @@ -80,7 +80,7 @@ static ssize_t lp55xx_show_current(struct device *dev, { struct lp55xx_led *led = dev_to_lp55xx_led(dev); - return sprintf(buf, "%d\n", led->led_current); + return scnprintf(buf, PAGE_SIZE, "%d\n", led->led_current); } static ssize_t lp55xx_store_current(struct device *dev, @@ -113,7 +113,7 @@ static ssize_t lp55xx_show_max_current(struct device *dev, { struct lp55xx_led *led = dev_to_lp55xx_led(dev); - return sprintf(buf, "%d\n", led->max_current); + return scnprintf(buf, PAGE_SIZE, "%d\n", led->max_current); } static DEVICE_ATTR(led_current, S_IRUGO | S_IWUSR, lp55xx_show_current, -- cgit v0.10.2 From c971ff185f6443e834686f140ba6d6e341ced600 Mon Sep 17 00:00:00 2001 From: Florian Vaussard Date: Mon, 28 Jan 2013 06:00:59 -0800 Subject: leds: leds-pwm: Defer led_pwm_set() if PWM can sleep Call to led_pwm_set() can happen inside atomic context, like triggers. If the PWM call can sleep, defer using a worker. Signed-off-by: Florian Vaussard Reviewed-by: Peter Ujfalusi Acked-by: Thierry Reding Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index a1ea5f6..faf52c0 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -23,12 +23,16 @@ #include #include #include +#include struct led_pwm_data { struct led_classdev cdev; struct pwm_device *pwm; + struct work_struct work; unsigned int active_low; unsigned int period; + int duty; + bool can_sleep; }; struct led_pwm_priv { @@ -36,6 +40,26 @@ struct led_pwm_priv { struct led_pwm_data leds[0]; }; +static void __led_pwm_set(struct led_pwm_data *led_dat) +{ + int new_duty = led_dat->duty; + + pwm_config(led_dat->pwm, new_duty, led_dat->period); + + if (new_duty == 0) + pwm_disable(led_dat->pwm); + else + pwm_enable(led_dat->pwm); +} + +static void led_pwm_work(struct work_struct *work) +{ + struct led_pwm_data *led_dat = + container_of(work, struct led_pwm_data, work); + + __led_pwm_set(led_dat); +} + static void led_pwm_set(struct led_classdev *led_cdev, enum led_brightness brightness) { @@ -44,13 +68,12 @@ static void led_pwm_set(struct led_classdev *led_cdev, unsigned int max = led_dat->cdev.max_brightness; unsigned int period = led_dat->period; - if (brightness == 0) { - pwm_config(led_dat->pwm, 0, period); - pwm_disable(led_dat->pwm); - } else { - pwm_config(led_dat->pwm, brightness * period / max, period); - pwm_enable(led_dat->pwm); - } + led_dat->duty = brightness * period / max; + + if (led_dat->can_sleep) + schedule_work(&led_dat->work); + else + __led_pwm_set(led_dat); } static inline size_t sizeof_pwm_leds_priv(int num_leds) @@ -100,6 +123,10 @@ static struct led_pwm_priv *led_pwm_create_of(struct platform_device *pdev) led_dat->cdev.brightness = LED_OFF; led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; + led_dat->can_sleep = pwm_can_sleep(led_dat->pwm); + if (led_dat->can_sleep) + INIT_WORK(&led_dat->work, led_pwm_work); + ret = led_classdev_register(&pdev->dev, &led_dat->cdev); if (ret < 0) { dev_err(&pdev->dev, "failed to register for %s\n", @@ -153,6 +180,10 @@ static int led_pwm_probe(struct platform_device *pdev) led_dat->cdev.max_brightness = cur_led->max_brightness; led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; + led_dat->can_sleep = pwm_can_sleep(led_dat->pwm); + if (led_dat->can_sleep) + INIT_WORK(&led_dat->work, led_pwm_work); + ret = led_classdev_register(&pdev->dev, &led_dat->cdev); if (ret < 0) goto err; @@ -180,8 +211,11 @@ static int led_pwm_remove(struct platform_device *pdev) struct led_pwm_priv *priv = platform_get_drvdata(pdev); int i; - for (i = 0; i < priv->num_leds; i++) + for (i = 0; i < priv->num_leds; i++) { led_classdev_unregister(&priv->leds[i].cdev); + if (priv->leds[i].can_sleep) + cancel_work_sync(&priv->leds[i].work); + } return 0; } -- cgit v0.10.2 From 3de1929ba61df87a0561c4f1fb7161c401fe3d04 Mon Sep 17 00:00:00 2001 From: Simon Guinot Date: Tue, 19 Mar 2013 11:07:29 -0700 Subject: leds: leds-ns2: fix oops at module removal This patch fixes a regression introduced by commit 72052fcc10 ("leds: leds-ns2: add device tree binding"). When the driver is initialized with device tree data, platform_data pointer is NULL. This causes a kernel oops at removal. To fix this bug, num_leds is moved into driver_data and platform_data is not longer used from ns2_led_remove(). Signed-off-by: Simon Guinot Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-ns2.c b/drivers/leds/leds-ns2.c index e02b313..70137b1 100644 --- a/drivers/leds/leds-ns2.c +++ b/drivers/leds/leds-ns2.c @@ -308,10 +308,21 @@ static const struct of_device_id of_ns2_leds_match[] = { }; #endif /* CONFIG_OF_GPIO */ +struct ns2_led_priv { + int num_leds; + struct ns2_led_data leds_data[]; +}; + +static inline int sizeof_ns2_led_priv(int num_leds) +{ + return sizeof(struct ns2_led_priv) + + (sizeof(struct ns2_led_data) * num_leds); +} + static int ns2_led_probe(struct platform_device *pdev) { struct ns2_led_platform_data *pdata = pdev->dev.platform_data; - struct ns2_led_data *leds_data; + struct ns2_led_priv *priv; int i; int ret; @@ -332,21 +343,23 @@ static int ns2_led_probe(struct platform_device *pdev) return -EINVAL; #endif /* CONFIG_OF_GPIO */ - leds_data = devm_kzalloc(&pdev->dev, sizeof(struct ns2_led_data) * - pdata->num_leds, GFP_KERNEL); - if (!leds_data) + priv = devm_kzalloc(&pdev->dev, + sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL); + if (!priv) return -ENOMEM; + priv->num_leds = pdata->num_leds; - for (i = 0; i < pdata->num_leds; i++) { - ret = create_ns2_led(pdev, &leds_data[i], &pdata->leds[i]); + for (i = 0; i < priv->num_leds; i++) { + ret = create_ns2_led(pdev, &priv->leds_data[i], + &pdata->leds[i]); if (ret < 0) { for (i = i - 1; i >= 0; i--) - delete_ns2_led(&leds_data[i]); + delete_ns2_led(&priv->leds_data[i]); return ret; } } - platform_set_drvdata(pdev, leds_data); + platform_set_drvdata(pdev, priv); return 0; } @@ -354,13 +367,12 @@ static int ns2_led_probe(struct platform_device *pdev) static int ns2_led_remove(struct platform_device *pdev) { int i; - struct ns2_led_platform_data *pdata = pdev->dev.platform_data; - struct ns2_led_data *leds_data; + struct ns2_led_priv *priv; - leds_data = platform_get_drvdata(pdev); + priv = platform_get_drvdata(pdev); - for (i = 0; i < pdata->num_leds; i++) - delete_ns2_led(&leds_data[i]); + for (i = 0; i < priv->num_leds; i++) + delete_ns2_led(&priv->leds_data[i]); platform_set_drvdata(pdev, NULL); -- cgit v0.10.2 From 53b4192266436e75dea96c8ef495eadd6f3df981 Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Wed, 20 Mar 2013 17:37:00 -0700 Subject: leds: lp55xx: use common clock framework when external clock is used Program execution is timed with 32768Hz clock in the LP55xx family devices. To run LED functionalities, LP55xx devices provide two options. One is using internal clock. The other is using external clock. This patch enables external clock detection automatically. If external clock is not detected, then the internal clock will be used in the LP55xx driver. Valid clock rate is 32768Hz in LP55xx devices. This new API is used in each LP55xx driver like LP5521 and LP5562. Signed-off-by: Milo(Woogyom) Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c index 715a602..ba34199 100644 --- a/drivers/leds/leds-lp55xx-common.c +++ b/drivers/leds/leds-lp55xx-common.c @@ -12,6 +12,7 @@ * Derived from leds-lp5521.c, leds-lp5523.c */ +#include #include #include #include @@ -21,6 +22,9 @@ #include "leds-lp55xx-common.h" +/* External clock rate */ +#define LP55XX_CLK_32K 32768 + static struct lp55xx_led *cdev_to_lp55xx_led(struct led_classdev *cdev) { return container_of(cdev, struct lp55xx_led, cdev); @@ -357,6 +361,35 @@ int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg, u8 mask, u8 val) } EXPORT_SYMBOL_GPL(lp55xx_update_bits); +bool lp55xx_is_extclk_used(struct lp55xx_chip *chip) +{ + struct clk *clk; + int err; + + clk = devm_clk_get(&chip->cl->dev, "32k_clk"); + if (IS_ERR(clk)) + goto use_internal_clk; + + err = clk_prepare_enable(clk); + if (err) + goto use_internal_clk; + + if (clk_get_rate(clk) != LP55XX_CLK_32K) { + clk_disable_unprepare(clk); + goto use_internal_clk; + } + + dev_info(&chip->cl->dev, "%dHz external clock used\n", LP55XX_CLK_32K); + + chip->clk = clk; + return true; + +use_internal_clk: + dev_info(&chip->cl->dev, "internal clock used\n"); + return false; +} +EXPORT_SYMBOL_GPL(lp55xx_is_extclk_used); + int lp55xx_init_device(struct lp55xx_chip *chip) { struct lp55xx_platform_data *pdata; @@ -421,6 +454,9 @@ void lp55xx_deinit_device(struct lp55xx_chip *chip) { struct lp55xx_platform_data *pdata = chip->pdata; + if (chip->clk) + clk_disable_unprepare(chip->clk); + if (pdata->enable) pdata->enable(0); diff --git a/drivers/leds/leds-lp55xx-common.h b/drivers/leds/leds-lp55xx-common.h index ece4761..fa6a078 100644 --- a/drivers/leds/leds-lp55xx-common.h +++ b/drivers/leds/leds-lp55xx-common.h @@ -83,6 +83,7 @@ struct lp55xx_device_config { */ struct lp55xx_chip { struct i2c_client *cl; + struct clk *clk; struct lp55xx_platform_data *pdata; struct mutex lock; /* lock for user-space interface */ int num_leds; @@ -117,6 +118,9 @@ extern int lp55xx_read(struct lp55xx_chip *chip, u8 reg, u8 *val); extern int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg, u8 mask, u8 val); +/* external clock detection */ +extern bool lp55xx_is_extclk_used(struct lp55xx_chip *chip); + /* common device init/deinit functions */ extern int lp55xx_init_device(struct lp55xx_chip *chip); extern void lp55xx_deinit_device(struct lp55xx_chip *chip); -- cgit v0.10.2 From 81f2a5b4a0570a662efd629c176fc1d67e56f7e3 Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Wed, 20 Mar 2013 17:37:04 -0700 Subject: leds: lp55xx: configure the clock detection Now LP55xx provides automatic clock detection API, lp55xx_is_extclk_used(). The clock configuration can be done by the driver itself. (a) Concept The default value is set by each driver with clock selection. The internal clock selection bit is updated in case that the external clock is not detected or clock rate is not 32KHz. (b) Change on LP55xx platform data The clock configuration is done automatically, so no need to define 'update_config' in the platform side. Correlated information are removed in the documentations and header. (c) Definitions moved from header to driver files CONFIG register values are moved each driver, LP5521 and LP5562. Not necessary definitions are removed also. Signed-off-by: Milo(Woogyom) Kim Signed-off-by: Bryan Wu diff --git a/Documentation/leds/leds-lp5521.txt b/Documentation/leds/leds-lp5521.txt index 270f571..79e4c2e 100644 --- a/Documentation/leds/leds-lp5521.txt +++ b/Documentation/leds/leds-lp5521.txt @@ -81,22 +81,3 @@ static struct lp55xx_platform_data lp5521_platform_data = { If the current is set to 0 in the platform data, that channel is disabled and it is not visible in the sysfs. - -The 'update_config' : CONFIG register (ADDR 08h) -This value is platform-specific data. -If update_config is not defined, the CONFIG register is set with -'LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT'. -(Enable auto-powersave, set charge pump to auto, red to battery) - -example of update_config : - -#define LP5521_CONFIGS (LP5521_PWM_HF | LP5521_PWRSAVE_EN | \ - LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT | \ - LP5521_CLK_INT) - -static struct lp55xx_platform_data lp5521_pdata = { - .led_config = lp5521_led_config, - .num_channels = ARRAY_SIZE(lp5521_led_config), - .clock_mode = LP55XX_CLOCK_INT, - .update_config = LP5521_CONFIGS, -}; diff --git a/Documentation/leds/leds-lp5562.txt b/Documentation/leds/leds-lp5562.txt index 9606100..5a823ff 100644 --- a/Documentation/leds/leds-lp5562.txt +++ b/Documentation/leds/leds-lp5562.txt @@ -118,18 +118,3 @@ static struct lp55xx_platform_data lp5562_platform_data = { If the current is set to 0 in the platform data, that channel is disabled and it is not visible in the sysfs. - -The 'update_config' : CONFIG register (ADDR 08h) -This value is platform-specific data. -If update_config is not defined, the CONFIG register is set with -'LP5562_PWRSAVE_EN | LP5562_CLK_AUTO'. -(Enable auto-powersave, set automatic clock source selection) - -#define LP5562_CONFIGS (LP5562_PWM_HF | LP5562_PWRSAVE_EN | \ - LP5562_CLK_SRC_EXT) - -static struct lp55xx_platform_data lp5562_pdata = { - .led_config = lp5562_led_config, - .num_channels = ARRAY_SIZE(lp5562_led_config), - .update_config = LP5562_CONFIGS, -}; diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index 7f10304..19752c9 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -68,6 +68,18 @@ #define LP5521_ENABLE_RUN_PROGRAM \ (LP5521_ENABLE_DEFAULT | LP5521_EXEC_RUN) +/* CONFIG register */ +#define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */ +#define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */ +#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */ +#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */ +#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */ +#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */ +#define LP5521_R_TO_BATT 0x04 /* R out: 0 = CP, 1 = Vbat */ +#define LP5521_CLK_INT 0x01 /* Internal clock */ +#define LP5521_DEFAULT_CFG \ + (LP5521_PWM_HF | LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO) + /* Status */ #define LP5521_EXT_CLK_USED 0x08 @@ -296,8 +308,11 @@ static int lp5521_post_init_device(struct lp55xx_chip *chip) /* Set all PWMs to direct control mode */ ret = lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); - val = chip->pdata->update_config ? - : (LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT); + /* Update configuration for the clock setting */ + val = LP5521_DEFAULT_CFG; + if (!lp55xx_is_extclk_used(chip)) + val |= LP5521_CLK_INT; + ret = lp55xx_write(chip, LP5521_REG_CONFIG, val); if (ret) return ret; diff --git a/drivers/leds/leds-lp5562.c b/drivers/leds/leds-lp5562.c index f8b9277..513f239 100644 --- a/drivers/leds/leds-lp5562.c +++ b/drivers/leds/leds-lp5562.c @@ -71,8 +71,10 @@ /* CONFIG Register 08h */ #define LP5562_REG_CONFIG 0x08 -#define LP5562_DEFAULT_CFG \ - (LP5562_PWM_HF | LP5562_PWRSAVE_EN | LP5562_CLK_INT) +#define LP5562_PWM_HF 0x40 +#define LP5562_PWRSAVE_EN 0x20 +#define LP5562_CLK_INT 0x01 /* Internal clock */ +#define LP5562_DEFAULT_CFG (LP5562_PWM_HF | LP5562_PWRSAVE_EN) /* RESET Register 0Dh */ #define LP5562_REG_RESET 0x0D @@ -280,7 +282,7 @@ static void lp5562_firmware_loaded(struct lp55xx_chip *chip) static int lp5562_post_init_device(struct lp55xx_chip *chip) { int ret; - u8 update_cfg = chip->pdata->update_config ? : LP5562_DEFAULT_CFG; + u8 cfg = LP5562_DEFAULT_CFG; /* Set all PWMs to direct control mode */ ret = lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DIRECT); @@ -289,7 +291,11 @@ static int lp5562_post_init_device(struct lp55xx_chip *chip) lp5562_wait_opmode_done(); - ret = lp55xx_write(chip, LP5562_REG_CONFIG, update_cfg); + /* Update configuration for the clock setting */ + if (!lp55xx_is_extclk_used(chip)) + cfg |= LP5562_CLK_INT; + + ret = lp55xx_write(chip, LP5562_REG_CONFIG, cfg); if (ret) return ret; diff --git a/include/linux/platform_data/leds-lp55xx.h b/include/linux/platform_data/leds-lp55xx.h index 1f1041e..202e290 100644 --- a/include/linux/platform_data/leds-lp55xx.h +++ b/include/linux/platform_data/leds-lp55xx.h @@ -20,25 +20,6 @@ #define LP55XX_CLOCK_INT 1 #define LP55XX_CLOCK_EXT 2 -/* Bits in LP5521 CONFIG register. 'update_config' in lp55xx_platform_data */ -#define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */ -#define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */ -#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */ -#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */ -#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */ -#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */ -#define LP5521_R_TO_BATT 4 /* R out: 0 = CP, 1 = Vbat */ -#define LP5521_CLK_SRC_EXT 0 /* Ext-clk source (CLK_32K) */ -#define LP5521_CLK_INT 1 /* Internal clock */ -#define LP5521_CLK_AUTO 2 /* Automatic clock selection */ - -/* Bits in LP5562 CONFIG register */ -#define LP5562_PWM_HF LP5521_PWM_HF -#define LP5562_PWRSAVE_EN LP5521_PWRSAVE_EN -#define LP5562_CLK_SRC_EXT LP5521_CLK_SRC_EXT -#define LP5562_CLK_INT LP5521_CLK_INT -#define LP5562_CLK_AUTO LP5521_CLK_AUTO - struct lp55xx_led_config { const char *name; u8 chan_nr; @@ -86,9 +67,6 @@ struct lp55xx_platform_data { /* Predefined pattern data */ struct lp55xx_predef_pattern *patterns; unsigned int num_patterns; - - /* _CONFIG register */ - u8 update_config; }; #endif /* _LEDS_LP55XX_H */ -- cgit v0.10.2 From cceba0e4b8f41a495dc7a5ab2795dcab0ddb64c1 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Thu, 21 Mar 2013 19:09:01 -0700 Subject: leds: leds-bd2802: add CONFIG_PM_SLEEP to suspend/resume functions Add CONFIG_PM_SLEEP to suspend/resume functions to fix the following build warning when CONFIG_PM_SLEEP is not selected. This is because sleep PM callbacks defined by SIMPLE_DEV_PM_OPS are only used when the CONFIG_PM_SLEEP is enabled. drivers/leds/leds-bd2802.c:766:12: warning: 'bd2802_suspend' defined but not used [-Wunused-function] drivers/leds/leds-bd2802.c:776:12: warning: 'bd2802_resume' defined but not used [-Wunused-function] Signed-off-by: Jingoo Han Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-bd2802.c b/drivers/leds/leds-bd2802.c index 9f9177d..2db0423 100644 --- a/drivers/leds/leds-bd2802.c +++ b/drivers/leds/leds-bd2802.c @@ -747,8 +747,7 @@ static int bd2802_remove(struct i2c_client *client) return 0; } -#ifdef CONFIG_PM - +#ifdef CONFIG_PM_SLEEP static void bd2802_restore_state(struct bd2802_led *led) { int i; @@ -785,12 +784,9 @@ static int bd2802_resume(struct device *dev) return 0; } +#endif static SIMPLE_DEV_PM_OPS(bd2802_pm, bd2802_suspend, bd2802_resume); -#define BD2802_PM (&bd2802_pm) -#else /* CONFIG_PM */ -#define BD2802_PM NULL -#endif static const struct i2c_device_id bd2802_id[] = { { "BD2802", 0 }, @@ -801,7 +797,7 @@ MODULE_DEVICE_TABLE(i2c, bd2802_id); static struct i2c_driver bd2802_i2c_driver = { .driver = { .name = "BD2802", - .pm = BD2802_PM, + .pm = &bd2802_pm, }, .probe = bd2802_probe, .remove = bd2802_remove, -- cgit v0.10.2 From df92d5ff5e70999274f53884cc2c40ae620a109a Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Mon, 25 Mar 2013 23:47:19 -0700 Subject: leds: leds-asic3: switch to using SIMPLE_DEV_PM_OPS Switch to using SIMPLE_DEV_PM_OPS macro to declare the driver's pm_ops. It reduces code size. Also, CONFIG_PM_SLEEP is added to suspend/ resume functions to prevent build warnings when CONFIG_PM_SLEEP is not selected. Signed-off-by: Jingoo Han Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-asic3.c b/drivers/leds/leds-asic3.c index b474745..cf9efe4 100644 --- a/drivers/leds/leds-asic3.c +++ b/drivers/leds/leds-asic3.c @@ -134,6 +134,7 @@ static int asic3_led_remove(struct platform_device *pdev) return mfd_cell_disable(pdev); } +#ifdef CONFIG_PM_SLEEP static int asic3_led_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); @@ -159,11 +160,9 @@ static int asic3_led_resume(struct device *dev) return ret; } +#endif -static const struct dev_pm_ops asic3_led_pm_ops = { - .suspend = asic3_led_suspend, - .resume = asic3_led_resume, -}; +static SIMPLE_DEV_PM_OPS(asic3_led_pm_ops, asic3_led_suspend, asic3_led_resume); static struct platform_driver asic3_led_driver = { .probe = asic3_led_probe, -- cgit v0.10.2