From 5e417281cde2ef56e9eb1a95d080d6254402e794 Mon Sep 17 00:00:00 2001 From: Fabio Baltieri Date: Thu, 7 Jun 2012 06:11:05 +0800 Subject: leds: add oneshot trigger Add oneshot trigger to blink a led with configurale parameters via sysfs. Signed-off-by: Fabio Baltieri Cc: Shuah Khan Signed-off-by: Bryan Wu --- Documentation/leds/ledtrig-oneshot.txt | 59 ++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 Documentation/leds/ledtrig-oneshot.txt (limited to 'Documentation') diff --git a/Documentation/leds/ledtrig-oneshot.txt b/Documentation/leds/ledtrig-oneshot.txt new file mode 100644 index 000000000000..07cd1fa41a3a --- /dev/null +++ b/Documentation/leds/ledtrig-oneshot.txt @@ -0,0 +1,59 @@ +One-shot LED Trigger +==================== + +This is a LED trigger useful for signaling the user of an event where there are +no clear trap points to put standard led-on and led-off settings. Using this +trigger, the application needs only to signal the trigger when an event has +happened, than the trigger turns the LED on and than keeps it off for a +specified amount of time. + +This trigger is meant to be usable both for sporadic and dense events. In the +first case, the trigger produces a clear single controlled blink for each +event, while in the latter it keeps blinking at constant rate, as to signal +that the events are arriving continuously. + +A one-shot LED only stays in a constant state when there are no events. An +additional "invert" property specifies if the LED has to stay off (normal) or +on (inverted) when not rearmed. + +The trigger can be activated from user space on led class devices as shown +below: + + echo oneshot > trigger + +This adds the following sysfs attributes to the LED: + + delay_on - specifies for how many milliseconds the LED has to stay at + LED_FULL brightness after it has been armed. + Default to 100 ms. + + delay_off - specifies for how many milliseconds the LED has to stay at + LED_OFF brightness after it has been armed. + Default to 100 ms. + + invert - reverse the blink logic. If set to 0 (default) blink on for delay_on + ms, then blink off for delay_off ms, leaving the LED normally off. If + set to 1, blink off for delay_off ms, then blink on for delay_on ms, + leaving the LED normally on. + Setting this value also immediately change the LED state. + + shot - write any non-empty string to signal an events, this starts a blink + sequence if not already running. + +Example use-case: network devices, initialization: + + echo oneshot > trigger # set trigger for this led + echo 33 > delay_on # blink at 1 / (33 + 33) Hz on continuous traffic + echo 33 > delay_off + +interface goes up: + + echo 1 > invert # set led as normally-on, turn the led on + +packet received/transmitted: + + echo 1 > shot # led starts blinking, ignored if already blinking + +interface goes down + + echo 0 > invert # set led as normally-off, turn the led off -- cgit v1.2.3 From 32abb4788d3fff69fa242c7850e39ec1418df4f4 Mon Sep 17 00:00:00 2001 From: "G.Shark Jeong" Date: Fri, 22 Jun 2012 08:12:06 +0800 Subject: leds: Add LED driver for lm3556 chip LM3556 : The LM3556 is a 4 MHz fixed-frequency synchronous boost converter plus 1.5A constant current driver for a high-current white LED. Datasheet: www.national.com/ds/LM/LM3556.pdf Tested on OMAP4430 (bryan.wu@canonical.com: use module_i2c_driver() rather than lm3556_init/lm3556_exit for code simplicity; fixed some typo pointed out by Rob Landley) Signed-off-by: G.Shark Jeong Reviewed-by: Axel Lin Reviewed-by: Kim, Milo Acked-by: Rob Landley Signed-off-by: Bryan Wu --- Documentation/leds/00-INDEX | 2 + Documentation/leds/leds-lm3556.txt | 85 +++++ drivers/leds/Kconfig | 8 + drivers/leds/Makefile | 1 + drivers/leds/leds-lm3556.c | 512 ++++++++++++++++++++++++++++++ include/linux/platform_data/leds-lm3556.h | 50 +++ 6 files changed, 658 insertions(+) create mode 100644 Documentation/leds/leds-lm3556.txt create mode 100644 drivers/leds/leds-lm3556.c create mode 100644 include/linux/platform_data/leds-lm3556.h (limited to 'Documentation') diff --git a/Documentation/leds/00-INDEX b/Documentation/leds/00-INDEX index 29f481df32c7..5fefe374892f 100644 --- a/Documentation/leds/00-INDEX +++ b/Documentation/leds/00-INDEX @@ -6,3 +6,5 @@ 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-lm3556.txt + - notes on how to use the leds-lm3556 driver. diff --git a/Documentation/leds/leds-lm3556.txt b/Documentation/leds/leds-lm3556.txt new file mode 100644 index 000000000000..d9eb91b51913 --- /dev/null +++ b/Documentation/leds/leds-lm3556.txt @@ -0,0 +1,85 @@ +Kernel driver for lm3556 +======================== + +*Texas Instrument: + 1.5 A Synchronous Boost LED Flash Driver w/ High-Side Current Source +* Datasheet: http://www.national.com/ds/LM/LM3556.pdf + +Authors: + Daniel Jeong + Contact:Daniel Jeong(daniel.jeong-at-ti.com, gshark.jeong-at-gmail.com) + +Description +----------- +There are 3 functions in LM3556, Flash, Torch and Indicator. + +FLASH MODE +In Flash Mode, the LED current source(LED) provides 16 target current levels +from 93.75 mA to 1500 mA.The Flash currents are adjusted via the CURRENT +CONTROL REGISTER(0x09).Flash mode is activated by the ENABLE REGISTER(0x0A), +or by pulling the STROBE pin HIGH. +LM3556 Flash can be controlled through sys/class/leds/flash/brightness file +* if STROBE pin is enabled, below example control brightness only, and +ON / OFF will be controlled by STROBE pin. + +Flash Example: +OFF : #echo 0 > sys/class/leds/flash/brightness +93.75 mA: #echo 1 > sys/class/leds/flash/brightness +... ..... +1500 mA: #echo 16 > sys/class/leds/flash/brightness + +TORCH MODE +In Torch Mode, the current source(LED) is programmed via the CURRENT CONTROL +REGISTER(0x09).Torch Mode is activated by the ENABLE REGISTER(0x0A) or by the +hardware TORCH input. +LM3556 torch can be controlled through sys/class/leds/torch/brightness file. +* if TORCH pin is enabled, below example control brightness only, +and ON / OFF will be controlled by TORCH pin. + +Torch Example: +OFF : #echo 0 > sys/class/leds/torch/brightness +46.88 mA: #echo 1 > sys/class/leds/torch/brightness +... ..... +375 mA : #echo 8 > sys/class/leds/torch/brightness + +INDICATOR MODE +Indicator pattern can be set through sys/class/leds/indicator/pattern file, +and 4 patterns are pre-defined in indicator_pattern array. +According to N-lank, Pulse time and N Period values, different pattern wiill +be generated.If you want new patterns for your own device, change +indicator_pattern array with your own values and INDIC_PATTERN_SIZE. +Please refer datasheet for more detail about N-Blank, Pulse time and N Period. + +Indicator pattern example: +pattern 0: #echo 0 > sys/class/leds/indicator/pattern +.... +pattern 3: #echo 3 > sys/class/leds/indicator/pattern + +Indicator brightness can be controlled through +sys/class/leds/indicator/brightness file. + +Example: +OFF : #echo 0 > sys/class/leds/indicator/brightness +5.86 mA : #echo 1 > sys/class/leds/indicator/brightness +........ +46.875mA : #echo 8 > sys/class/leds/indicator/brightness + +Notes +----- +Driver expects it is registered using the i2c_board_info mechanism. +To register the chip at address 0x63 on specific adapter, set the platform data +according to include/linux/platform_data/leds-lm3556.h, set the i2c board info + +Example: + static struct i2c_board_info __initdata board_i2c_ch4[] = { + { + I2C_BOARD_INFO(LM3556_NAME, 0x63), + .platform_data = &lm3556_pdata, + }, + }; + +and register it in the platform init function + +Example: + board_register_i2c_bus(4, 400, + board_i2c_ch4, ARRAY_SIZE(board_i2c_ch4)); diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 52f1d456f3d2..f028f0348e83 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -415,6 +415,14 @@ config LEDS_MAX8997 This option enables support for on-chip LED drivers on MAXIM MAX8997 PMIC. +config LEDS_LM3556 + tristate "LED support for LM3556 Chip" + depends on LEDS_CLASS && I2C + select REGMAP_I2C + help + This option enables support for LEDs connected to LM3556. + LM3556 includes Torch, Flash and Indicator functions. + config LEDS_OT200 tristate "LED support for the Bachmann OT200" depends on LEDS_CLASS && HAS_IOMEM diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 9112d518f9d4..5eebd7bce4be 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o obj-$(CONFIG_LEDS_RENESAS_TPU) += leds-renesas-tpu.o obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o +obj-$(CONFIG_LEDS_LM3556) += leds-lm3556.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o diff --git a/drivers/leds/leds-lm3556.c b/drivers/leds/leds-lm3556.c new file mode 100644 index 000000000000..3062abd9a532 --- /dev/null +++ b/drivers/leds/leds-lm3556.c @@ -0,0 +1,512 @@ +/* + * Simple driver for Texas Instruments LM3556 LED Flash driver chip (Rev0x03) + * Copyright (C) 2012 Texas Instruments + * + * 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. + * + * Please refer Documentation/leds/leds-lm3556.txt file. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REG_FILT_TIME (0x0) +#define REG_IVFM_MODE (0x1) +#define REG_NTC (0x2) +#define REG_INDIC_TIME (0x3) +#define REG_INDIC_BLINK (0x4) +#define REG_INDIC_PERIOD (0x5) +#define REG_TORCH_TIME (0x6) +#define REG_CONF (0x7) +#define REG_FLASH (0x8) +#define REG_I_CTRL (0x9) +#define REG_ENABLE (0xA) +#define REG_FLAG (0xB) +#define REG_MAX (0xB) + +#define IVFM_FILTER_TIME_SHIFT (3) +#define UVLO_EN_SHIFT (7) +#define HYSTERSIS_SHIFT (5) +#define IVM_D_TH_SHIFT (2) +#define IVFM_ADJ_MODE_SHIFT (0) +#define NTC_EVENT_LVL_SHIFT (5) +#define NTC_TRIP_TH_SHIFT (2) +#define NTC_BIAS_I_LVL_SHIFT (0) +#define INDIC_RAMP_UP_TIME_SHIFT (3) +#define INDIC_RAMP_DN_TIME_SHIFT (0) +#define INDIC_N_BLANK_SHIFT (4) +#define INDIC_PULSE_TIME_SHIFT (0) +#define INDIC_N_PERIOD_SHIFT (0) +#define TORCH_RAMP_UP_TIME_SHIFT (3) +#define TORCH_RAMP_DN_TIME_SHIFT (0) +#define STROBE_USUAGE_SHIFT (7) +#define STROBE_PIN_POLARITY_SHIFT (6) +#define TORCH_PIN_POLARITY_SHIFT (5) +#define TX_PIN_POLARITY_SHIFT (4) +#define TX_EVENT_LVL_SHIFT (3) +#define IVFM_EN_SHIFT (2) +#define NTC_MODE_SHIFT (1) +#define INDIC_MODE_SHIFT (0) +#define INDUCTOR_I_LIMIT_SHIFT (6) +#define FLASH_RAMP_TIME_SHIFT (3) +#define FLASH_TOUT_TIME_SHIFT (0) +#define TORCH_I_SHIFT (4) +#define FLASH_I_SHIFT (0) +#define NTC_EN_SHIFT (7) +#define TX_PIN_EN_SHIFT (6) +#define STROBE_PIN_EN_SHIFT (5) +#define TORCH_PIN_EN_SHIFT (4) +#define PRECHG_MODE_EN_SHIFT (3) +#define PASS_MODE_ONLY_EN_SHIFT (2) +#define MODE_BITS_SHIFT (0) + +#define IVFM_FILTER_TIME_MASK (0x3) +#define UVLO_EN_MASK (0x1) +#define HYSTERSIS_MASK (0x3) +#define IVM_D_TH_MASK (0x7) +#define IVFM_ADJ_MODE_MASK (0x3) +#define NTC_EVENT_LVL_MASK (0x1) +#define NTC_TRIP_TH_MASK (0x7) +#define NTC_BIAS_I_LVL_MASK (0x3) +#define INDIC_RAMP_UP_TIME_MASK (0x7) +#define INDIC_RAMP_DN_TIME_MASK (0x7) +#define INDIC_N_BLANK_MASK (0x7) +#define INDIC_PULSE_TIME_MASK (0x7) +#define INDIC_N_PERIOD_MASK (0x7) +#define TORCH_RAMP_UP_TIME_MASK (0x7) +#define TORCH_RAMP_DN_TIME_MASK (0x7) +#define STROBE_USUAGE_MASK (0x1) +#define STROBE_PIN_POLARITY_MASK (0x1) +#define TORCH_PIN_POLARITY_MASK (0x1) +#define TX_PIN_POLARITY_MASK (0x1) +#define TX_EVENT_LVL_MASK (0x1) +#define IVFM_EN_MASK (0x1) +#define NTC_MODE_MASK (0x1) +#define INDIC_MODE_MASK (0x1) +#define INDUCTOR_I_LIMIT_MASK (0x3) +#define FLASH_RAMP_TIME_MASK (0x7) +#define FLASH_TOUT_TIME_MASK (0x7) +#define TORCH_I_MASK (0x7) +#define FLASH_I_MASK (0xF) +#define NTC_EN_MASK (0x1) +#define TX_PIN_EN_MASK (0x1) +#define STROBE_PIN_EN_MASK (0x1) +#define TORCH_PIN_EN_MASK (0x1) +#define PRECHG_MODE_EN_MASK (0x1) +#define PASS_MODE_ONLY_EN_MASK (0x1) +#define MODE_BITS_MASK (0x13) +#define EX_PIN_CONTROL_MASK (0xF1) +#define EX_PIN_ENABLE_MASK (0x70) + +enum lm3556_indic_pulse_time { + PULSE_TIME_0_MS = 0, + PULSE_TIME_32_MS, + PULSE_TIME_64_MS, + PULSE_TIME_92_MS, + PULSE_TIME_128_MS, + PULSE_TIME_160_MS, + PULSE_TIME_196_MS, + PULSE_TIME_224_MS, + PULSE_TIME_256_MS, + PULSE_TIME_288_MS, + PULSE_TIME_320_MS, + PULSE_TIME_352_MS, + PULSE_TIME_384_MS, + PULSE_TIME_416_MS, + PULSE_TIME_448_MS, + PULSE_TIME_480_MS, +}; + +enum lm3556_indic_n_blank { + INDIC_N_BLANK_0 = 0, + INDIC_N_BLANK_1, + INDIC_N_BLANK_2, + INDIC_N_BLANK_3, + INDIC_N_BLANK_4, + INDIC_N_BLANK_5, + INDIC_N_BLANK_6, + INDIC_N_BLANK_7, + INDIC_N_BLANK_8, + INDIC_N_BLANK_9, + INDIC_N_BLANK_10, + INDIC_N_BLANK_11, + INDIC_N_BLANK_12, + INDIC_N_BLANK_13, + INDIC_N_BLANK_14, + INDIC_N_BLANK_15, +}; + +enum lm3556_indic_period { + INDIC_PERIOD_0 = 0, + INDIC_PERIOD_1, + INDIC_PERIOD_2, + INDIC_PERIOD_3, + INDIC_PERIOD_4, + INDIC_PERIOD_5, + INDIC_PERIOD_6, + INDIC_PERIOD_7, +}; + +enum lm3556_mode { + MODES_STASNDBY = 0, + MODES_INDIC, + MODES_TORCH, + MODES_FLASH +}; + +#define INDIC_PATTERN_SIZE 4 + +struct indicator { + u8 blinking; + u8 period_cnt; +}; + +struct lm3556_chip_data { + struct device *dev; + + struct led_classdev cdev_flash; + struct led_classdev cdev_torch; + struct led_classdev cdev_indicator; + + struct lm3556_platform_data *pdata; + struct regmap *regmap; + struct mutex lock; + + unsigned int last_flag; +}; + +/* indicator pattern */ +static struct indicator indicator_pattern[INDIC_PATTERN_SIZE] = { + [0] = {(INDIC_N_BLANK_1 << INDIC_N_BLANK_SHIFT) + | PULSE_TIME_32_MS, INDIC_PERIOD_1}, + [1] = {(INDIC_N_BLANK_15 << INDIC_N_BLANK_SHIFT) + | PULSE_TIME_32_MS, INDIC_PERIOD_2}, + [2] = {(INDIC_N_BLANK_10 << INDIC_N_BLANK_SHIFT) + | PULSE_TIME_32_MS, INDIC_PERIOD_4}, + [3] = {(INDIC_N_BLANK_5 << INDIC_N_BLANK_SHIFT) + | PULSE_TIME_32_MS, INDIC_PERIOD_7}, +}; + +/* chip initialize */ +static int __devinit lm3556_chip_init(struct lm3556_chip_data *chip) +{ + unsigned int reg_val; + int ret; + struct lm3556_platform_data *pdata = chip->pdata; + + /* set config register */ + ret = regmap_read(chip->regmap, REG_CONF, ®_val); + if (ret < 0) { + dev_err(chip->dev, "Failed to read REG_CONF Register\n"); + goto out; + } + + reg_val &= (~EX_PIN_CONTROL_MASK); + reg_val |= ((pdata->torch_pin_polarity & 0x01) + << TORCH_PIN_POLARITY_SHIFT); + reg_val |= ((pdata->strobe_usuage & 0x01) << STROBE_USUAGE_SHIFT); + reg_val |= ((pdata->strobe_pin_polarity & 0x01) + << STROBE_PIN_POLARITY_SHIFT); + reg_val |= ((pdata->tx_pin_polarity & 0x01) << TX_PIN_POLARITY_SHIFT); + reg_val |= ((pdata->indicator_mode & 0x01) << INDIC_MODE_SHIFT); + + ret = regmap_write(chip->regmap, REG_CONF, reg_val); + if (ret < 0) { + dev_err(chip->dev, "Failed to write REG_CONF Regisgter\n"); + goto out; + } + + /* set enable register */ + ret = regmap_read(chip->regmap, REG_ENABLE, ®_val); + if (ret < 0) { + dev_err(chip->dev, "Failed to read REG_ENABLE Register\n"); + goto out; + } + + reg_val &= (~EX_PIN_ENABLE_MASK); + reg_val |= ((pdata->torch_pin_en & 0x01) << TORCH_PIN_EN_SHIFT); + reg_val |= ((pdata->strobe_pin_en & 0x01) << STROBE_PIN_EN_SHIFT); + reg_val |= ((pdata->tx_pin_en & 0x01) << TX_PIN_EN_SHIFT); + + ret = regmap_write(chip->regmap, REG_ENABLE, reg_val); + if (ret < 0) { + dev_err(chip->dev, "Failed to write REG_ENABLE Regisgter\n"); + goto out; + } + +out: + return ret; +} + +/* chip control */ +static int lm3556_control(struct lm3556_chip_data *chip, + u8 brightness, enum lm3556_mode opmode) +{ + int ret; + struct lm3556_platform_data *pdata = chip->pdata; + + ret = regmap_read(chip->regmap, REG_FLAG, &chip->last_flag); + if (ret < 0) { + dev_err(chip->dev, "Failed to read REG_FLAG Register\n"); + goto out; + } + + if (chip->last_flag) + dev_info(chip->dev, "Last FLAG is 0x%x\n", chip->last_flag); + + /* brightness 0 means off-state */ + if (!brightness) + opmode = MODES_STASNDBY; + + switch (opmode) { + case MODES_TORCH: + ret = regmap_update_bits(chip->regmap, REG_I_CTRL, + TORCH_I_MASK << TORCH_I_SHIFT, + (brightness - 1) << TORCH_I_SHIFT); + + if (pdata->torch_pin_en) + opmode |= (TORCH_PIN_EN_MASK << TORCH_PIN_EN_SHIFT); + break; + + case MODES_FLASH: + ret = regmap_update_bits(chip->regmap, REG_I_CTRL, + FLASH_I_MASK << FLASH_I_SHIFT, + (brightness - 1) << FLASH_I_SHIFT); + break; + + case MODES_INDIC: + ret = regmap_update_bits(chip->regmap, REG_I_CTRL, + TORCH_I_MASK << TORCH_I_SHIFT, + (brightness - 1) << TORCH_I_SHIFT); + break; + + case MODES_STASNDBY: + if (pdata->torch_pin_en) + opmode |= (TORCH_PIN_EN_MASK << TORCH_PIN_EN_SHIFT); + break; + + default: + return ret; + } + if (ret < 0) { + dev_err(chip->dev, "Failed to write REG_I_CTRL Register\n"); + goto out; + } + ret = regmap_update_bits(chip->regmap, REG_ENABLE, + MODE_BITS_MASK << MODE_BITS_SHIFT, + opmode << MODE_BITS_SHIFT); + +out: + return ret; +} + +/* torch */ +static void lm3556_torch_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct lm3556_chip_data *chip = + container_of(cdev, struct lm3556_chip_data, cdev_torch); + + mutex_lock(&chip->lock); + lm3556_control(chip, brightness, MODES_TORCH); + mutex_unlock(&chip->lock); +} + +/* flash */ +static void lm3556_strobe_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct lm3556_chip_data *chip = + container_of(cdev, struct lm3556_chip_data, cdev_flash); + + mutex_lock(&chip->lock); + lm3556_control(chip, brightness, MODES_FLASH); + mutex_unlock(&chip->lock); +} + +/* indicator */ +static void lm3556_indicator_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct lm3556_chip_data *chip = + container_of(cdev, struct lm3556_chip_data, cdev_indicator); + + mutex_lock(&chip->lock); + lm3556_control(chip, brightness, MODES_INDIC); + mutex_unlock(&chip->lock); +} + +/* indicator pattern */ +static ssize_t lm3556_indicator_pattern_store(struct device *dev, + struct device_attribute *devAttr, + const char *buf, size_t size) +{ + ssize_t ret; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3556_chip_data *chip = + container_of(led_cdev, struct lm3556_chip_data, cdev_indicator); + unsigned int state; + + ret = kstrtouint(buf, 10, &state); + if (ret) + goto out; + if (state > INDIC_PATTERN_SIZE - 1) + state = INDIC_PATTERN_SIZE - 1; + + ret = regmap_write(chip->regmap, REG_INDIC_BLINK, + indicator_pattern[state].blinking); + if (ret < 0) { + dev_err(chip->dev, "Failed to write REG_ENABLE Regisgter\n"); + goto out; + } + + ret = regmap_write(chip->regmap, REG_INDIC_PERIOD, + indicator_pattern[state].period_cnt); + if (ret < 0) { + dev_err(chip->dev, "Failed to write REG_ENABLE Regisgter\n"); + goto out; + } + + return size; +out: + dev_err(chip->dev, "Indicator pattern doesn't saved\n"); + return size; +} + +static DEVICE_ATTR(pattern, 0666, NULL, lm3556_indicator_pattern_store); + +static const struct regmap_config lm3556_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = REG_MAX, +}; + +/* module initialize */ +static int __devinit lm3556_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lm3556_platform_data *pdata = client->dev.platform_data; + struct lm3556_chip_data *chip; + + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "i2c functionality check fail.\n"); + return -EOPNOTSUPP; + } + + if (pdata == NULL) { + dev_err(&client->dev, "Needs Platform Data.\n"); + return -ENODATA; + } + + chip = + devm_kzalloc(&client->dev, sizeof(struct lm3556_chip_data), + GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &client->dev; + chip->pdata = pdata; + + chip->regmap = devm_regmap_init_i2c(client, &lm3556_regmap); + if (IS_ERR(chip->regmap)) { + err = PTR_ERR(chip->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + err); + return err; + } + + mutex_init(&chip->lock); + i2c_set_clientdata(client, chip); + + err = lm3556_chip_init(chip); + if (err < 0) + goto err_out; + + /* flash */ + chip->cdev_flash.name = "flash"; + chip->cdev_flash.max_brightness = 16; + chip->cdev_flash.brightness_set = lm3556_strobe_brightness_set; + err = led_classdev_register((struct device *) + &client->dev, &chip->cdev_flash); + if (err < 0) + goto err_out; + /* torch */ + chip->cdev_torch.name = "torch"; + chip->cdev_torch.max_brightness = 8; + chip->cdev_torch.brightness_set = lm3556_torch_brightness_set; + err = led_classdev_register((struct device *) + &client->dev, &chip->cdev_torch); + if (err < 0) + goto err_create_torch_file; + /* indicator */ + chip->cdev_indicator.name = "indicator"; + chip->cdev_indicator.max_brightness = 8; + chip->cdev_indicator.brightness_set = lm3556_indicator_brightness_set; + err = led_classdev_register((struct device *) + &client->dev, &chip->cdev_indicator); + if (err < 0) + goto err_create_indicator_file; + + err = device_create_file(chip->cdev_indicator.dev, &dev_attr_pattern); + if (err < 0) + goto err_create_pattern_file; + + dev_info(&client->dev, "LM3556 is initialized\n"); + return 0; + +err_create_pattern_file: + led_classdev_unregister(&chip->cdev_indicator); +err_create_indicator_file: + led_classdev_unregister(&chip->cdev_torch); +err_create_torch_file: + led_classdev_unregister(&chip->cdev_flash); +err_out: + return err; +} + +static int __devexit lm3556_remove(struct i2c_client *client) +{ + struct lm3556_chip_data *chip = i2c_get_clientdata(client); + + device_remove_file(chip->cdev_indicator.dev, &dev_attr_pattern); + led_classdev_unregister(&chip->cdev_indicator); + led_classdev_unregister(&chip->cdev_torch); + led_classdev_unregister(&chip->cdev_flash); + regmap_write(chip->regmap, REG_ENABLE, 0); + return 0; +} + +static const struct i2c_device_id lm3556_id[] = { + {LM3556_NAME, 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lm3556_id); + +static struct i2c_driver lm3556_i2c_driver = { + .driver = { + .name = LM3556_NAME, + .owner = THIS_MODULE, + .pm = NULL, + }, + .probe = lm3556_probe, + .remove = __devexit_p(lm3556_remove), + .id_table = lm3556_id, +}; + +module_i2c_driver(lm3556_i2c_driver); + +MODULE_DESCRIPTION("Texas Instruments Flash Lighting driver for LM3556"); +MODULE_AUTHOR("Daniel Jeong "); +MODULE_AUTHOR("G.Shark Jeong "); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/platform_data/leds-lm3556.h b/include/linux/platform_data/leds-lm3556.h new file mode 100644 index 000000000000..4b4e7d6b0527 --- /dev/null +++ b/include/linux/platform_data/leds-lm3556.h @@ -0,0 +1,50 @@ +/* + * Simple driver for Texas Instruments LM3556 LED Flash driver chip (Rev0x03) + * Copyright (C) 2012 Texas Instruments + * + * 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. + * + */ + +#ifndef __LINUX_LM3556_H +#define __LINUX_LM3556_H + +#define LM3556_NAME "leds-lm3556" + +enum lm3556_pin_polarity { + PIN_LOW_ACTIVE = 0, + PIN_HIGH_ACTIVE, +}; + +enum lm3556_pin_enable { + PIN_DISABLED = 0, + PIN_ENABLED, +}; + +enum lm3556_strobe_usuage { + STROBE_EDGE_DETECT = 0, + STROBE_LEVEL_DETECT, +}; + +enum lm3556_indic_mode { + INDIC_MODE_INTERNAL = 0, + INDIC_MODE_EXTERNAL, +}; + +struct lm3556_platform_data { + enum lm3556_pin_enable torch_pin_en; + enum lm3556_pin_polarity torch_pin_polarity; + + enum lm3556_strobe_usuage strobe_usuage; + enum lm3556_pin_enable strobe_pin_en; + enum lm3556_pin_polarity strobe_pin_polarity; + + enum lm3556_pin_enable tx_pin_en; + enum lm3556_pin_polarity tx_pin_polarity; + + enum lm3556_indic_mode indicator_mode; +}; + +#endif /* __LINUX_LM3556_H */ -- cgit v1.2.3 From b54cf35a7f656c61dd695509e8cf8cc7e1dc3e53 Mon Sep 17 00:00:00 2001 From: Jan-Simon Möller Date: Fri, 20 Jul 2012 16:49:06 +0800 Subject: LEDS: add BlinkM RGB LED driver, documentation and update MAINTAINERS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add driver for BlinkM device to drivers/leds/. Add entry to MAINTAINERS file. Add documentation in Documentation/leds/. A BlinkM is a RGB LED controlled through I2C. This driver implements an interface to the LED framework and another sysfs group to access the internal options of the BlinkM. rev6: Use module_i2c_driver(). rev5: Removed own workqueue in favor of events wq. rev4: Fixed comments by Bryan Wu. rev3: Fixed issues found by Jonathan Neuschäfer. (bryan.wu@canonical.com: remove 2 trailing whitespace) Signed-off-by: Jan-Simon Möller Signed-off-by: Bryan Wu --- Documentation/leds/leds-blinkm.txt | 80 ++++ MAINTAINERS | 5 + drivers/leds/Kconfig | 8 + drivers/leds/Makefile | 1 + drivers/leds/leds-blinkm.c | 812 +++++++++++++++++++++++++++++++++++++ 5 files changed, 906 insertions(+) create mode 100644 Documentation/leds/leds-blinkm.txt create mode 100644 drivers/leds/leds-blinkm.c (limited to 'Documentation') diff --git a/Documentation/leds/leds-blinkm.txt b/Documentation/leds/leds-blinkm.txt new file mode 100644 index 000000000000..9dd92f4cf4e1 --- /dev/null +++ b/Documentation/leds/leds-blinkm.txt @@ -0,0 +1,80 @@ +The leds-blinkm driver supports the devices of the BlinkM family. + +They are RGB-LED modules driven by a (AT)tiny microcontroller and +communicate through I2C. The default address of these modules is +0x09 but this can be changed through a command. By this you could +dasy-chain up to 127 BlinkMs on an I2C bus. + +The device accepts RGB and HSB color values through separate commands. +Also you can store blinking sequences as "scripts" in +the controller and run them. Also fading is an option. + +The interface this driver provides is 2-fold: + +a) LED class interface for use with triggers +############################################ + +The registration follows the scheme: +blinkm--- + +$ ls -h /sys/class/leds/blinkm-6-* +/sys/class/leds/blinkm-6-9-blue: +brightness device max_brightness power subsystem trigger uevent + +/sys/class/leds/blinkm-6-9-green: +brightness device max_brightness power subsystem trigger uevent + +/sys/class/leds/blinkm-6-9-red: +brightness device max_brightness power subsystem trigger uevent + +(same is /sys/bus/i2c/devices/6-0009/leds) + +We can control the colors separated into red, green and blue and +assign triggers on each color. + +E.g.: + +$ cat blinkm-6-9-blue/brightness +05 + +$ echo 200 > blinkm-6-9-blue/brightness +$ + +$ modprobe ledtrig-heartbeat +$ echo heartbeat > blinkm-6-9-green/trigger +$ + + +b) Sysfs group to control rgb, fade, hsb, scripts ... +##################################################### + +This extended interface is available as folder blinkm +in the sysfs folder of the I2C device. +E.g. below /sys/bus/i2c/devices/6-0009/blinkm + +$ ls -h /sys/bus/i2c/devices/6-0009/blinkm/ +blue green red test + +Currently supported is just setting red, green, blue +and a test sequence. + +E.g.: + +$ cat * +00 +00 +00 +#Write into test to start test sequence!# + +$ echo 1 > test +$ + +$ echo 255 > red +$ + + + +as of 6/2012 + +dl9pf gmx de + diff --git a/MAINTAINERS b/MAINTAINERS index fe643e7b9df6..4d8811af9edd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1518,6 +1518,11 @@ W: http://blackfin.uclinux.org/ S: Supported F: drivers/i2c/busses/i2c-bfin-twi.c +BLINKM RGB LED DRIVER +M: Jan-Simon Moeller +S: Maintained +F: drivers/leds/leds-blinkm.c + BLOCK LAYER M: Jens Axboe T: git git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux-block.git diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index f028f0348e83..f0aaf415316d 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -430,6 +430,14 @@ config LEDS_OT200 This option enables support for the LEDs on the Bachmann OT200. Say Y to enable LEDs on the Bachmann OT200. +config LEDS_BLINKM + tristate "LED support for the BlinkM I2C RGB LED" + depends on LEDS_CLASS + depends on I2C + help + 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 diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 5eebd7bce4be..d94d18f8a4be 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o obj-$(CONFIG_LEDS_RENESAS_TPU) += leds-renesas-tpu.o obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o obj-$(CONFIG_LEDS_LM3556) += leds-lm3556.o +obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o diff --git a/drivers/leds/leds-blinkm.c b/drivers/leds/leds-blinkm.c new file mode 100644 index 000000000000..5a9df43e5302 --- /dev/null +++ b/drivers/leds/leds-blinkm.c @@ -0,0 +1,812 @@ +/* + * leds-blinkm.c + * (c) Jan-Simon Möller (dl9pf@gmx.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan - BlinkM is on 0x09 by default*/ +static const unsigned short normal_i2c[] = { 0x09, I2C_CLIENT_END }; + +static int blinkm_transfer_hw(struct i2c_client *client, int cmd); +static int blinkm_test_run(struct i2c_client *client); + +struct blinkm_led { + struct i2c_client *i2c_client; + struct led_classdev led_cdev; + int id; + atomic_t active; +}; + +struct blinkm_work { + struct blinkm_led *blinkm_led; + struct work_struct work; +}; + +#define cdev_to_blmled(c) container_of(c, struct blinkm_led, led_cdev) +#define work_to_blmwork(c) container_of(c, struct blinkm_work, work) + +struct blinkm_data { + struct i2c_client *i2c_client; + struct mutex update_lock; + /* used for led class interface */ + struct blinkm_led blinkm_leds[3]; + /* used for "blinkm" sysfs interface */ + u8 red; /* color red */ + u8 green; /* color green */ + u8 blue; /* color blue */ + /* next values to use for transfer */ + u8 next_red; /* color red */ + u8 next_green; /* color green */ + u8 next_blue; /* color blue */ + /* internal use */ + u8 args[7]; /* set of args for transmission */ + u8 i2c_addr; /* i2c addr */ + u8 fw_ver; /* firmware version */ + /* used, but not from userspace */ + u8 hue; /* HSB hue */ + u8 saturation; /* HSB saturation */ + u8 brightness; /* HSB brightness */ + u8 next_hue; /* HSB hue */ + u8 next_saturation; /* HSB saturation */ + u8 next_brightness; /* HSB brightness */ + /* currently unused / todo */ + u8 fade_speed; /* fade speed 1 - 255 */ + s8 time_adjust; /* time adjust -128 - 127 */ + u8 fade:1; /* fade on = 1, off = 0 */ + u8 rand:1; /* rand fade mode on = 1 */ + u8 script_id; /* script ID */ + u8 script_repeats; /* repeats of script */ + u8 script_startline; /* line to start */ +}; + +/* Colors */ +#define RED 0 +#define GREEN 1 +#define BLUE 2 + +/* mapping command names to cmd chars - see datasheet */ +#define BLM_GO_RGB 0 +#define BLM_FADE_RGB 1 +#define BLM_FADE_HSB 2 +#define BLM_FADE_RAND_RGB 3 +#define BLM_FADE_RAND_HSB 4 +#define BLM_PLAY_SCRIPT 5 +#define BLM_STOP_SCRIPT 6 +#define BLM_SET_FADE_SPEED 7 +#define BLM_SET_TIME_ADJ 8 +#define BLM_GET_CUR_RGB 9 +#define BLM_WRITE_SCRIPT_LINE 10 +#define BLM_READ_SCRIPT_LINE 11 +#define BLM_SET_SCRIPT_LR 12 /* Length & Repeats */ +#define BLM_SET_ADDR 13 +#define BLM_GET_ADDR 14 +#define BLM_GET_FW_VER 15 +#define BLM_SET_STARTUP_PARAM 16 + +/* BlinkM Commands + * as extracted out of the datasheet: + * + * cmdchar = command (ascii) + * cmdbyte = command in hex + * nr_args = number of arguments (to send) + * nr_ret = number of return values (to read) + * dir = direction (0 = read, 1 = write, 2 = both) + * + */ +static const struct { + char cmdchar; + u8 cmdbyte; + u8 nr_args; + u8 nr_ret; + u8 dir:2; +} blinkm_cmds[17] = { + /* cmdchar, cmdbyte, nr_args, nr_ret, dir */ + { 'n', 0x6e, 3, 0, 1}, + { 'c', 0x63, 3, 0, 1}, + { 'h', 0x68, 3, 0, 1}, + { 'C', 0x43, 3, 0, 1}, + { 'H', 0x48, 3, 0, 1}, + { 'p', 0x70, 3, 0, 1}, + { 'o', 0x6f, 0, 0, 1}, + { 'f', 0x66, 1, 0, 1}, + { 't', 0x74, 1, 0, 1}, + { 'g', 0x67, 0, 3, 0}, + { 'W', 0x57, 7, 0, 1}, + { 'R', 0x52, 2, 5, 2}, + { 'L', 0x4c, 3, 0, 1}, + { 'A', 0x41, 4, 0, 1}, + { 'a', 0x61, 0, 1, 0}, + { 'Z', 0x5a, 0, 1, 0}, + { 'B', 0x42, 5, 0, 1}, +}; + +static ssize_t show_color_common(struct device *dev, char *buf, int color) +{ + struct i2c_client *client; + struct blinkm_data *data; + int ret; + + client = to_i2c_client(dev); + data = i2c_get_clientdata(client); + + ret = blinkm_transfer_hw(client, BLM_GET_CUR_RGB); + if (ret < 0) + return ret; + switch (color) { + case RED: + return scnprintf(buf, PAGE_SIZE, "%02X\n", data->red); + break; + case GREEN: + return scnprintf(buf, PAGE_SIZE, "%02X\n", data->green); + break; + case BLUE: + return scnprintf(buf, PAGE_SIZE, "%02X\n", data->blue); + break; + default: + return -EINVAL; + } + return -EINVAL; +} + +static int store_color_common(struct device *dev, const char *buf, int color) +{ + struct i2c_client *client; + struct blinkm_data *data; + int ret; + u8 value; + + client = to_i2c_client(dev); + data = i2c_get_clientdata(client); + + ret = kstrtou8(buf, 10, &value); + if (ret < 0) { + dev_err(dev, "BlinkM: value too large!\n"); + return ret; + } + + switch (color) { + case RED: + data->next_red = value; + break; + case GREEN: + data->next_green = value; + break; + case BLUE: + data->next_blue = value; + break; + default: + return -EINVAL; + } + + dev_dbg(dev, "next_red = %d, next_green = %d, next_blue = %d\n", + data->next_red, data->next_green, data->next_blue); + + /* if mode ... */ + ret = blinkm_transfer_hw(client, BLM_GO_RGB); + if (ret < 0) { + dev_err(dev, "BlinkM: can't set RGB\n"); + return ret; + } + return 0; +} + +static ssize_t show_red(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_color_common(dev, buf, RED); +} + +static ssize_t store_red(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + + ret = store_color_common(dev, buf, RED); + if (ret < 0) + return ret; + return count; +} + +static DEVICE_ATTR(red, S_IRUGO | S_IWUSR, show_red, store_red); + +static ssize_t show_green(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_color_common(dev, buf, GREEN); +} + +static ssize_t store_green(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + + int ret; + + ret = store_color_common(dev, buf, GREEN); + if (ret < 0) + return ret; + return count; +} + +static DEVICE_ATTR(green, S_IRUGO | S_IWUSR, show_green, store_green); + +static ssize_t show_blue(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_color_common(dev, buf, BLUE); +} + +static ssize_t store_blue(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + + ret = store_color_common(dev, buf, BLUE); + if (ret < 0) + return ret; + return count; +} + +static DEVICE_ATTR(blue, S_IRUGO | S_IWUSR, show_blue, store_blue); + +static ssize_t show_test(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return scnprintf(buf, PAGE_SIZE, + "#Write into test to start test sequence!#\n"); +} + +static ssize_t store_test(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + + struct i2c_client *client; + int ret; + client = to_i2c_client(dev); + + /*test */ + ret = blinkm_test_run(client); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR(test, S_IRUGO | S_IWUSR, show_test, store_test); + +/* TODO: HSB, fade, timeadj, script ... */ + +static struct attribute *blinkm_attrs[] = { + &dev_attr_red.attr, + &dev_attr_green.attr, + &dev_attr_blue.attr, + &dev_attr_test.attr, + NULL, +}; + +static struct attribute_group blinkm_group = { + .name = "blinkm", + .attrs = blinkm_attrs, +}; + +static int blinkm_write(struct i2c_client *client, int cmd, u8 *arg) +{ + int result; + int i; + int arglen = blinkm_cmds[cmd].nr_args; + /* write out cmd to blinkm - always / default step */ + result = i2c_smbus_write_byte(client, blinkm_cmds[cmd].cmdbyte); + if (result < 0) + return result; + /* no args to write out */ + if (arglen == 0) + return 0; + + for (i = 0; i < arglen; i++) { + /* repeat for arglen */ + result = i2c_smbus_write_byte(client, arg[i]); + if (result < 0) + return result; + } + return 0; +} + +static int blinkm_read(struct i2c_client *client, int cmd, u8 *arg) +{ + int result; + int i; + int retlen = blinkm_cmds[cmd].nr_ret; + for (i = 0; i < retlen; i++) { + /* repeat for retlen */ + result = i2c_smbus_read_byte(client); + if (result < 0) + return result; + arg[i] = result; + } + + return 0; +} + +static int blinkm_transfer_hw(struct i2c_client *client, int cmd) +{ + /* the protocol is simple but non-standard: + * e.g. cmd 'g' (= 0x67) for "get device address" + * - which defaults to 0x09 - would be the sequence: + * a) write 0x67 to the device (byte write) + * b) read the value (0x09) back right after (byte read) + * + * Watch out for "unfinished" sequences (i.e. not enough reads + * or writes after a command. It will make the blinkM misbehave. + * Sequence is key here. + */ + + /* args / return are in private data struct */ + struct blinkm_data *data = i2c_get_clientdata(client); + + /* We start hardware transfers which are not to be + * mixed with other commands. Aquire a lock now. */ + if (mutex_lock_interruptible(&data->update_lock) < 0) + return -EAGAIN; + + /* switch cmd - usually write before reads */ + switch (cmd) { + case BLM_FADE_RAND_RGB: + case BLM_GO_RGB: + case BLM_FADE_RGB: + data->args[0] = data->next_red; + data->args[1] = data->next_green; + data->args[2] = data->next_blue; + blinkm_write(client, cmd, data->args); + data->red = data->args[0]; + data->green = data->args[1]; + data->blue = data->args[2]; + break; + case BLM_FADE_HSB: + case BLM_FADE_RAND_HSB: + data->args[0] = data->next_hue; + data->args[1] = data->next_saturation; + data->args[2] = data->next_brightness; + blinkm_write(client, cmd, data->args); + data->hue = data->next_hue; + data->saturation = data->next_saturation; + data->brightness = data->next_brightness; + break; + case BLM_PLAY_SCRIPT: + data->args[0] = data->script_id; + data->args[1] = data->script_repeats; + data->args[2] = data->script_startline; + blinkm_write(client, cmd, data->args); + break; + case BLM_STOP_SCRIPT: + blinkm_write(client, cmd, NULL); + break; + case BLM_GET_CUR_RGB: + data->args[0] = data->red; + data->args[1] = data->green; + data->args[2] = data->blue; + blinkm_write(client, cmd, NULL); + blinkm_read(client, cmd, data->args); + data->red = data->args[0]; + data->green = data->args[1]; + data->blue = data->args[2]; + break; + case BLM_GET_ADDR: + data->args[0] = data->i2c_addr; + blinkm_write(client, cmd, NULL); + blinkm_read(client, cmd, data->args); + data->i2c_addr = data->args[0]; + break; + case BLM_SET_TIME_ADJ: + case BLM_SET_FADE_SPEED: + case BLM_READ_SCRIPT_LINE: + case BLM_WRITE_SCRIPT_LINE: + case BLM_SET_SCRIPT_LR: + case BLM_SET_ADDR: + case BLM_GET_FW_VER: + case BLM_SET_STARTUP_PARAM: + dev_err(&client->dev, + "BlinkM: cmd %d not implemented yet.\n", cmd); + break; + default: + dev_err(&client->dev, "BlinkM: unknown command %d\n", cmd); + mutex_unlock(&data->update_lock); + return -EINVAL; + } /* end switch(cmd) */ + + /* transfers done, unlock */ + mutex_unlock(&data->update_lock); + return 0; +} + +static void led_work(struct work_struct *work) +{ + int ret; + struct blinkm_led *led; + struct blinkm_data *data ; + struct blinkm_work *blm_work = work_to_blmwork(work); + + led = blm_work->blinkm_led; + data = i2c_get_clientdata(led->i2c_client); + ret = blinkm_transfer_hw(led->i2c_client, BLM_GO_RGB); + atomic_dec(&led->active); + dev_dbg(&led->i2c_client->dev, + "# DONE # next_red = %d, next_green = %d," + " next_blue = %d, active = %d\n", + data->next_red, data->next_green, + data->next_blue, atomic_read(&led->active)); + kfree(blm_work); +} + +static int blinkm_led_common_set(struct led_classdev *led_cdev, + enum led_brightness value, int color) +{ + /* led_brightness is 0, 127 or 255 - we just use it here as-is */ + struct blinkm_led *led = cdev_to_blmled(led_cdev); + struct blinkm_data *data = i2c_get_clientdata(led->i2c_client); + struct blinkm_work *bl_work = kzalloc(sizeof(struct blinkm_work), + GFP_ATOMIC); + + switch (color) { + case RED: + /* bail out if there's no change */ + if (data->next_red == (u8) value) + return 0; + /* we assume a quite fast sequence here ([off]->on->off) + * think of network led trigger - we cannot blink that fast, so + * in case we already have a off->on->off transition queued up, + * we refuse to queue up more. + * Revisit: fast-changing brightness. */ + if (atomic_read(&led->active) > 1) + return 0; + data->next_red = (u8) value; + break; + case GREEN: + /* bail out if there's no change */ + if (data->next_green == (u8) value) + return 0; + /* we assume a quite fast sequence here ([off]->on->off) + * Revisit: fast-changing brightness. */ + if (atomic_read(&led->active) > 1) + return 0; + data->next_green = (u8) value; + break; + case BLUE: + /* bail out if there's no change */ + if (data->next_blue == (u8) value) + return 0; + /* we assume a quite fast sequence here ([off]->on->off) + * Revisit: fast-changing brightness. */ + if (atomic_read(&led->active) > 1) + return 0; + data->next_blue = (u8) value; + break; + + default: + dev_err(&led->i2c_client->dev, "BlinkM: unknown color.\n"); + return -EINVAL; + } + + atomic_inc(&led->active); + dev_dbg(&led->i2c_client->dev, + "#TO_SCHED# next_red = %d, next_green = %d," + " next_blue = %d, active = %d\n", + data->next_red, data->next_green, + data->next_blue, atomic_read(&led->active)); + + /* a fresh work _item_ for each change */ + bl_work->blinkm_led = led; + INIT_WORK(&bl_work->work, led_work); + /* queue work in own queue for easy sync on exit*/ + schedule_work(&bl_work->work); + + return 0; +} + +static void blinkm_led_red_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + blinkm_led_common_set(led_cdev, value, RED); +} + +static void blinkm_led_green_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + blinkm_led_common_set(led_cdev, value, GREEN); +} + +static void blinkm_led_blue_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + blinkm_led_common_set(led_cdev, value, BLUE); +} + +static void blinkm_init_hw(struct i2c_client *client) +{ + int ret; + ret = blinkm_transfer_hw(client, BLM_STOP_SCRIPT); + ret = blinkm_transfer_hw(client, BLM_GO_RGB); +} + +static int blinkm_test_run(struct i2c_client *client) +{ + int ret; + struct blinkm_data *data = i2c_get_clientdata(client); + + data->next_red = 0x01; + data->next_green = 0x05; + data->next_blue = 0x10; + ret = blinkm_transfer_hw(client, BLM_GO_RGB); + if (ret < 0) + return ret; + msleep(2000); + + data->next_red = 0x25; + data->next_green = 0x10; + data->next_blue = 0x31; + ret = blinkm_transfer_hw(client, BLM_FADE_RGB); + if (ret < 0) + return ret; + msleep(2000); + + data->next_hue = 0x50; + data->next_saturation = 0x10; + data->next_brightness = 0x20; + ret = blinkm_transfer_hw(client, BLM_FADE_HSB); + if (ret < 0) + return ret; + msleep(2000); + + return 0; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int ret; + int count = 99; + u8 tmpargs[7]; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_WORD_DATA + | I2C_FUNC_SMBUS_WRITE_BYTE)) + return -ENODEV; + + /* Now, we do the remaining detection. Simple for now. */ + /* We might need more guards to protect other i2c slaves */ + + /* make sure the blinkM is balanced (read/writes) */ + while (count > 0) { + ret = blinkm_write(client, BLM_GET_ADDR, NULL); + usleep_range(5000, 10000); + ret = blinkm_read(client, BLM_GET_ADDR, tmpargs); + usleep_range(5000, 10000); + if (tmpargs[0] == 0x09) + count = 0; + count--; + } + + /* Step 1: Read BlinkM address back - cmd_char 'a' */ + ret = blinkm_write(client, BLM_GET_ADDR, NULL); + if (ret < 0) + return -ENODEV; + usleep_range(20000, 30000); /* allow a small delay */ + ret = blinkm_read(client, BLM_GET_ADDR, tmpargs); + if (ret < 0) + return -ENODEV; + + if (tmpargs[0] != 0x09) { + dev_err(&client->dev, "enodev DEV ADDR = 0x%02X\n", tmpargs[0]); + return -ENODEV; + } + + strlcpy(info->type, "blinkm", I2C_NAME_SIZE); + return 0; +} + +static int __devinit blinkm_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct blinkm_data *data; + struct blinkm_led *led[3]; + int err, i; + char blinkm_led_name[28]; + + data = devm_kzalloc(&client->dev, + sizeof(struct blinkm_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + data->i2c_addr = 0x09; + data->i2c_addr = 0x08; + /* i2c addr - use fake addr of 0x08 initially (real is 0x09) */ + data->fw_ver = 0xfe; + /* firmware version - use fake until we read real value + * (currently broken - BlinkM confused!) */ + data->script_id = 0x01; + data->i2c_client = client; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &blinkm_group); + if (err < 0) { + dev_err(&client->dev, "couldn't register sysfs group\n"); + goto exit; + } + + for (i = 0; i < 3; i++) { + /* RED = 0, GREEN = 1, BLUE = 2 */ + led[i] = &data->blinkm_leds[i]; + led[i]->i2c_client = client; + led[i]->id = i; + led[i]->led_cdev.max_brightness = 255; + led[i]->led_cdev.flags = LED_CORE_SUSPENDRESUME; + atomic_set(&led[i]->active, 0); + switch (i) { + case RED: + snprintf(blinkm_led_name, sizeof(blinkm_led_name), + "blinkm-%d-%d-red", + client->adapter->nr, + client->addr); + led[i]->led_cdev.name = blinkm_led_name; + led[i]->led_cdev.brightness_set = blinkm_led_red_set; + err = led_classdev_register(&client->dev, + &led[i]->led_cdev); + if (err < 0) { + dev_err(&client->dev, + "couldn't register LED %s\n", + led[i]->led_cdev.name); + goto failred; + } + break; + case GREEN: + snprintf(blinkm_led_name, sizeof(blinkm_led_name), + "blinkm-%d-%d-green", + client->adapter->nr, + client->addr); + led[i]->led_cdev.name = blinkm_led_name; + led[i]->led_cdev.brightness_set = blinkm_led_green_set; + err = led_classdev_register(&client->dev, + &led[i]->led_cdev); + if (err < 0) { + dev_err(&client->dev, + "couldn't register LED %s\n", + led[i]->led_cdev.name); + goto failgreen; + } + break; + case BLUE: + snprintf(blinkm_led_name, sizeof(blinkm_led_name), + "blinkm-%d-%d-blue", + client->adapter->nr, + client->addr); + led[i]->led_cdev.name = blinkm_led_name; + led[i]->led_cdev.brightness_set = blinkm_led_blue_set; + err = led_classdev_register(&client->dev, + &led[i]->led_cdev); + if (err < 0) { + dev_err(&client->dev, + "couldn't register LED %s\n", + led[i]->led_cdev.name); + goto failblue; + } + break; + } /* end switch */ + } /* end for */ + + /* Initialize the blinkm */ + blinkm_init_hw(client); + + return 0; + +failblue: + led_classdev_unregister(&led[GREEN]->led_cdev); + +failgreen: + led_classdev_unregister(&led[RED]->led_cdev); + +failred: + sysfs_remove_group(&client->dev.kobj, &blinkm_group); +exit: + return err; +} + +static int __devexit blinkm_remove(struct i2c_client *client) +{ + struct blinkm_data *data = i2c_get_clientdata(client); + int ret = 0; + int i; + + /* make sure no workqueue entries are pending */ + for (i = 0; i < 3; i++) { + flush_scheduled_work(); + led_classdev_unregister(&data->blinkm_leds[i].led_cdev); + } + + /* reset rgb */ + data->next_red = 0x00; + data->next_green = 0x00; + data->next_blue = 0x00; + ret = blinkm_transfer_hw(client, BLM_FADE_RGB); + if (ret < 0) + dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); + + /* reset hsb */ + data->next_hue = 0x00; + data->next_saturation = 0x00; + data->next_brightness = 0x00; + ret = blinkm_transfer_hw(client, BLM_FADE_HSB); + if (ret < 0) + dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); + + /* red fade to off */ + data->next_red = 0xff; + ret = blinkm_transfer_hw(client, BLM_GO_RGB); + if (ret < 0) + dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); + + /* off */ + data->next_red = 0x00; + ret = blinkm_transfer_hw(client, BLM_FADE_RGB); + if (ret < 0) + dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); + + sysfs_remove_group(&client->dev.kobj, &blinkm_group); + return 0; +} + +static const struct i2c_device_id blinkm_id[] = { + {"blinkm", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, blinkm_id); + + /* This is the driver that will be inserted */ +static struct i2c_driver blinkm_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "blinkm", + }, + .probe = blinkm_probe, + .remove = __devexit_p(blinkm_remove), + .id_table = blinkm_id, + .detect = blinkm_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(blinkm_driver); + +MODULE_AUTHOR("Jan-Simon Moeller "); +MODULE_DESCRIPTION("BlinkM RGB LED driver"); +MODULE_LICENSE("GPL"); + -- cgit v1.2.3