From 81d22878092feab779f3efaab404036d31dc06f8 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Wed, 26 Jun 2013 15:52:49 +0300 Subject: leds: Add device tree binding for pca9633 Similar to tca6507, we can just parse the standard LED properties for pca9633. Tested on a pca9632, which is compatible with pca9633. Signed-off-by: Tony Lindgren Signed-off-by: Bryan Wu diff --git a/Documentation/devicetree/bindings/leds/pca9633.txt b/Documentation/devicetree/bindings/leds/pca9633.txt new file mode 100644 index 0000000..8140512 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/pca9633.txt @@ -0,0 +1,45 @@ +LEDs connected to pca9633 or pca9632 + +Required properties: +- compatible : should be : "nxp,pca963x" + +Optional properties: +- nxp,totem-pole : use totem pole (push-pull) instead of default open-drain + +Each led is represented as a sub-node of the nxp,pca9633 device. + +LED sub-node properties: +- label : (optional) see Documentation/devicetree/bindings/leds/common.txt +- reg : number of LED line (could be from 0 to 4) +- linux,default-trigger : (optional) + see Documentation/devicetree/bindings/leds/common.txt + +Examples: + +pca9632: pca9632 { + compatible = "nxp,pca9632", "nxp,pca963x"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x62>; + + red@0 { + label = "red"; + reg = <0>; + linux,default-trigger = "none"; + }; + green@1 { + label = "green"; + reg = <1>; + linux,default-trigger = "none"; + }; + blue@2 { + label = "blue"; + reg = <2>; + linux,default-trigger = "none"; + }; + unused@3 { + label = "unused"; + reg = <3>; + linux,default-trigger = "none"; + }; +}; diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c index 9aae567..90935e4 100644 --- a/drivers/leds/leds-pca9633.c +++ b/drivers/leds/leds-pca9633.c @@ -22,6 +22,7 @@ #include #include #include +#include #include /* LED select registers determine the source that drives LED outputs */ @@ -93,6 +94,67 @@ static void pca9633_led_set(struct led_classdev *led_cdev, schedule_work(&pca9633->work); } +#if IS_ENABLED(CONFIG_OF) +static struct pca9633_platform_data * +pca9633_dt_init(struct i2c_client *client) +{ + struct device_node *np = client->dev.of_node, *child; + struct pca9633_platform_data *pdata; + struct led_info *pca9633_leds; + int count; + + count = of_get_child_count(np); + if (!count || count > 4) + return ERR_PTR(-ENODEV); + + pca9633_leds = devm_kzalloc(&client->dev, + sizeof(struct led_info) * count, GFP_KERNEL); + if (!pca9633_leds) + return ERR_PTR(-ENOMEM); + + for_each_child_of_node(np, child) { + struct led_info led; + u32 reg; + int res; + + led.name = + of_get_property(child, "label", NULL) ? : child->name; + led.default_trigger = + of_get_property(child, "linux,default-trigger", NULL); + res = of_property_read_u32(child, "reg", ®); + if (res != 0) + continue; + pca9633_leds[reg] = led; + } + pdata = devm_kzalloc(&client->dev, + sizeof(struct pca9633_platform_data), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->leds.leds = pca9633_leds; + pdata->leds.num_leds = count; + + /* default to open-drain unless totem pole (push-pull) is specified */ + if (of_property_read_bool(np, "nxp,totem-pole")) + pdata->outdrv = PCA9633_TOTEM_POLE; + else + pdata->outdrv = PCA9633_OPEN_DRAIN; + + return pdata; +} + +static const struct of_device_id of_pca9633_match[] = { + { .compatible = "nxp,pca963x", }, + {}, +}; +#else +static struct pca9633_platform_data * +pca9633_dt_init(struct i2c_client *client) +{ + return ERR_PTR(-ENODEV); +} +#endif + static int pca9633_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -102,6 +164,14 @@ static int pca9633_probe(struct i2c_client *client, pdata = client->dev.platform_data; + if (!pdata) { + pdata = pca9633_dt_init(client); + if (IS_ERR(pdata)) { + dev_warn(&client->dev, "could not parse configuration\n"); + pdata = NULL; + } + } + if (pdata) { if (pdata->leds.num_leds <= 0 || pdata->leds.num_leds > 4) { dev_err(&client->dev, "board info must claim at most 4 LEDs"); @@ -181,6 +251,7 @@ static struct i2c_driver pca9633_driver = { .driver = { .name = "leds-pca9633", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(of_pca9633_match), }, .probe = pca9633_probe, .remove = pca9633_remove, -- cgit v0.10.2 From 33b3a561f417ec3e1013999ce8bdb6c055abb1ce Mon Sep 17 00:00:00 2001 From: "Kim, Milo" Date: Tue, 9 Jul 2013 02:11:37 -0700 Subject: leds: support new LP8501 device - another LP55xx common LP8501 can drive up to 9 channels like LP5523. LEDs can be controlled directly via the I2C and programmable engines are supported. LP55xx common driver LP8501 is one of LP55xx family device, so LP55xx common code are used. Chip specific data is defined in the structure, 'lp55xx_device_config'. Differences between LP8501 and LP5523 Different register layout for LED output control and others. LP8501 specific feature for separate output power selection. LP8501 doesn't support external clock detection. Different programming engine data. LP8501 specific feature - output power selection Output channels are selected by power selection - Vout or Vdd. Separate power for VDD1-6 and VDD7-9 are available. It is configurable in the platform data. To support this feature, LP55xx DT structure and header are changed. Device tree binding is updated as well. LED pattern data Example pattern data is updated in the driver documentation. Signed-off-by: Milo Kim Signed-off-by: Bryan Wu diff --git a/Documentation/devicetree/bindings/leds/leds-lp55xx.txt b/Documentation/devicetree/bindings/leds/leds-lp55xx.txt index d517688..a61727f 100644 --- a/Documentation/devicetree/bindings/leds/leds-lp55xx.txt +++ b/Documentation/devicetree/bindings/leds/leds-lp55xx.txt @@ -1,7 +1,7 @@ Binding for TI/National Semiconductor LP55xx Led Drivers Required properties: -- compatible: "national,lp5521" or "national,lp5523" or "ti,lp5562" +- compatible: "national,lp5521" or "national,lp5523" or "ti,lp5562" or "ti,lp8501" - reg: I2C slave address - clock-mode: Input clock mode, (0: automode, 1: internal, 2: external) @@ -11,6 +11,11 @@ Each child has own specific current settings Optional properties: - label: Used for naming LEDs +- pwr-sel: LP8501 specific property. Power selection for output channels. + 0: D1~9 are connected to VDD + 1: D1~6 with VDD, D7~9 with VOUT + 2: D1~6 with VOUT, D7~9 with VDD + 3: D1~9 are connected to VOUT Alternatively, each child can have specific channel name - chan-name: Name of each channel name @@ -145,3 +150,68 @@ lp5562@30 { max-cur = /bits/ 8 <0x60>; }; }; + +example 4) LP8501 +9 channels are defined. The 'pwr-sel' is LP8501 specific property. +Others are same as LP5523. + +lp8501@32 { + compatible = "ti,lp8501"; + reg = <0x32>; + clock-mode = /bits/ 8 <2>; + pwr-sel = /bits/ 8 <3>; /* D1~9 connected to VOUT */ + + chan0 { + chan-name = "d1"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan1 { + chan-name = "d2"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan2 { + chan-name = "d3"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan3 { + chan-name = "d4"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan4 { + chan-name = "d5"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan5 { + chan-name = "d6"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan6 { + chan-name = "d7"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan7 { + chan-name = "d8"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; + + chan8 { + chan-name = "d9"; + led-cur = /bits/ 8 <0x14>; + max-cur = /bits/ 8 <0x20>; + }; +}; diff --git a/Documentation/leds/leds-lp55xx.txt b/Documentation/leds/leds-lp55xx.txt index eec8fa2..82713ff 100644 --- a/Documentation/leds/leds-lp55xx.txt +++ b/Documentation/leds/leds-lp55xx.txt @@ -1,11 +1,11 @@ -LP5521/LP5523/LP55231 Common Driver -=================================== +LP5521/LP5523/LP55231/LP5562/LP8501 Common Driver +================================================= Authors: Milo(Woogyom) Kim Description ----------- -LP5521, LP5523/55231 and LP5562 have common features as below. +LP5521, LP5523/55231, LP5562 and LP8501 have common features as below. Register access via the I2C Device initialization/deinitialization @@ -109,6 +109,30 @@ As soon as 'loading' is set to 0, registered callback is called. Inside the callback, the selected engine is loaded and memory is updated. To run programmed pattern, 'run_engine' attribute should be enabled. +The pattern sqeuence of LP8501 is same as LP5523. +However pattern data is specific. +Ex 1) Engine 1 is used +echo 1 > /sys/bus/i2c/devices/xxxx/select_engine +echo 1 > /sys/class/firmware/lp8501/loading +echo "9d0140ff7e0040007e00a001c000" > /sys/class/firmware/lp8501/data +echo 0 > /sys/class/firmware/lp8501/loading +echo 1 > /sys/bus/i2c/devices/xxxx/run_engine + +Ex 2) Engine 2 and 3 are used at the same time +echo 2 > /sys/bus/i2c/devices/xxxx/select_engine +sleep 1 +echo 1 > /sys/class/firmware/lp8501/loading +echo "9d0140ff7e0040007e00a001c000" > /sys/class/firmware/lp8501/data +echo 0 > /sys/class/firmware/lp8501/loading +sleep 1 +echo 3 > /sys/bus/i2c/devices/xxxx/select_engine +sleep 1 +echo 1 > /sys/class/firmware/lp8501/loading +echo "9d0340ff7e0040007e00a001c000" > /sys/class/firmware/lp8501/data +echo 0 > /sys/class/firmware/lp8501/loading +sleep 1 +echo 1 > /sys/class/leds/d1/device/run_engine + ( 'run_engine' and 'firmware_cb' ) The sequence of running the program data is common. But each device has own specific register addresses for commands. diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index e43402dd..77329ce 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -194,11 +194,11 @@ config LEDS_LP3944 module will be called leds-lp3944. config LEDS_LP55XX_COMMON - tristate "Common Driver for TI/National LP5521, LP5523/55231 and LP5562" - depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 + tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501" + depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 || LEDS_LP8501 select FW_LOADER help - This option supports common operations for LP5521 and LP5523/55231 + This option supports common operations for LP5521/5523/55231/5562/8501 devices. config LEDS_LP5521 @@ -232,6 +232,18 @@ config LEDS_LP5562 Driver provides direct control via LED class and interface for programming the engines. +config LEDS_LP8501 + tristate "LED Support for TI LP8501 LED driver chip" + depends on LEDS_CLASS && I2C + select LEDS_LP55XX_COMMON + help + If you say yes here you get support for TI LP8501 LED driver. + It is 9 channel chip with programmable engines. + Driver provides direct control via LED class and interface for + programming the engines. + It is similar as LP5523, but output power selection is available. + And register layout and engine program schemes are different. + 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 ac28977..3013113 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -27,6 +27,7 @@ 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_LP8501) += leds-lp8501.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-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c index c2fecd4..351825b 100644 --- a/drivers/leds/leds-lp55xx-common.c +++ b/drivers/leds/leds-lp55xx-common.c @@ -593,6 +593,9 @@ int lp55xx_of_populate_pdata(struct device *dev, struct device_node *np) of_property_read_string(np, "label", &pdata->label); of_property_read_u8(np, "clock-mode", &pdata->clock_mode); + /* LP8501 specific */ + of_property_read_u8(np, "pwr-sel", (u8 *)&pdata->pwr_sel); + dev->platform_data = pdata; return 0; diff --git a/drivers/leds/leds-lp8501.c b/drivers/leds/leds-lp8501.c new file mode 100644 index 0000000..4573b94 --- /dev/null +++ b/drivers/leds/leds-lp8501.c @@ -0,0 +1,410 @@ +/* + * TI LP8501 9 channel 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 LP8501_PROGRAM_LENGTH 32 +#define LP8501_MAX_LEDS 9 + +/* Registers */ +#define LP8501_REG_ENABLE 0x00 +#define LP8501_ENABLE BIT(6) +#define LP8501_EXEC_M 0x3F +#define LP8501_EXEC_ENG1_M 0x30 +#define LP8501_EXEC_ENG2_M 0x0C +#define LP8501_EXEC_ENG3_M 0x03 +#define LP8501_RUN_ENG1 0x20 +#define LP8501_RUN_ENG2 0x08 +#define LP8501_RUN_ENG3 0x02 + +#define LP8501_REG_OP_MODE 0x01 +#define LP8501_MODE_ENG1_M 0x30 +#define LP8501_MODE_ENG2_M 0x0C +#define LP8501_MODE_ENG3_M 0x03 +#define LP8501_LOAD_ENG1 0x10 +#define LP8501_LOAD_ENG2 0x04 +#define LP8501_LOAD_ENG3 0x01 + +#define LP8501_REG_PWR_CONFIG 0x05 +#define LP8501_PWR_CONFIG_M 0x03 + +#define LP8501_REG_LED_PWM_BASE 0x16 + +#define LP8501_REG_LED_CURRENT_BASE 0x26 + +#define LP8501_REG_CONFIG 0x36 +#define LP8501_PWM_PSAVE BIT(7) +#define LP8501_AUTO_INC BIT(6) +#define LP8501_PWR_SAVE BIT(5) +#define LP8501_CP_AUTO 0x18 +#define LP8501_INT_CLK BIT(0) +#define LP8501_DEFAULT_CFG \ + (LP8501_PWM_PSAVE | LP8501_AUTO_INC | LP8501_PWR_SAVE | LP8501_CP_AUTO) + +#define LP8501_REG_RESET 0x3D +#define LP8501_RESET 0xFF + +#define LP8501_REG_PROG_PAGE_SEL 0x4F +#define LP8501_PAGE_ENG1 0 +#define LP8501_PAGE_ENG2 1 +#define LP8501_PAGE_ENG3 2 + +#define LP8501_REG_PROG_MEM 0x50 + +#define LP8501_ENG1_IS_LOADING(mode) \ + ((mode & LP8501_MODE_ENG1_M) == LP8501_LOAD_ENG1) +#define LP8501_ENG2_IS_LOADING(mode) \ + ((mode & LP8501_MODE_ENG2_M) == LP8501_LOAD_ENG2) +#define LP8501_ENG3_IS_LOADING(mode) \ + ((mode & LP8501_MODE_ENG3_M) == LP8501_LOAD_ENG3) + +static inline void lp8501_wait_opmode_done(void) +{ + usleep_range(1000, 2000); +} + +static void lp8501_set_led_current(struct lp55xx_led *led, u8 led_current) +{ + led->led_current = led_current; + lp55xx_write(led->chip, LP8501_REG_LED_CURRENT_BASE + led->chan_nr, + led_current); +} + +static int lp8501_post_init_device(struct lp55xx_chip *chip) +{ + int ret; + u8 val = LP8501_DEFAULT_CFG; + + ret = lp55xx_write(chip, LP8501_REG_ENABLE, LP8501_ENABLE); + if (ret) + return ret; + + /* Chip startup time is 500 us, 1 - 2 ms gives some margin */ + usleep_range(1000, 2000); + + if (chip->pdata->clock_mode != LP55XX_CLOCK_EXT) + val |= LP8501_INT_CLK; + + ret = lp55xx_write(chip, LP8501_REG_CONFIG, val); + if (ret) + return ret; + + /* Power selection for each output */ + return lp55xx_update_bits(chip, LP8501_REG_PWR_CONFIG, + LP8501_PWR_CONFIG_M, chip->pdata->pwr_sel); +} + +static void lp8501_load_engine(struct lp55xx_chip *chip) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + u8 mask[] = { + [LP55XX_ENGINE_1] = LP8501_MODE_ENG1_M, + [LP55XX_ENGINE_2] = LP8501_MODE_ENG2_M, + [LP55XX_ENGINE_3] = LP8501_MODE_ENG3_M, + }; + + u8 val[] = { + [LP55XX_ENGINE_1] = LP8501_LOAD_ENG1, + [LP55XX_ENGINE_2] = LP8501_LOAD_ENG2, + [LP55XX_ENGINE_3] = LP8501_LOAD_ENG3, + }; + + u8 page_sel[] = { + [LP55XX_ENGINE_1] = LP8501_PAGE_ENG1, + [LP55XX_ENGINE_2] = LP8501_PAGE_ENG2, + [LP55XX_ENGINE_3] = LP8501_PAGE_ENG3, + }; + + lp55xx_update_bits(chip, LP8501_REG_OP_MODE, mask[idx], val[idx]); + + lp8501_wait_opmode_done(); + + lp55xx_write(chip, LP8501_REG_PROG_PAGE_SEL, page_sel[idx]); +} + +static void lp8501_stop_engine(struct lp55xx_chip *chip) +{ + lp55xx_write(chip, LP8501_REG_OP_MODE, 0); + lp8501_wait_opmode_done(); +} + +static void lp8501_turn_off_channels(struct lp55xx_chip *chip) +{ + int i; + + for (i = 0; i < LP8501_MAX_LEDS; i++) + lp55xx_write(chip, LP8501_REG_LED_PWM_BASE + i, 0); +} + +static void lp8501_run_engine(struct lp55xx_chip *chip, bool start) +{ + int ret; + u8 mode; + u8 exec; + + /* stop engine */ + if (!start) { + lp8501_stop_engine(chip); + lp8501_turn_off_channels(chip); + return; + } + + /* + * To run the engine, + * operation mode and enable register should updated at the same time + */ + + ret = lp55xx_read(chip, LP8501_REG_OP_MODE, &mode); + if (ret) + return; + + ret = lp55xx_read(chip, LP8501_REG_ENABLE, &exec); + if (ret) + return; + + /* change operation mode to RUN only when each engine is loading */ + if (LP8501_ENG1_IS_LOADING(mode)) { + mode = (mode & ~LP8501_MODE_ENG1_M) | LP8501_RUN_ENG1; + exec = (exec & ~LP8501_EXEC_ENG1_M) | LP8501_RUN_ENG1; + } + + if (LP8501_ENG2_IS_LOADING(mode)) { + mode = (mode & ~LP8501_MODE_ENG2_M) | LP8501_RUN_ENG2; + exec = (exec & ~LP8501_EXEC_ENG2_M) | LP8501_RUN_ENG2; + } + + if (LP8501_ENG3_IS_LOADING(mode)) { + mode = (mode & ~LP8501_MODE_ENG3_M) | LP8501_RUN_ENG3; + exec = (exec & ~LP8501_EXEC_ENG3_M) | LP8501_RUN_ENG3; + } + + lp55xx_write(chip, LP8501_REG_OP_MODE, mode); + lp8501_wait_opmode_done(); + + lp55xx_update_bits(chip, LP8501_REG_ENABLE, LP8501_EXEC_M, exec); +} + +static int lp8501_update_program_memory(struct lp55xx_chip *chip, + const u8 *data, size_t size) +{ + u8 pattern[LP8501_PROGRAM_LENGTH] = {0}; + unsigned cmd; + char c[3]; + int update_size; + int nrchars; + int offset = 0; + int ret; + int i; + + /* clear program memory before updating */ + for (i = 0; i < LP8501_PROGRAM_LENGTH; i++) + lp55xx_write(chip, LP8501_REG_PROG_MEM + i, 0); + + i = 0; + while ((offset < size - 1) && (i < LP8501_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; + + update_size = i; + for (i = 0; i < update_size; i++) + lp55xx_write(chip, LP8501_REG_PROG_MEM + i, pattern[i]); + + return 0; + +err: + dev_err(&chip->cl->dev, "wrong pattern format\n"); + return -EINVAL; +} + +static void lp8501_firmware_loaded(struct lp55xx_chip *chip) +{ + const struct firmware *fw = chip->fw; + + if (fw->size > LP8501_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 + */ + + lp8501_load_engine(chip); + lp8501_update_program_memory(chip, fw->data, fw->size); +} + +static void lp8501_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; + + mutex_lock(&chip->lock); + lp55xx_write(chip, LP8501_REG_LED_PWM_BASE + led->chan_nr, + led->brightness); + mutex_unlock(&chip->lock); +} + +/* Chip specific configurations */ +static struct lp55xx_device_config lp8501_cfg = { + .reset = { + .addr = LP8501_REG_RESET, + .val = LP8501_RESET, + }, + .enable = { + .addr = LP8501_REG_ENABLE, + .val = LP8501_ENABLE, + }, + .max_channel = LP8501_MAX_LEDS, + .post_init_device = lp8501_post_init_device, + .brightness_work_fn = lp8501_led_brightness_work, + .set_led_current = lp8501_set_led_current, + .firmware_cb = lp8501_firmware_loaded, + .run_engine = lp8501_run_engine, +}; + +static int lp8501_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; + struct device_node *np = client->dev.of_node; + + if (!client->dev.platform_data) { + if (np) { + ret = lp55xx_of_populate_pdata(&client->dev, np); + if (ret < 0) + return ret; + } else { + dev_err(&client->dev, "no platform data\n"); + return -EINVAL; + } + } + pdata = client->dev.platform_data; + + 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 = &lp8501_cfg; + + mutex_init(&chip->lock); + + i2c_set_clientdata(client, led); + + ret = lp55xx_init_device(chip); + if (ret) + goto err_init; + + dev_info(&client->dev, "%s Programmable led chip found\n", id->name); + + 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 lp8501_remove(struct i2c_client *client) +{ + struct lp55xx_led *led = i2c_get_clientdata(client); + struct lp55xx_chip *chip = led->chip; + + lp8501_stop_engine(chip); + lp55xx_unregister_sysfs(chip); + lp55xx_unregister_leds(led, chip); + lp55xx_deinit_device(chip); + + return 0; +} + +static const struct i2c_device_id lp8501_id[] = { + { "lp8501", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp8501_id); + +#ifdef CONFIG_OF +static const struct of_device_id of_lp8501_leds_match[] = { + { .compatible = "ti,lp8501", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, of_lp8501_leds_match); +#endif + +static struct i2c_driver lp8501_driver = { + .driver = { + .name = "lp8501", + .of_match_table = of_match_ptr(of_lp8501_leds_match), + }, + .probe = lp8501_probe, + .remove = lp8501_remove, + .id_table = lp8501_id, +}; + +module_i2c_driver(lp8501_driver); + +MODULE_DESCRIPTION("Texas Instruments LP8501 LED drvier"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/leds-lp55xx.h b/include/linux/platform_data/leds-lp55xx.h index 202e290..51a2ff5 100644 --- a/include/linux/platform_data/leds-lp55xx.h +++ b/include/linux/platform_data/leds-lp55xx.h @@ -36,6 +36,13 @@ struct lp55xx_predef_pattern { u8 size_b; }; +enum lp8501_pwr_sel { + LP8501_ALL_VDD, /* D1~9 are connected to VDD */ + LP8501_6VDD_3VOUT, /* D1~6 with VDD, D7~9 with VOUT */ + LP8501_3VDD_6VOUT, /* D1~6 with VOUT, D7~9 with VDD */ + LP8501_ALL_VOUT, /* D1~9 are connected to VOUT */ +}; + /* * struct lp55xx_platform_data * @led_config : Configurable led class device @@ -67,6 +74,9 @@ struct lp55xx_platform_data { /* Predefined pattern data */ struct lp55xx_predef_pattern *patterns; unsigned int num_patterns; + + /* LP8501 specific */ + enum lp8501_pwr_sel pwr_sel; }; #endif /* _LEDS_LP55XX_H */ -- cgit v0.10.2 From 8465b01827b7a1e0e2464b0a300618bf7add25d8 Mon Sep 17 00:00:00 2001 From: "Mark A. Greer" Date: Thu, 25 Jul 2013 10:16:41 -0700 Subject: leds: pca9633: Add hardware blink support Add hardware blinking support to the pca9633 driver. NOTE: Hardware blinking violates the leds infrastructure driver interface since the hardware only supports blinking all LEDs with the same delay_on/delay_off rates. That is, only the LEDs that are set to blink will actually blink but all LEDs that are set to blink will blink in identical fashion. The delay_on/delay_off values of the last LED that is set to blink will be used for all of the blinking LEDs. If the hardware doesn't support the requested blinking pattern, a default of 500ms on and off will be used. Hardware blinking is disabled by default but can be enabled by setting the 'blink_type' member in the platform_data struct to 'PCA9633_HW_BLINK' or by adding the 'nxp,hw-blink' property to the DTS. (fengguang.wu@intel.com: Removes unneeded semicolon.) Signed-off-by: Mark A. Greer Reported-by: Fengguang Wu Signed-off-by: Bryan Wu diff --git a/Documentation/devicetree/bindings/leds/pca9633.txt b/Documentation/devicetree/bindings/leds/pca9633.txt index 8140512..6d9e1a9 100644 --- a/Documentation/devicetree/bindings/leds/pca9633.txt +++ b/Documentation/devicetree/bindings/leds/pca9633.txt @@ -5,6 +5,7 @@ Required properties: Optional properties: - nxp,totem-pole : use totem pole (push-pull) instead of default open-drain +- nxp,hw-blink : use hardware blinking instead of software blinking Each led is represented as a sub-node of the nxp,pca9633 device. diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c index 90935e4..77b3f24 100644 --- a/drivers/leds/leds-pca9633.c +++ b/drivers/leds/leds-pca9633.c @@ -11,6 +11,15 @@ * * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62) * + * Note that hardware blinking violates the leds infrastructure driver + * interface since the hardware only supports blinking all LEDs with the + * same delay_on/delay_off rates. That is, only the LEDs that are set to + * blink will actually blink but all LEDs that are set to blink will blink + * in identical fashion. The delay_on/delay_off values of the last LED + * that is set to blink will be used for all of the blinking LEDs. + * Hardware blinking is disabled by default but can be enabled by setting + * the 'blink_type' member in the platform_data struct to 'PCA9633_HW_BLINK' + * or by adding the 'nxp,hw-blink' property to the DTS. */ #include @@ -31,30 +40,44 @@ #define PCA9633_LED_PWM 0x2 /* Controlled through PWM */ #define PCA9633_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */ +#define PCA9633_MODE2_DMBLNK 0x20 /* Enable blinking */ + #define PCA9633_MODE1 0x00 #define PCA9633_MODE2 0x01 #define PCA9633_PWM_BASE 0x02 +#define PCA9633_GRPPWM 0x06 +#define PCA9633_GRPFREQ 0x07 #define PCA9633_LEDOUT 0x08 +/* Total blink period in milliseconds */ +#define PCA9632_BLINK_PERIOD_MIN 42 +#define PCA9632_BLINK_PERIOD_MAX 10667 + static const struct i2c_device_id pca9633_id[] = { { "pca9633", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, pca9633_id); +enum pca9633_cmd { + BRIGHTNESS_SET, + BLINK_SET, +}; + struct pca9633_led { struct i2c_client *client; struct work_struct work; enum led_brightness brightness; struct led_classdev led_cdev; int led_num; /* 0 .. 3 potentially */ + enum pca9633_cmd cmd; char name[32]; + u8 gdc; + u8 gfrq; }; -static void pca9633_led_work(struct work_struct *work) +static void pca9633_brightness_work(struct pca9633_led *pca9633) { - struct pca9633_led *pca9633 = container_of(work, - struct pca9633_led, work); u8 ledout = i2c_smbus_read_byte_data(pca9633->client, PCA9633_LEDOUT); int shift = 2 * pca9633->led_num; u8 mask = 0x3 << shift; @@ -78,6 +101,43 @@ static void pca9633_led_work(struct work_struct *work) } } +static void pca9633_blink_work(struct pca9633_led *pca9633) +{ + u8 ledout = i2c_smbus_read_byte_data(pca9633->client, PCA9633_LEDOUT); + u8 mode2 = i2c_smbus_read_byte_data(pca9633->client, PCA9633_MODE2); + int shift = 2 * pca9633->led_num; + u8 mask = 0x3 << shift; + + i2c_smbus_write_byte_data(pca9633->client, PCA9633_GRPPWM, + pca9633->gdc); + + i2c_smbus_write_byte_data(pca9633->client, PCA9633_GRPFREQ, + pca9633->gfrq); + + if (!(mode2 & PCA9633_MODE2_DMBLNK)) + i2c_smbus_write_byte_data(pca9633->client, PCA9633_MODE2, + mode2 | PCA9633_MODE2_DMBLNK); + + if ((ledout & mask) != (PCA9633_LED_GRP_PWM << shift)) + i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT, + (ledout & ~mask) | (PCA9633_LED_GRP_PWM << shift)); +} + +static void pca9633_work(struct work_struct *work) +{ + struct pca9633_led *pca9633 = container_of(work, + struct pca9633_led, work); + + switch (pca9633->cmd) { + case BRIGHTNESS_SET: + pca9633_brightness_work(pca9633); + break; + case BLINK_SET: + pca9633_blink_work(pca9633); + break; + } +} + static void pca9633_led_set(struct led_classdev *led_cdev, enum led_brightness value) { @@ -85,6 +145,7 @@ static void pca9633_led_set(struct led_classdev *led_cdev, pca9633 = container_of(led_cdev, struct pca9633_led, led_cdev); + pca9633->cmd = BRIGHTNESS_SET; pca9633->brightness = value; /* @@ -94,6 +155,64 @@ static void pca9633_led_set(struct led_classdev *led_cdev, schedule_work(&pca9633->work); } +static int pca9633_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + struct pca9633_led *pca9633; + unsigned long time_on, time_off, period; + u8 gdc, gfrq; + + pca9633 = container_of(led_cdev, struct pca9633_led, led_cdev); + + time_on = *delay_on; + time_off = *delay_off; + + /* If both zero, pick reasonable defaults of 500ms each */ + if (!time_on && !time_off) { + time_on = 500; + time_off = 500; + } + + period = time_on + time_off; + + /* If period not supported by hardware, default to someting sane. */ + if ((period < PCA9632_BLINK_PERIOD_MIN) || + (period > PCA9632_BLINK_PERIOD_MAX)) { + time_on = 500; + time_off = 500; + period = time_on + time_off; + } + + /* + * From manual: duty cycle = (GDC / 256) -> + * (time_on / period) = (GDC / 256) -> + * GDC = ((time_on * 256) / period) + */ + gdc = (time_on * 256) / period; + + /* + * From manual: period = ((GFRQ + 1) / 24) in seconds. + * So, period (in ms) = (((GFRQ + 1) / 24) * 1000) -> + * GFRQ = ((period * 24 / 1000) - 1) + */ + gfrq = (period * 24 / 1000) - 1; + + pca9633->cmd = BLINK_SET; + pca9633->gdc = gdc; + pca9633->gfrq = gfrq; + + /* + * Must use workqueue for the actual I/O since I2C operations + * can sleep. + */ + schedule_work(&pca9633->work); + + *delay_on = time_on; + *delay_off = time_off; + + return 0; +} + #if IS_ENABLED(CONFIG_OF) static struct pca9633_platform_data * pca9633_dt_init(struct i2c_client *client) @@ -140,6 +259,12 @@ pca9633_dt_init(struct i2c_client *client) else pdata->outdrv = PCA9633_OPEN_DRAIN; + /* default to software blinking unless hardware blinking is specified */ + if (of_property_read_bool(np, "nxp,hw-blink")) + pdata->blink_type = PCA9633_HW_BLINK; + else + pdata->blink_type = PCA9633_SW_BLINK; + return pdata; } @@ -206,7 +331,10 @@ static int pca9633_probe(struct i2c_client *client, pca9633[i].led_cdev.name = pca9633[i].name; pca9633[i].led_cdev.brightness_set = pca9633_led_set; - INIT_WORK(&pca9633[i].work, pca9633_led_work); + if (pdata && pdata->blink_type == PCA9633_HW_BLINK) + pca9633[i].led_cdev.blink_set = pca9633_blink_set; + + INIT_WORK(&pca9633[i].work, pca9633_work); err = led_classdev_register(&client->dev, &pca9633[i].led_cdev); if (err < 0) diff --git a/include/linux/platform_data/leds-pca9633.h b/include/linux/platform_data/leds-pca9633.h index c5bf29b..3c1037a 100644 --- a/include/linux/platform_data/leds-pca9633.h +++ b/include/linux/platform_data/leds-pca9633.h @@ -27,9 +27,15 @@ enum pca9633_outdrv { PCA9633_TOTEM_POLE, /* aka push-pull */ }; +enum pca9633_blink_type { + PCA9633_SW_BLINK, + PCA9633_HW_BLINK, +}; + struct pca9633_platform_data { struct led_platform_data leds; enum pca9633_outdrv outdrv; + enum pca9633_blink_type blink_type; }; #endif /* __LINUX_PCA9633_H*/ -- cgit v0.10.2 From 87aae1ea82f93f0f00cb955044ea1db3501cf233 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Tue, 30 Jul 2013 01:07:35 -0700 Subject: leds: use dev_get_platdata() Use the wrapper function for retrieving the platform data instead of accessing dev->platform_data directly. Signed-off-by: Jingoo Han Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-88pm860x.c b/drivers/leds/leds-88pm860x.c index 232b3ce..5f588c0 100644 --- a/drivers/leds/leds-88pm860x.c +++ b/drivers/leds/leds-88pm860x.c @@ -157,7 +157,7 @@ static int pm860x_led_dt_init(struct platform_device *pdev, static int pm860x_led_probe(struct platform_device *pdev) { struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); - struct pm860x_led_pdata *pdata = pdev->dev.platform_data; + struct pm860x_led_pdata *pdata = dev_get_platdata(&pdev->dev); struct pm860x_led *data; struct resource *res; int ret = 0; diff --git a/drivers/leds/leds-adp5520.c b/drivers/leds/leds-adp5520.c index e8072ab..7e311a1 100644 --- a/drivers/leds/leds-adp5520.c +++ b/drivers/leds/leds-adp5520.c @@ -87,7 +87,7 @@ static int adp5520_led_setup(struct adp5520_led *led) static int adp5520_led_prepare(struct platform_device *pdev) { - struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data; + struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); struct device *dev = pdev->dev.parent; int ret = 0; @@ -103,7 +103,7 @@ static int adp5520_led_prepare(struct platform_device *pdev) static int adp5520_led_probe(struct platform_device *pdev) { - struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data; + struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); struct adp5520_led *led, *led_dat; struct led_info *cur_led; int ret, i; @@ -185,7 +185,7 @@ err: static int adp5520_led_remove(struct platform_device *pdev) { - struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data; + struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); struct adp5520_led *led; int i; diff --git a/drivers/leds/leds-asic3.c b/drivers/leds/leds-asic3.c index cf9efe4..6de216a 100644 --- a/drivers/leds/leds-asic3.c +++ b/drivers/leds/leds-asic3.c @@ -94,7 +94,7 @@ static int blink_set(struct led_classdev *cdev, static int asic3_led_probe(struct platform_device *pdev) { - struct asic3_led *led = pdev->dev.platform_data; + struct asic3_led *led = dev_get_platdata(&pdev->dev); int ret; ret = mfd_cell_enable(pdev); @@ -127,7 +127,7 @@ out: static int asic3_led_remove(struct platform_device *pdev) { - struct asic3_led *led = pdev->dev.platform_data; + struct asic3_led *led = dev_get_platdata(&pdev->dev); led_classdev_unregister(led->cdev); diff --git a/drivers/leds/leds-atmel-pwm.c b/drivers/leds/leds-atmel-pwm.c index 90518f8..56cec8d 100644 --- a/drivers/leds/leds-atmel-pwm.c +++ b/drivers/leds/leds-atmel-pwm.c @@ -42,7 +42,7 @@ static int pwmled_probe(struct platform_device *pdev) int i; int status; - pdata = pdev->dev.platform_data; + pdata = dev_get_platdata(&pdev->dev); if (!pdata || pdata->num_leds < 1) return -ENODEV; @@ -119,7 +119,7 @@ static int pwmled_remove(struct platform_device *pdev) struct pwmled *leds; unsigned i; - pdata = pdev->dev.platform_data; + pdata = dev_get_platdata(&pdev->dev); leds = platform_get_drvdata(pdev); for (i = 0; i < pdata->num_leds; i++) { diff --git a/drivers/leds/leds-bd2802.c b/drivers/leds/leds-bd2802.c index 2db0423..fb5a347 100644 --- a/drivers/leds/leds-bd2802.c +++ b/drivers/leds/leds-bd2802.c @@ -684,7 +684,7 @@ static int bd2802_probe(struct i2c_client *client, } led->client = client; - pdata = led->pdata = client->dev.platform_data; + pdata = led->pdata = dev_get_platdata(&client->dev); i2c_set_clientdata(client, led); /* Configure RESET GPIO (L: RESET, H: RESET cancel) */ diff --git a/drivers/leds/leds-da903x.c b/drivers/leds/leds-da903x.c index c263a21..2a4b87f 100644 --- a/drivers/leds/leds-da903x.c +++ b/drivers/leds/leds-da903x.c @@ -93,7 +93,7 @@ static void da903x_led_set(struct led_classdev *led_cdev, static int da903x_led_probe(struct platform_device *pdev) { - struct led_info *pdata = pdev->dev.platform_data; + struct led_info *pdata = dev_get_platdata(&pdev->dev); struct da903x_led *led; int id, ret; diff --git a/drivers/leds/leds-da9052.c b/drivers/leds/leds-da9052.c index efec433..865d4fa 100644 --- a/drivers/leds/leds-da9052.c +++ b/drivers/leds/leds-da9052.c @@ -112,7 +112,7 @@ static int da9052_led_probe(struct platform_device *pdev) int i; da9052 = dev_get_drvdata(pdev->dev.parent); - pdata = da9052->dev->platform_data; + pdata = dev_get_platdata(da9052->dev); if (pdata == NULL) { dev_err(&pdev->dev, "No platform data\n"); goto err; @@ -185,7 +185,7 @@ static int da9052_led_remove(struct platform_device *pdev) int i; da9052 = dev_get_drvdata(pdev->dev.parent); - pdata = da9052->dev->platform_data; + pdata = dev_get_platdata(da9052->dev); pled = pdata->pled; for (i = 0; i < pled->num_leds; i++) { diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 84d74c37..e8b01e5 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -233,7 +233,7 @@ static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev) static int gpio_led_probe(struct platform_device *pdev) { - struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev); struct gpio_leds_priv *priv; int i, ret = 0; diff --git a/drivers/leds/leds-lm3530.c b/drivers/leds/leds-lm3530.c index a036a19..652368c 100644 --- a/drivers/leds/leds-lm3530.c +++ b/drivers/leds/leds-lm3530.c @@ -403,7 +403,7 @@ static DEVICE_ATTR(mode, 0644, lm3530_mode_get, lm3530_mode_set); static int lm3530_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct lm3530_platform_data *pdata = client->dev.platform_data; + struct lm3530_platform_data *pdata = dev_get_platdata(&client->dev); struct lm3530_data *drvdata; int err = 0; diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c index bbf24d0..027ede7 100644 --- a/drivers/leds/leds-lm3533.c +++ b/drivers/leds/leds-lm3533.c @@ -671,7 +671,7 @@ static int lm3533_led_probe(struct platform_device *pdev) if (!lm3533) return -EINVAL; - pdata = pdev->dev.platform_data; + pdata = dev_get_platdata(&pdev->dev); if (!pdata) { dev_err(&pdev->dev, "no platform data\n"); return -EINVAL; diff --git a/drivers/leds/leds-lm355x.c b/drivers/leds/leds-lm355x.c index d81a8e7..591eb5e 100644 --- a/drivers/leds/leds-lm355x.c +++ b/drivers/leds/leds-lm355x.c @@ -423,7 +423,7 @@ static const struct regmap_config lm355x_regmap = { static int lm355x_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct lm355x_platform_data *pdata = client->dev.platform_data; + struct lm355x_platform_data *pdata = dev_get_platdata(&client->dev); struct lm355x_chip_data *chip; int err; diff --git a/drivers/leds/leds-lm3642.c b/drivers/leds/leds-lm3642.c index f361bbe..ceb6b3c 100644 --- a/drivers/leds/leds-lm3642.c +++ b/drivers/leds/leds-lm3642.c @@ -316,7 +316,7 @@ static const struct regmap_config lm3642_regmap = { static int lm3642_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct lm3642_platform_data *pdata = client->dev.platform_data; + struct lm3642_platform_data *pdata = dev_get_platdata(&client->dev); struct lm3642_chip_data *chip; int err; diff --git a/drivers/leds/leds-lp3944.c b/drivers/leds/leds-lp3944.c index 0c4386e..2b53d10 100644 --- a/drivers/leds/leds-lp3944.c +++ b/drivers/leds/leds-lp3944.c @@ -377,7 +377,8 @@ exit: static int lp3944_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct lp3944_platform_data *lp3944_pdata = client->dev.platform_data; + struct lp3944_platform_data *lp3944_pdata = + dev_get_platdata(&client->dev); struct lp3944_data *data; int err; @@ -413,7 +414,7 @@ static int lp3944_probe(struct i2c_client *client, static int lp3944_remove(struct i2c_client *client) { - struct lp3944_platform_data *pdata = client->dev.platform_data; + struct lp3944_platform_data *pdata = dev_get_platdata(&client->dev); struct lp3944_data *data = i2c_get_clientdata(client); int i; diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index 1392feb..9e28dd0 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -420,7 +420,7 @@ static int lp5521_probe(struct i2c_client *client, struct lp55xx_platform_data *pdata; struct device_node *np = client->dev.of_node; - if (!client->dev.platform_data) { + if (!dev_get_platdata(&client->dev)) { if (np) { ret = lp55xx_of_populate_pdata(&client->dev, np); if (ret < 0) @@ -430,7 +430,7 @@ static int lp5521_probe(struct i2c_client *client, return -EINVAL; } } - pdata = client->dev.platform_data; + pdata = dev_get_platdata(&client->dev); chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c index 3979428..72c10e2 100644 --- a/drivers/leds/leds-lp5523.c +++ b/drivers/leds/leds-lp5523.c @@ -432,7 +432,7 @@ static int lp5523_probe(struct i2c_client *client, struct lp55xx_platform_data *pdata; struct device_node *np = client->dev.of_node; - if (!client->dev.platform_data) { + if (!dev_get_platdata(&client->dev)) { if (np) { ret = lp55xx_of_populate_pdata(&client->dev, np); if (ret < 0) @@ -442,7 +442,7 @@ static int lp5523_probe(struct i2c_client *client, return -EINVAL; } } - pdata = client->dev.platform_data; + pdata = dev_get_platdata(&client->dev); chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) diff --git a/drivers/leds/leds-lp5562.c b/drivers/leds/leds-lp5562.c index cbd856d..a2c7398 100644 --- a/drivers/leds/leds-lp5562.c +++ b/drivers/leds/leds-lp5562.c @@ -518,7 +518,7 @@ static int lp5562_probe(struct i2c_client *client, struct lp55xx_platform_data *pdata; struct device_node *np = client->dev.of_node; - if (!client->dev.platform_data) { + if (!dev_get_platdata(&client->dev)) { if (np) { ret = lp55xx_of_populate_pdata(&client->dev, np); if (ret < 0) @@ -528,7 +528,7 @@ static int lp5562_probe(struct i2c_client *client, return -EINVAL; } } - pdata = client->dev.platform_data; + pdata = dev_get_platdata(&client->dev); chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) diff --git a/drivers/leds/leds-lp8501.c b/drivers/leds/leds-lp8501.c index 4573b94..8d55a780 100644 --- a/drivers/leds/leds-lp8501.c +++ b/drivers/leds/leds-lp8501.c @@ -310,7 +310,7 @@ static int lp8501_probe(struct i2c_client *client, struct lp55xx_platform_data *pdata; struct device_node *np = client->dev.of_node; - if (!client->dev.platform_data) { + if (!dev_get_platdata(&client->dev)) { if (np) { ret = lp55xx_of_populate_pdata(&client->dev, np); if (ret < 0) @@ -320,7 +320,7 @@ static int lp8501_probe(struct i2c_client *client, return -EINVAL; } } - pdata = client->dev.platform_data; + pdata = dev_get_platdata(&client->dev); chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) diff --git a/drivers/leds/leds-lt3593.c b/drivers/leds/leds-lt3593.c index ca48a7d5..3417e5b 100644 --- a/drivers/leds/leds-lt3593.c +++ b/drivers/leds/leds-lt3593.c @@ -135,7 +135,7 @@ static void delete_lt3593_led(struct lt3593_led_data *led) static int lt3593_led_probe(struct platform_device *pdev) { - struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev); struct lt3593_led_data *leds_data; int i, ret = 0; @@ -169,7 +169,7 @@ err: static int lt3593_led_remove(struct platform_device *pdev) { int i; - struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev); struct lt3593_led_data *leds_data; leds_data = platform_get_drvdata(pdev); diff --git a/drivers/leds/leds-netxbig.c b/drivers/leds/leds-netxbig.c index c61c5eb..2f9f141 100644 --- a/drivers/leds/leds-netxbig.c +++ b/drivers/leds/leds-netxbig.c @@ -306,7 +306,7 @@ create_netxbig_led(struct platform_device *pdev, struct netxbig_led_data *led_dat, const struct netxbig_led *template) { - struct netxbig_led_platform_data *pdata = pdev->dev.platform_data; + struct netxbig_led_platform_data *pdata = dev_get_platdata(&pdev->dev); int ret; spin_lock_init(&led_dat->lock); @@ -354,7 +354,7 @@ create_netxbig_led(struct platform_device *pdev, static int netxbig_led_probe(struct platform_device *pdev) { - struct netxbig_led_platform_data *pdata = pdev->dev.platform_data; + struct netxbig_led_platform_data *pdata = dev_get_platdata(&pdev->dev); struct netxbig_led_data *leds_data; int i; int ret; @@ -391,7 +391,7 @@ err_free_leds: static int netxbig_led_remove(struct platform_device *pdev) { - struct netxbig_led_platform_data *pdata = pdev->dev.platform_data; + struct netxbig_led_platform_data *pdata = dev_get_platdata(&pdev->dev); struct netxbig_led_data *leds_data; int i; diff --git a/drivers/leds/leds-ns2.c b/drivers/leds/leds-ns2.c index e7df987..141f134 100644 --- a/drivers/leds/leds-ns2.c +++ b/drivers/leds/leds-ns2.c @@ -321,7 +321,7 @@ static inline int sizeof_ns2_led_priv(int num_leds) static int ns2_led_probe(struct platform_device *pdev) { - struct ns2_led_platform_data *pdata = pdev->dev.platform_data; + struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev); struct ns2_led_priv *priv; int i; int ret; diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index 0c597bd..4a0e786 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -446,7 +446,8 @@ static int pca9532_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct pca9532_data *data = i2c_get_clientdata(client); - struct pca9532_platform_data *pca9532_pdata = client->dev.platform_data; + struct pca9532_platform_data *pca9532_pdata = + dev_get_platdata(&client->dev); if (!pca9532_pdata) return -EIO; diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index edf485b..c3a08b6 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -267,7 +267,7 @@ static int pca955x_probe(struct i2c_client *client, chip = &pca955x_chipdefs[id->driver_data]; adapter = to_i2c_adapter(client->dev.parent); - pdata = client->dev.platform_data; + pdata = dev_get_platdata(&client->dev); /* Make sure the slave address / chip type combo given is possible */ if ((client->addr & ~((1 << chip->slv_addr_shift) - 1)) != diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c index 77b3f24..ecd1449 100644 --- a/drivers/leds/leds-pca9633.c +++ b/drivers/leds/leds-pca9633.c @@ -287,7 +287,7 @@ static int pca9633_probe(struct i2c_client *client, struct pca9633_platform_data *pdata; int i, err; - pdata = client->dev.platform_data; + pdata = dev_get_platdata(&client->dev); if (!pdata) { pdata = pca9633_dt_init(client); diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index faf52c0..bb6f948 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -147,7 +147,7 @@ err: static int led_pwm_probe(struct platform_device *pdev) { - struct led_pwm_platform_data *pdata = pdev->dev.platform_data; + struct led_pwm_platform_data *pdata = dev_get_platdata(&pdev->dev); struct led_pwm_priv *priv; int i, ret = 0; diff --git a/drivers/leds/leds-regulator.c b/drivers/leds/leds-regulator.c index 4253a9b..358430d 100644 --- a/drivers/leds/leds-regulator.c +++ b/drivers/leds/leds-regulator.c @@ -142,7 +142,8 @@ static void regulator_led_brightness_set(struct led_classdev *led_cdev, static int regulator_led_probe(struct platform_device *pdev) { - struct led_regulator_platform_data *pdata = pdev->dev.platform_data; + struct led_regulator_platform_data *pdata = + dev_get_platdata(&pdev->dev); struct regulator_led *led; struct regulator *vcc; int ret = 0; diff --git a/drivers/leds/leds-renesas-tpu.c b/drivers/leds/leds-renesas-tpu.c index adebf49..397b92a 100644 --- a/drivers/leds/leds-renesas-tpu.c +++ b/drivers/leds/leds-renesas-tpu.c @@ -65,7 +65,7 @@ static DEFINE_SPINLOCK(r_tpu_lock); static inline u16 r_tpu_read(struct r_tpu_priv *p, int reg_nr) { - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; + struct led_renesas_tpu_config *cfg = dev_get_platdata(&p->pdev->dev); void __iomem *base = p->mapbase; unsigned long offs = reg_nr << 2; @@ -77,7 +77,7 @@ static inline u16 r_tpu_read(struct r_tpu_priv *p, int reg_nr) static inline void r_tpu_write(struct r_tpu_priv *p, int reg_nr, u16 value) { - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; + struct led_renesas_tpu_config *cfg = dev_get_platdata(&p->pdev->dev); void __iomem *base = p->mapbase; unsigned long offs = reg_nr << 2; @@ -91,7 +91,7 @@ static inline void r_tpu_write(struct r_tpu_priv *p, int reg_nr, u16 value) static void r_tpu_start_stop_ch(struct r_tpu_priv *p, int start) { - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; + struct led_renesas_tpu_config *cfg = dev_get_platdata(&p->pdev->dev); unsigned long flags; u16 value; @@ -110,7 +110,7 @@ static void r_tpu_start_stop_ch(struct r_tpu_priv *p, int start) static int r_tpu_enable(struct r_tpu_priv *p, enum led_brightness brightness) { - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; + struct led_renesas_tpu_config *cfg = dev_get_platdata(&p->pdev->dev); int prescaler[] = { 1, 4, 16, 64 }; int k, ret; unsigned long rate, tmp; @@ -190,7 +190,7 @@ static void r_tpu_disable(struct r_tpu_priv *p) static void r_tpu_set_pin(struct r_tpu_priv *p, enum r_tpu_pin new_state, enum led_brightness brightness) { - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; + struct led_renesas_tpu_config *cfg = dev_get_platdata(&p->pdev->dev); if (p->pin_state == new_state) { if (p->pin_state == R_TPU_PIN_GPIO) @@ -241,7 +241,7 @@ static void r_tpu_set_brightness(struct led_classdev *ldev, static int r_tpu_probe(struct platform_device *pdev) { - struct led_renesas_tpu_config *cfg = pdev->dev.platform_data; + struct led_renesas_tpu_config *cfg = dev_get_platdata(&pdev->dev); struct r_tpu_priv *p; struct resource *res; int ret; diff --git a/drivers/leds/leds-s3c24xx.c b/drivers/leds/leds-s3c24xx.c index e1a0df6..76483fb 100644 --- a/drivers/leds/leds-s3c24xx.c +++ b/drivers/leds/leds-s3c24xx.c @@ -71,7 +71,7 @@ static int s3c24xx_led_remove(struct platform_device *dev) static int s3c24xx_led_probe(struct platform_device *dev) { - struct s3c24xx_led_platdata *pdata = dev->dev.platform_data; + struct s3c24xx_led_platdata *pdata = dev_get_platdata(&dev->dev); struct s3c24xx_gpio_led *led; int ret; diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c index 98fe021..8cc304f 100644 --- a/drivers/leds/leds-tca6507.c +++ b/drivers/leds/leds-tca6507.c @@ -737,7 +737,7 @@ static int tca6507_probe(struct i2c_client *client, int i = 0; adapter = to_i2c_adapter(client->dev.parent); - pdata = client->dev.platform_data; + pdata = dev_get_platdata(&client->dev); if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) return -EIO; diff --git a/drivers/leds/leds-wm831x-status.c b/drivers/leds/leds-wm831x-status.c index 120815a..c32dad4 100644 --- a/drivers/leds/leds-wm831x-status.c +++ b/drivers/leds/leds-wm831x-status.c @@ -246,8 +246,8 @@ static int wm831x_status_probe(struct platform_device *pdev) drvdata->wm831x = wm831x; drvdata->reg = res->start; - if (wm831x->dev->platform_data) - chip_pdata = wm831x->dev->platform_data; + if (dev_get_platdata(wm831x->dev)) + chip_pdata = dev_get_platdata(wm831x->dev); else chip_pdata = NULL; diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c index 8a181d5..3f75fd2 100644 --- a/drivers/leds/leds-wm8350.c +++ b/drivers/leds/leds-wm8350.c @@ -203,7 +203,7 @@ static int wm8350_led_probe(struct platform_device *pdev) { struct regulator *isink, *dcdc; struct wm8350_led *led; - struct wm8350_led_platform_data *pdata = pdev->dev.platform_data; + struct wm8350_led_platform_data *pdata = dev_get_platdata(&pdev->dev); int i; if (pdata == NULL) { -- cgit v0.10.2 From 2e87c09209cce10a0744eb28c25e93fe9fdc3e81 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Thu, 1 Aug 2013 19:49:45 -0700 Subject: leds: leds-ss4200: Staticize nasgpio_led_get_attr() nasgpio_led_get_attr() is used only in this file. Fix the following sparse warning: drivers/leds/leds-ss4200.c:200:5: warning: symbol 'nasgpio_led_get_attr' was not declared. Should it be static? Signed-off-by: Jingoo Han Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-ss4200.c b/drivers/leds/leds-ss4200.c index 64e204e..51bb0ee 100644 --- a/drivers/leds/leds-ss4200.c +++ b/drivers/leds/leds-ss4200.c @@ -197,7 +197,7 @@ static void nasgpio_led_set_attr(struct led_classdev *led_cdev, spin_unlock(&nasgpio_gpio_lock); } -u32 nasgpio_led_get_attr(struct led_classdev *led_cdev, u32 port) +static u32 nasgpio_led_get_attr(struct led_classdev *led_cdev, u32 port) { struct nasgpio_led *led = led_classdev_to_nasgpio_led(led_cdev); u32 gpio_in; -- cgit v0.10.2 From 29ab311c44fd9de932bceddc6b2587600abd1c49 Mon Sep 17 00:00:00 2001 From: Antonio Ospite Date: Fri, 2 Aug 2013 11:23:01 -0700 Subject: leds: leds-lp3944, fix "sparse" warning "mixing different enum types" Fix a warning from "sparse": drivers/leds/leds-lp3944.c:292:23: warning: mixing different enum types drivers/leds/leds-lp3944.c:292:23: int enum led_brightness versus drivers/leds/leds-lp3944.c:292:23: int enum lp3944_status Keeping track of LP3944_LED_STATUS_OFF and LP3944_LED_STATUS_ON only in lp3944_led_set_brightness() is OK, as the handling of DIM (blinking) mode[s] is in lp3944_led_set_blink(). Reported-by: Dan Carpenter Signed-off-by: Antonio Ospite Reviewed-by: Jingoo Han Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lp3944.c b/drivers/leds/leds-lp3944.c index 2b53d10..8e1abdc 100644 --- a/drivers/leds/leds-lp3944.c +++ b/drivers/leds/leds-lp3944.c @@ -289,7 +289,7 @@ static void lp3944_led_set_brightness(struct led_classdev *led_cdev, dev_dbg(&led->client->dev, "%s: %s, %d\n", __func__, led_cdev->name, brightness); - led->status = brightness; + led->status = !!brightness; schedule_work(&led->work); } -- cgit v0.10.2 From d6626d1052a2180c3270b017f9f5b8c4ac14255c Mon Sep 17 00:00:00 2001 From: Simon Guinot Date: Fri, 2 Aug 2013 07:46:38 -0700 Subject: leds: leds-ns2: depends on ARCH_KIRKWOOD With the DT conversion, the board Kconfig symbols MACH_ are going to be removed. In order to prepare this removal, this patch replaces alls the machines dependencies for leds-ns2 by ARCH_KIRKWOOD. Signed-off-by: Simon Guinot Acked-by: Jason Cooper Signed-off-by: Bryan Wu diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 77329ce..6f4d61a 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -410,10 +410,7 @@ config LEDS_MC13783 config LEDS_NS2 tristate "LED support for Network Space v2 GPIO LEDs" depends on LEDS_CLASS - depends on MACH_NETSPACE_V2 || MACH_INETSPACE_V2 || \ - MACH_NETSPACE_MAX_V2 || MACH_D2NET_V2 || \ - MACH_NETSPACE_V2_DT || MACH_INETSPACE_V2_DT || \ - MACH_NETSPACE_MAX_V2_DT || MACH_NETSPACE_MINI_V2_DT + depends on ARCH_KIRKWOOD default y help This option enable support for the dual-GPIO LED found on the -- cgit v0.10.2 From d6d240bf526e64304d1f0a712a3c9ce69a3f3aa6 Mon Sep 17 00:00:00 2001 From: Simon Guinot Date: Fri, 2 Aug 2013 14:34:34 -0700 Subject: leds: leds-netxbig: depends on ARCH_KIRKWOOD With the DT conversion, the board Kconfig symbols MACH_ are going to be removed. In order to prepare this removal, this patch replaces alls the machines dependencies for leds-netxbig by ARCH_KIRKWOOD. Signed-off-by: Simon Guinot Acked-by: Jason Cooper Signed-off-by: Bryan Wu diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 6f4d61a..f2c738d 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -419,8 +419,8 @@ config LEDS_NS2 config LEDS_NETXBIG tristate "LED support for Big Network series LEDs" - depends on MACH_NET2BIG_V2 || MACH_NET5BIG_V2 depends on LEDS_CLASS + depends on ARCH_KIRKWOOD default y help This option enable support for LEDs found on the LaCie 2Big -- cgit v0.10.2 From 939086ea552743144bbcb759f19c7e354f720786 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Wed, 7 Aug 2013 02:30:42 -0700 Subject: leds: clevo-mail: Fix incorrect placement of __initdata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit __initdata should be placed between the variable name and equal sign for the variable to be placed in the intended section. Signed-off-by: Sachin Kamat Cc: Márton Németh Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-clevo-mail.c b/drivers/leds/leds-clevo-mail.c index 6a8405d..d93e245 100644 --- a/drivers/leds/leds-clevo-mail.c +++ b/drivers/leds/leds-clevo-mail.c @@ -40,7 +40,7 @@ static int __init clevo_mail_led_dmi_callback(const struct dmi_system_id *id) * detected as working, but in reality it is not) as low as * possible. */ -static struct dmi_system_id __initdata clevo_mail_led_dmi_table[] = { +static struct dmi_system_id clevo_mail_led_dmi_table[] __initdata = { { .callback = clevo_mail_led_dmi_callback, .ident = "Clevo D410J", -- cgit v0.10.2 From b06cf2d7ec51c12eb5f9d191d461bd9c08f2c107 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Wed, 7 Aug 2013 02:30:43 -0700 Subject: leds: ss4200: Fix incorrect placement of __initdata __initdata should be placed between the variable name and equal sign for the variable to be placed in the intended section. Signed-off-by: Sachin Kamat Cc: Dave Hansen Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-ss4200.c b/drivers/leds/leds-ss4200.c index 51bb0ee..5b8f938 100644 --- a/drivers/leds/leds-ss4200.c +++ b/drivers/leds/leds-ss4200.c @@ -91,7 +91,7 @@ MODULE_PARM_DESC(nodetect, "Skip DMI-based hardware detection"); * detected as working, but in reality it is not) as low as * possible. */ -static struct dmi_system_id __initdata nas_led_whitelist[] = { +static struct dmi_system_id nas_led_whitelist[] __initdata = { { .callback = ss4200_led_dmi_callback, .ident = "Intel SS4200-E", -- cgit v0.10.2 From e5862b9a521fcb2d5f95898b506fbc788762fca5 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Wed, 7 Aug 2013 22:07:59 -0700 Subject: Documentation: leds: Fix a typo __initdata should be placed between the variable name and equal sign for the variable to be placed in the intended section. Fix the example. Signed-off-by: Sachin Kamat Signed-off-by: Bryan Wu diff --git a/Documentation/leds/leds-lm3556.txt b/Documentation/leds/leds-lm3556.txt index d9eb91b..62278e8 100644 --- a/Documentation/leds/leds-lm3556.txt +++ b/Documentation/leds/leds-lm3556.txt @@ -71,7 +71,7 @@ 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[] = { + static struct i2c_board_info board_i2c_ch4[] __initdata = { { I2C_BOARD_INFO(LM3556_NAME, 0x63), .platform_data = &lm3556_pdata, diff --git a/Documentation/leds/leds-lp3944.txt b/Documentation/leds/leds-lp3944.txt index c6eda18..e88ac3b 100644 --- a/Documentation/leds/leds-lp3944.txt +++ b/Documentation/leds/leds-lp3944.txt @@ -37,7 +37,7 @@ registered using the i2c_board_info mechanism. To register the chip at address 0x60 on adapter 0, set the platform data according to include/linux/leds-lp3944.h, set the i2c board info: - static struct i2c_board_info __initdata a910_i2c_board_info[] = { + static struct i2c_board_info a910_i2c_board_info[] __initdata = { { I2C_BOARD_INFO("lp3944", 0x60), .platform_data = &a910_lp3944_leds, -- cgit v0.10.2 From 6841a91dc5cfcb02b99413fa4199d9a8ce5ae770 Mon Sep 17 00:00:00 2001 From: Milo Kim Date: Thu, 8 Aug 2013 12:45:41 +0900 Subject: leds: lp55xx: add common data structure for program LP55xx family devices have internal three program engines which are used for loading LED patterns. To maintain legacy device attributes, specific data structure is used, 'mode' and 'led_mux'. The mode is used for showing/storing current engine mode such like disabled, load and run. Then led_mux is used for showing/storing current output LED selection. This is only for LP5523/55231. Signed-off-by: Milo Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lp55xx-common.h b/drivers/leds/leds-lp55xx-common.h index dbbf86d..04c1d4f 100644 --- a/drivers/leds/leds-lp55xx-common.h +++ b/drivers/leds/leds-lp55xx-common.h @@ -20,6 +20,13 @@ enum lp55xx_engine_index { LP55XX_ENGINE_1, LP55XX_ENGINE_2, LP55XX_ENGINE_3, + LP55XX_ENGINE_MAX = LP55XX_ENGINE_3, +}; + +enum lp55xx_engine_mode { + LP55XX_ENGINE_DISABLED, + LP55XX_ENGINE_LOAD, + LP55XX_ENGINE_RUN, }; struct lp55xx_led; @@ -72,6 +79,16 @@ struct lp55xx_device_config { }; /* + * struct lp55xx_engine + * @mode : Engine mode + * @led_mux : Mux bits for LED selection. Only used in LP5523 + */ +struct lp55xx_engine { + enum lp55xx_engine_mode mode; + u16 led_mux; +}; + +/* * struct lp55xx_chip * @cl : I2C communication for access registers * @pdata : Platform specific data @@ -79,6 +96,7 @@ struct lp55xx_device_config { * @num_leds : Number of registered LEDs * @cfg : Device specific configuration data * @engine_idx : Selected engine number + * @engines : Engine structure for the device attribute R/W interface * @fw : Firmware data for running a LED pattern */ struct lp55xx_chip { @@ -89,6 +107,7 @@ struct lp55xx_chip { int num_leds; struct lp55xx_device_config *cfg; enum lp55xx_engine_index engine_idx; + struct lp55xx_engine engines[LP55XX_ENGINE_MAX]; const struct firmware *fw; }; -- cgit v0.10.2 From 36030978f51b596c30188187ffcc8a436cce16f0 Mon Sep 17 00:00:00 2001 From: Milo Kim Date: Thu, 8 Aug 2013 12:46:43 +0900 Subject: leds: lp55xx: add common macros for device attributes This patch provides common macros for LP5521 and LP5523 device attributes and functions. (Device attributes) LP5521: 'mode', 'load' and 'selftest' LP5523: 'mode', 'load', 'leds' and 'selftest' (Permissions) mode: R/W load: Write-only leds: R/W selftest: Read-only Couple of lines are duplicate, so use these macros for adding device attributes in LP5521 and LP5523 drivers. Signed-off-by: Milo Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lp55xx-common.h b/drivers/leds/leds-lp55xx-common.h index 04c1d4f..cceab48 100644 --- a/drivers/leds/leds-lp55xx-common.h +++ b/drivers/leds/leds-lp55xx-common.h @@ -29,6 +29,53 @@ enum lp55xx_engine_mode { LP55XX_ENGINE_RUN, }; +#define LP55XX_DEV_ATTR_RW(name, show, store) \ + DEVICE_ATTR(name, S_IRUGO | S_IWUSR, show, store) +#define LP55XX_DEV_ATTR_RO(name, show) \ + DEVICE_ATTR(name, S_IRUGO, show, NULL) +#define LP55XX_DEV_ATTR_WO(name, store) \ + DEVICE_ATTR(name, S_IWUSR, NULL, store) + +#define show_mode(nr) \ +static ssize_t show_engine##nr##_mode(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + return show_engine_mode(dev, attr, buf, nr); \ +} + +#define store_mode(nr) \ +static ssize_t store_engine##nr##_mode(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t len) \ +{ \ + return store_engine_mode(dev, attr, buf, len, nr); \ +} + +#define show_leds(nr) \ +static ssize_t show_engine##nr##_leds(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + return show_engine_leds(dev, attr, buf, nr); \ +} + +#define store_leds(nr) \ +static ssize_t store_engine##nr##_leds(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t len) \ +{ \ + return store_engine_leds(dev, attr, buf, len, nr); \ +} + +#define store_load(nr) \ +static ssize_t store_engine##nr##_load(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t len) \ +{ \ + return store_engine_load(dev, attr, buf, len, nr); \ +} + struct lp55xx_led; struct lp55xx_chip; -- cgit v0.10.2 From c0e5e9b542d134eb43ea4ebc83446e6f2f63089a Mon Sep 17 00:00:00 2001 From: Milo Kim Date: Thu, 8 Aug 2013 13:41:40 +0900 Subject: leds: lp5521: restore legacy device attributes git commit 9ce7cb170f97f83a78dc948bf7d25690f15e1328 may cause an application confict, engineN_mode and engineN_load. This interface should be maintained for compatibility. Restored device attributes are 'engineN_mode' and 'engineN_load'. A 'selftest' attribute macro is replaced with LP55xx common macro. Use a mutex in lp5521_update_program_memory() : This function is called when an user-application writes a 'engineN_load' file or pattern data is loaded from generic firmware interface. So, writing program memory should be protected. If an error occurs on accessing this area, just it returns as -EINVAL quickly. This error code is exact same as old driver function, lp5521_do_store_load() because it should be kept for an user-application compatibility. Even the driver is changed, we can use the application without re-compiling sources. 'led_pattern' attribute is not included : engineN_mode and _load were created for custom user-application. 'led_pattern' is an exception. I added this attribute not for custom application but for simple test. Now it is used only in LP5562 driver, not LP5521. Signed-off-by: Milo Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index 9e28dd0..c00f922 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -251,10 +251,20 @@ static int lp5521_update_program_memory(struct lp55xx_chip *chip, goto err; program_size = i; - for (i = 0; i < program_size; i++) - lp55xx_write(chip, addr[idx] + i, pattern[i]); - return 0; + mutex_lock(&chip->lock); + + for (i = 0; i < program_size; i++) { + ret = lp55xx_write(chip, addr[idx] + i, pattern[i]); + if (ret) { + mutex_unlock(&chip->lock); + return -EINVAL; + } + } + + mutex_unlock(&chip->lock); + + return size; err: dev_err(&chip->cl->dev, "wrong pattern format\n"); @@ -365,6 +375,80 @@ static void lp5521_led_brightness_work(struct work_struct *work) mutex_unlock(&chip->lock); } +static ssize_t show_engine_mode(struct device *dev, + struct device_attribute *attr, + char *buf, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode; + + switch (mode) { + case LP55XX_ENGINE_RUN: + return sprintf(buf, "run\n"); + case LP55XX_ENGINE_LOAD: + return sprintf(buf, "load\n"); + case LP55XX_ENGINE_DISABLED: + default: + return sprintf(buf, "disabled\n"); + } +} +show_mode(1) +show_mode(2) +show_mode(3) + +static ssize_t store_engine_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + struct lp55xx_engine *engine = &chip->engines[nr - 1]; + + mutex_lock(&chip->lock); + + chip->engine_idx = nr; + + if (!strncmp(buf, "run", 3)) { + lp5521_run_engine(chip, true); + engine->mode = LP55XX_ENGINE_RUN; + } else if (!strncmp(buf, "load", 4)) { + lp5521_stop_engine(chip); + lp5521_load_engine(chip); + engine->mode = LP55XX_ENGINE_LOAD; + } else if (!strncmp(buf, "disabled", 8)) { + lp5521_stop_engine(chip); + engine->mode = LP55XX_ENGINE_DISABLED; + } + + mutex_unlock(&chip->lock); + + return len; +} +store_mode(1) +store_mode(2) +store_mode(3) + +static ssize_t store_engine_load(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + + mutex_lock(&chip->lock); + + chip->engine_idx = nr; + lp5521_load_engine(chip); + + mutex_unlock(&chip->lock); + + return lp5521_update_program_memory(chip, buf, len); +} +store_load(1) +store_load(2) +store_load(3) + static ssize_t lp5521_selftest(struct device *dev, struct device_attribute *attr, char *buf) @@ -381,9 +465,21 @@ static ssize_t lp5521_selftest(struct device *dev, } /* device attributes */ -static DEVICE_ATTR(selftest, S_IRUGO, lp5521_selftest, NULL); +static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode); +static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode); +static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode); +static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load); +static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load); +static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load); +static LP55XX_DEV_ATTR_RO(selftest, lp5521_selftest); static struct attribute *lp5521_attributes[] = { + &dev_attr_engine1_mode.attr, + &dev_attr_engine2_mode.attr, + &dev_attr_engine3_mode.attr, + &dev_attr_engine1_load.attr, + &dev_attr_engine2_load.attr, + &dev_attr_engine3_load.attr, &dev_attr_selftest.attr, NULL }; -- cgit v0.10.2 From 1eca0b3ab8d04e2b1749b28cbcafbba6f6e74225 Mon Sep 17 00:00:00 2001 From: Milo Kim Date: Thu, 8 Aug 2013 13:45:45 +0900 Subject: leds: lp5521: remove unnecessary writing commands This patch reduces the number of programming commands. (Count of sending commands) Old code: 32 + program size (32 counts for clearing program memory) New code: 32 Pattern buffer is initialized to 0 in this function. Just update new program data and remaining buffers are filled with 0. So it's needless to clear whole area. Signed-off-by: Milo Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index c00f922..0518835 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -220,17 +220,11 @@ static int lp5521_update_program_memory(struct lp55xx_chip *chip, }; 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 < LP5521_PROGRAM_LENGTH; i++) - lp55xx_write(chip, addr[idx] + i, 0); + int offset = 0; + int i = 0; - i = 0; while ((offset < size - 1) && (i < LP5521_PROGRAM_LENGTH)) { /* separate sscanfs because length is working only for %s */ ret = sscanf(data + offset, "%2s%n ", c, &nrchars); @@ -250,11 +244,9 @@ static int lp5521_update_program_memory(struct lp55xx_chip *chip, if (i % 2) goto err; - program_size = i; - mutex_lock(&chip->lock); - for (i = 0; i < program_size; i++) { + for (i = 0; i < LP5521_PROGRAM_LENGTH; i++) { ret = lp55xx_write(chip, addr[idx] + i, pattern[i]); if (ret) { mutex_unlock(&chip->lock); -- cgit v0.10.2 From b9e1730b295087f5561477a184878783fd3d96d2 Mon Sep 17 00:00:00 2001 From: Milo Kim Date: Thu, 8 Aug 2013 13:56:01 +0900 Subject: leds: lp5523: make separate API for loading engine lp5523_load_engine() It is called whenever the operation mode is changed to 'load'. It is used for simple operation mode change. It will be used when engine mode and LED selection is updated in later patch. lp5523_load_engine_and_select_page() Change the operation mode to 'load' and select program page number. This is used for programming a LED pattern at a time. So load_engine() is replaced with new API, load_engine_and_select_page() in lp5523_firmware_loaded(). Signed-off-by: Milo Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c index 72c10e2..b509480 100644 --- a/drivers/leds/leds-lp5523.c +++ b/drivers/leds/leds-lp5523.c @@ -152,15 +152,21 @@ static void lp5523_load_engine(struct lp55xx_chip *chip) [LP55XX_ENGINE_3] = LP5523_LOAD_ENG3, }; + lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], val[idx]); + + lp5523_wait_opmode_done(); +} + +static void lp5523_load_engine_and_select_page(struct lp55xx_chip *chip) +{ + enum lp55xx_engine_index idx = chip->engine_idx; u8 page_sel[] = { [LP55XX_ENGINE_1] = LP5523_PAGE_ENG1, [LP55XX_ENGINE_2] = LP5523_PAGE_ENG2, [LP55XX_ENGINE_3] = LP5523_PAGE_ENG3, }; - lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], val[idx]); - - lp5523_wait_opmode_done(); + lp5523_load_engine(chip); lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, page_sel[idx]); } @@ -290,7 +296,7 @@ static void lp5523_firmware_loaded(struct lp55xx_chip *chip) * 2) write firmware data into program memory */ - lp5523_load_engine(chip); + lp5523_load_engine_and_select_page(chip); lp5523_update_program_memory(chip, fw->data, fw->size); } -- cgit v0.10.2 From 224604389a5295360ef36b50ca5a51806a235fcf Mon Sep 17 00:00:00 2001 From: Milo Kim Date: Thu, 8 Aug 2013 13:57:35 +0900 Subject: leds: lp5523: LED MUX configuration on initializing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LED MUX start and stop address should be updated in the program memory on LP5523 initialization. LED pattern doesn't work without additional MUX address configuration. This handling is done by new function, lp5523_init_program_engine(). Eventually, it's called during device initialization, lp5523_post_init_device(). This is a conflict after git commit 632418bf65503405df3f9a6a1616f5a95f91db85 (leds-lp5523: clean up lp5523_configure()). So it should be fixed. Cc: Pali Rohár Signed-off-by: Milo Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c index b509480..29c8b19 100644 --- a/drivers/leds/leds-lp5523.c +++ b/drivers/leds/leds-lp5523.c @@ -49,6 +49,9 @@ #define LP5523_REG_RESET 0x3D #define LP5523_REG_LED_TEST_CTRL 0x41 #define LP5523_REG_LED_TEST_ADC 0x42 +#define LP5523_REG_CH1_PROG_START 0x4C +#define LP5523_REG_CH2_PROG_START 0x4D +#define LP5523_REG_CH3_PROG_START 0x4E #define LP5523_REG_PROG_PAGE_SEL 0x4F #define LP5523_REG_PROG_MEM 0x50 @@ -65,6 +68,7 @@ #define LP5523_RESET 0xFF #define LP5523_ADC_SHORTCIRC_LIM 80 #define LP5523_EXT_CLK_USED 0x08 +#define LP5523_ENG_STATUS_MASK 0x07 /* Memory Page Selection */ #define LP5523_PAGE_ENG1 0 @@ -99,6 +103,8 @@ enum lp5523_chip_id { LP55231, }; +static int lp5523_init_program_engine(struct lp55xx_chip *chip); + static inline void lp5523_wait_opmode_done(void) { usleep_range(1000, 2000); @@ -134,7 +140,11 @@ static int lp5523_post_init_device(struct lp55xx_chip *chip) if (ret) return ret; - return lp55xx_write(chip, LP5523_REG_ENABLE_LEDS_LSB, 0xff); + ret = lp55xx_write(chip, LP5523_REG_ENABLE_LEDS_LSB, 0xff); + if (ret) + return ret; + + return lp5523_init_program_engine(chip); } static void lp5523_load_engine(struct lp55xx_chip *chip) @@ -233,6 +243,64 @@ static void lp5523_run_engine(struct lp55xx_chip *chip, bool start) lp55xx_update_bits(chip, LP5523_REG_ENABLE, LP5523_EXEC_M, exec); } +static int lp5523_init_program_engine(struct lp55xx_chip *chip) +{ + int i; + int j; + int ret; + u8 status; + /* one pattern per engine setting LED MUX start and stop addresses */ + static const u8 pattern[][LP5523_PROGRAM_LENGTH] = { + { 0x9c, 0x30, 0x9c, 0xb0, 0x9d, 0x80, 0xd8, 0x00, 0}, + { 0x9c, 0x40, 0x9c, 0xc0, 0x9d, 0x80, 0xd8, 0x00, 0}, + { 0x9c, 0x50, 0x9c, 0xd0, 0x9d, 0x80, 0xd8, 0x00, 0}, + }; + + /* hardcode 32 bytes of memory for each engine from program memory */ + ret = lp55xx_write(chip, LP5523_REG_CH1_PROG_START, 0x00); + if (ret) + return ret; + + ret = lp55xx_write(chip, LP5523_REG_CH2_PROG_START, 0x10); + if (ret) + return ret; + + ret = lp55xx_write(chip, LP5523_REG_CH3_PROG_START, 0x20); + if (ret) + return ret; + + /* write LED MUX address space for each engine */ + for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) { + chip->engine_idx = i; + lp5523_load_engine_and_select_page(chip); + + for (j = 0; j < LP5523_PROGRAM_LENGTH; j++) { + ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + j, + pattern[i - 1][j]); + if (ret) + goto out; + } + } + + lp5523_run_engine(chip, true); + + /* Let the programs run for couple of ms and check the engine status */ + usleep_range(3000, 6000); + lp55xx_read(chip, LP5523_REG_STATUS, &status); + status &= LP5523_ENG_STATUS_MASK; + + if (status != LP5523_ENG_STATUS_MASK) { + dev_err(&chip->cl->dev, + "cound not configure LED engine, status = 0x%.2x\n", + status); + ret = -1; + } + +out: + lp5523_stop_engine(chip); + return ret; +} + static int lp5523_update_program_memory(struct lp55xx_chip *chip, const u8 *data, size_t size) { -- cgit v0.10.2 From 45e611bfbe18d854498c65d60703841e4a4c3c3a Mon Sep 17 00:00:00 2001 From: Milo Kim Date: Thu, 8 Aug 2013 14:02:29 +0900 Subject: leds: lp5523: restore legacy device attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git commit db6eaf8388a413a5ee1b4547ce78506b9c6456b0 (leds-lp5523: use generic firmware interface) causes an application conflict. This interface should be maintained for compatibility. Restored device attributes are 'engineN_mode', 'engineN_load' and 'engineN_leds'. (N = 1, 2 or 3) A 'selftest' attribute macro is replaced with LP55xx common macro. Those are accessed when a LED pattern is run by an application. Use a mutex in lp5523_update_program_memory() : This function is called when an user-application writes a 'engineN_load' file or pattern data is loaded from generic firmware interface. So, writing program memory should be protected. If an error occurs on accessing this area, just it returns as -EINVAL quickly. This error code is exact same as old driver function, lp5523_do_store_load() because it should be kept for an user-application compatibility. Even the driver is changed, we can use the application without re-compiling sources. Reported-by: Pali Rohár Signed-off-by: Milo Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c index 29c8b19..9b8be6f6 100644 --- a/drivers/leds/leds-lp5523.c +++ b/drivers/leds/leds-lp5523.c @@ -74,6 +74,9 @@ #define LP5523_PAGE_ENG1 0 #define LP5523_PAGE_ENG2 1 #define LP5523_PAGE_ENG3 2 +#define LP5523_PAGE_MUX1 3 +#define LP5523_PAGE_MUX2 4 +#define LP5523_PAGE_MUX3 5 /* Program Memory Operations */ #define LP5523_MODE_ENG1_M 0x30 /* Operation Mode Register */ @@ -98,6 +101,8 @@ #define LP5523_RUN_ENG2 0x08 #define LP5523_RUN_ENG3 0x02 +#define LED_ACTIVE(mux, led) (!!(mux & (0x0001 << led))) + enum lp5523_chip_id { LP5523, LP55231, @@ -338,10 +343,20 @@ static int lp5523_update_program_memory(struct lp55xx_chip *chip, goto err; update_size = i; - for (i = 0; i < update_size; i++) - lp55xx_write(chip, LP5523_REG_PROG_MEM + i, pattern[i]); - return 0; + mutex_lock(&chip->lock); + + for (i = 0; i < update_size; i++) { + ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + i, pattern[i]); + if (ret) { + mutex_unlock(&chip->lock); + return -EINVAL; + } + } + + mutex_unlock(&chip->lock); + + return size; err: dev_err(&chip->cl->dev, "wrong pattern format\n"); @@ -368,6 +383,192 @@ static void lp5523_firmware_loaded(struct lp55xx_chip *chip) lp5523_update_program_memory(chip, fw->data, fw->size); } +static ssize_t show_engine_mode(struct device *dev, + struct device_attribute *attr, + char *buf, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode; + + switch (mode) { + case LP55XX_ENGINE_RUN: + return sprintf(buf, "run\n"); + case LP55XX_ENGINE_LOAD: + return sprintf(buf, "load\n"); + case LP55XX_ENGINE_DISABLED: + default: + return sprintf(buf, "disabled\n"); + } +} +show_mode(1) +show_mode(2) +show_mode(3) + +static ssize_t store_engine_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + struct lp55xx_engine *engine = &chip->engines[nr - 1]; + + mutex_lock(&chip->lock); + + chip->engine_idx = nr; + + if (!strncmp(buf, "run", 3)) { + lp5523_run_engine(chip, true); + engine->mode = LP55XX_ENGINE_RUN; + } else if (!strncmp(buf, "load", 4)) { + lp5523_stop_engine(chip); + lp5523_load_engine(chip); + engine->mode = LP55XX_ENGINE_LOAD; + } else if (!strncmp(buf, "disabled", 8)) { + lp5523_stop_engine(chip); + engine->mode = LP55XX_ENGINE_DISABLED; + } + + mutex_unlock(&chip->lock); + + return len; +} +store_mode(1) +store_mode(2) +store_mode(3) + +static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len) +{ + u16 tmp_mux = 0; + int i; + + len = min_t(int, len, LP5523_MAX_LEDS); + + for (i = 0; i < len; i++) { + switch (buf[i]) { + case '1': + tmp_mux |= (1 << i); + break; + case '0': + break; + case '\n': + i = len; + break; + default: + return -1; + } + } + *mux = tmp_mux; + + return 0; +} + +static void lp5523_mux_to_array(u16 led_mux, char *array) +{ + int i, pos = 0; + for (i = 0; i < LP5523_MAX_LEDS; i++) + pos += sprintf(array + pos, "%x", LED_ACTIVE(led_mux, i)); + + array[pos] = '\0'; +} + +static ssize_t show_engine_leds(struct device *dev, + struct device_attribute *attr, + char *buf, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + char mux[LP5523_MAX_LEDS + 1]; + + lp5523_mux_to_array(chip->engines[nr - 1].led_mux, mux); + + return sprintf(buf, "%s\n", mux); +} +show_leds(1) +show_leds(2) +show_leds(3) + +static int lp5523_load_mux(struct lp55xx_chip *chip, u16 mux, int nr) +{ + struct lp55xx_engine *engine = &chip->engines[nr - 1]; + int ret; + u8 mux_page[] = { + [LP55XX_ENGINE_1] = LP5523_PAGE_MUX1, + [LP55XX_ENGINE_2] = LP5523_PAGE_MUX2, + [LP55XX_ENGINE_3] = LP5523_PAGE_MUX3, + }; + + lp5523_load_engine(chip); + + ret = lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, mux_page[nr]); + if (ret) + return ret; + + ret = lp55xx_write(chip, LP5523_REG_PROG_MEM , (u8)(mux >> 8)); + if (ret) + return ret; + + ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + 1, (u8)(mux)); + if (ret) + return ret; + + engine->led_mux = mux; + return 0; +} + +static ssize_t store_engine_leds(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + struct lp55xx_engine *engine = &chip->engines[nr - 1]; + u16 mux = 0; + ssize_t ret; + + if (lp5523_mux_parse(buf, &mux, len)) + return -EINVAL; + + mutex_lock(&chip->lock); + + chip->engine_idx = nr; + ret = -EINVAL; + + if (engine->mode != LP55XX_ENGINE_LOAD) + goto leave; + + if (lp5523_load_mux(chip, mux, nr)) + goto leave; + + ret = len; +leave: + mutex_unlock(&chip->lock); + return ret; +} +store_leds(1) +store_leds(2) +store_leds(3) + +static ssize_t store_engine_load(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + + mutex_lock(&chip->lock); + + chip->engine_idx = nr; + lp5523_load_engine_and_select_page(chip); + + mutex_unlock(&chip->lock); + + return lp5523_update_program_memory(chip, buf, len); +} +store_load(1) +store_load(2) +store_load(3) + static ssize_t lp5523_selftest(struct device *dev, struct device_attribute *attr, char *buf) @@ -467,9 +668,27 @@ static void lp5523_led_brightness_work(struct work_struct *work) mutex_unlock(&chip->lock); } -static DEVICE_ATTR(selftest, S_IRUGO, lp5523_selftest, NULL); +static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode); +static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode); +static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode); +static LP55XX_DEV_ATTR_RW(engine1_leds, show_engine1_leds, store_engine1_leds); +static LP55XX_DEV_ATTR_RW(engine2_leds, show_engine2_leds, store_engine2_leds); +static LP55XX_DEV_ATTR_RW(engine3_leds, show_engine3_leds, store_engine3_leds); +static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load); +static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load); +static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load); +static LP55XX_DEV_ATTR_RO(selftest, lp5523_selftest); static struct attribute *lp5523_attributes[] = { + &dev_attr_engine1_mode.attr, + &dev_attr_engine2_mode.attr, + &dev_attr_engine3_mode.attr, + &dev_attr_engine1_load.attr, + &dev_attr_engine2_load.attr, + &dev_attr_engine3_load.attr, + &dev_attr_engine1_leds.attr, + &dev_attr_engine2_leds.attr, + &dev_attr_engine3_leds.attr, &dev_attr_selftest.attr, NULL, }; -- cgit v0.10.2 From 2f733cad3699c6fd1cc3350ab43c9f684b976e73 Mon Sep 17 00:00:00 2001 From: Milo Kim Date: Thu, 8 Aug 2013 14:09:24 +0900 Subject: leds: lp5523: remove unnecessary writing commands This patch reduces the number of programming commands. (Count of sending commands) Old code: 32 + program size (32 counts for clearing program memory) New code: 32 Pattern buffer is initialized to 0 in this function. Just update new program data and remaining buffers are filled with 0. So it's needless to clear whole area. Signed-off-by: Milo Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c index 9b8be6f6..fe3bcbb 100644 --- a/drivers/leds/leds-lp5523.c +++ b/drivers/leds/leds-lp5523.c @@ -312,17 +312,11 @@ static int lp5523_update_program_memory(struct lp55xx_chip *chip, u8 pattern[LP5523_PROGRAM_LENGTH] = {0}; unsigned cmd; char c[3]; - int update_size; int nrchars; - int offset = 0; int ret; - int i; - - /* clear program memory before updating */ - for (i = 0; i < LP5523_PROGRAM_LENGTH; i++) - lp55xx_write(chip, LP5523_REG_PROG_MEM + i, 0); + int offset = 0; + int i = 0; - i = 0; while ((offset < size - 1) && (i < LP5523_PROGRAM_LENGTH)) { /* separate sscanfs because length is working only for %s */ ret = sscanf(data + offset, "%2s%n ", c, &nrchars); @@ -342,11 +336,9 @@ static int lp5523_update_program_memory(struct lp55xx_chip *chip, if (i % 2) goto err; - update_size = i; - mutex_lock(&chip->lock); - for (i = 0; i < update_size; i++) { + for (i = 0; i < LP5523_PROGRAM_LENGTH; i++) { ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + i, pattern[i]); if (ret) { mutex_unlock(&chip->lock); -- cgit v0.10.2 From 863724a696e1a96b07c1bd0eb03cf08f10a714ba Mon Sep 17 00:00:00 2001 From: Milo Kim Date: Thu, 8 Aug 2013 14:11:29 +0900 Subject: Documentation: leds-lp5521,lp5523: update device attribute information MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now, all legacy application interfaces are restored. Each driver documentation is updated. Cc: Pali Rohár Signed-off-by: Milo Kim Signed-off-by: Bryan Wu diff --git a/Documentation/leds/leds-lp5521.txt b/Documentation/leds/leds-lp5521.txt index 79e4c2e..d08d8c1 100644 --- a/Documentation/leds/leds-lp5521.txt +++ b/Documentation/leds/leds-lp5521.txt @@ -18,7 +18,25 @@ All three channels can be also controlled using the engine micro programs. More details of the instructions can be found from the public data sheet. LP5521 has the internal program memory for running various LED patterns. -For the details, please refer to 'firmware' section in leds-lp55xx.txt +There are two ways to run LED patterns. + +1) Legacy interface - enginex_mode and enginex_load + Control interface for the engines: + x is 1 .. 3 + enginex_mode : disabled, load, run + enginex_load : store program (visible only in engine load mode) + + Example (start to blink the channel 2 led): + cd /sys/class/leds/lp5521:channel2/device + echo "load" > engine3_mode + echo "037f4d0003ff6000" > engine3_load + echo "run" > engine3_mode + + To stop the engine: + echo "disabled" > engine3_mode + +2) Firmware interface - LP55xx common interface + For the details, please refer to 'firmware' section in leds-lp55xx.txt sysfs contains a selftest entry. The test communicates with the chip and checks that diff --git a/Documentation/leds/leds-lp5523.txt b/Documentation/leds/leds-lp5523.txt index 899fdad..5b3e91d4 100644 --- a/Documentation/leds/leds-lp5523.txt +++ b/Documentation/leds/leds-lp5523.txt @@ -28,7 +28,26 @@ If both fields are NULL, 'lp5523' is used by default. /sys/class/leds/lp5523:channelN (N: 0 ~ 8) LP5523 has the internal program memory for running various LED patterns. -For the details, please refer to 'firmware' section in leds-lp55xx.txt +There are two ways to run LED patterns. + +1) Legacy interface - enginex_mode, enginex_load and enginex_leds + Control interface for the engines: + x is 1 .. 3 + enginex_mode : disabled, load, run + enginex_load : microcode load (visible only in load mode) + enginex_leds : led mux control (visible only in load mode) + + cd /sys/class/leds/lp5523:channel2/device + echo "load" > engine3_mode + echo "9d80400004ff05ff437f0000" > engine3_load + echo "111111111" > engine3_leds + echo "run" > engine3_mode + + To stop the engine: + echo "disabled" > engine3_mode + +2) Firmware interface - LP55xx common interface + For the details, please refer to 'firmware' section in leds-lp55xx.txt Selftest uses always the current from the platform data. -- cgit v0.10.2 From daa124b1fe992f27f96d89cde5923bb929e28c1c Mon Sep 17 00:00:00 2001 From: Milo Kim Date: Thu, 8 Aug 2013 14:12:14 +0900 Subject: leds: lp5562: use LP55xx common macros for device attributes Signed-off-by: Milo Kim Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-lp5562.c b/drivers/leds/leds-lp5562.c index a2c7398..2585cfd 100644 --- a/drivers/leds/leds-lp5562.c +++ b/drivers/leds/leds-lp5562.c @@ -477,8 +477,8 @@ static ssize_t lp5562_store_engine_mux(struct device *dev, 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 LP55XX_DEV_ATTR_WO(led_pattern, lp5562_store_pattern); +static LP55XX_DEV_ATTR_WO(engine_mux, lp5562_store_engine_mux); static struct attribute *lp5562_attributes[] = { &dev_attr_led_pattern.attr, -- cgit v0.10.2 From af67384f011e81ea86aef8aec51e62e775432ea8 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Wed, 14 Aug 2013 14:23:47 -0700 Subject: leds-pca9633: Add support for PCA9634 Add support for PCA9634 chip, which belongs to the same family as the 9633 but with support for 8 outputs instead of 4. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Bryan Wu diff --git a/Documentation/devicetree/bindings/leds/pca9633.txt b/Documentation/devicetree/bindings/leds/pca9633.txt index 6d9e1a9..aece3ea 100644 --- a/Documentation/devicetree/bindings/leds/pca9633.txt +++ b/Documentation/devicetree/bindings/leds/pca9633.txt @@ -1,24 +1,25 @@ -LEDs connected to pca9633 or pca9632 +LEDs connected to pca9632, pca9633 or pca9634 Required properties: -- compatible : should be : "nxp,pca963x" +- compatible : should be : "nxp,pca9632", "nxp,pca9633" or "nxp,pca9634" Optional properties: - nxp,totem-pole : use totem pole (push-pull) instead of default open-drain - nxp,hw-blink : use hardware blinking instead of software blinking -Each led is represented as a sub-node of the nxp,pca9633 device. +Each led is represented as a sub-node of the nxp,pca963x device. LED sub-node properties: - label : (optional) see Documentation/devicetree/bindings/leds/common.txt -- reg : number of LED line (could be from 0 to 4) +- reg : number of LED line (could be from 0 to 3 in pca9632 or pca9633 + or 0 to 7 in pca9634) - linux,default-trigger : (optional) see Documentation/devicetree/bindings/leds/common.txt Examples: pca9632: pca9632 { - compatible = "nxp,pca9632", "nxp,pca963x"; + compatible = "nxp,pca9632"; #address-cells = <1>; #size-cells = <0>; reg = <0x62>; diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index f2c738d..e7977aa 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -292,12 +292,13 @@ config LEDS_PCA955X devices include PCA9550, PCA9551, PCA9552, and PCA9553. config LEDS_PCA9633 - tristate "LED support for PCA9633 I2C chip" + tristate "LED support for PCA963x I2C chip" depends on LEDS_CLASS depends on I2C help - This option enables support for LEDs connected to the PCA9633 - LED driver chip accessed via the I2C bus. + This option enables support for LEDs connected to the PCA963x + LED driver chip accessed via the I2C bus. Supported + devices include PCA9633 and PCA9634 config LEDS_WM831X_STATUS tristate "LED support for status LEDs on WM831x PMICs" diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c index ecd1449..e59ca0a 100644 --- a/drivers/leds/leds-pca9633.c +++ b/drivers/leds/leds-pca9633.c @@ -1,7 +1,9 @@ /* * Copyright 2011 bct electronic GmbH + * Copyright 2013 Qtechnology/AS * * Author: Peter Meerwald + * Author: Ricardo Ribalda * * Based on leds-pca955x.c * @@ -10,6 +12,7 @@ * directory of this archive for more details. * * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62) + * LED driver for the PCA9634 I2C LED driver (7-bit slave address set by hw.) * * Note that hardware blinking violates the leds infrastructure driver * interface since the hardware only supports blinking all LEDs with the @@ -45,16 +48,42 @@ #define PCA9633_MODE1 0x00 #define PCA9633_MODE2 0x01 #define PCA9633_PWM_BASE 0x02 -#define PCA9633_GRPPWM 0x06 -#define PCA9633_GRPFREQ 0x07 -#define PCA9633_LEDOUT 0x08 + +enum pca9633_type { + pca9633, + pca9634, +}; + +struct pca9633_chipdef { + u8 grppwm; + u8 grpfreq; + u8 ledout_base; + int n_leds; +}; + +static struct pca9633_chipdef pca9633_chipdefs[] = { + [pca9633] = { + .grppwm = 0x6, + .grpfreq = 0x7, + .ledout_base = 0x8, + .n_leds = 4, + }, + [pca9634] = { + .grppwm = 0xa, + .grpfreq = 0xb, + .ledout_base = 0xc, + .n_leds = 8, + }, +}; /* Total blink period in milliseconds */ #define PCA9632_BLINK_PERIOD_MIN 42 #define PCA9632_BLINK_PERIOD_MAX 10667 static const struct i2c_device_id pca9633_id[] = { - { "pca9633", 0 }, + { "pca9632", pca9633 }, + { "pca9633", pca9633 }, + { "pca9634", pca9634 }, { } }; MODULE_DEVICE_TABLE(i2c, pca9633_id); @@ -66,10 +95,11 @@ enum pca9633_cmd { struct pca9633_led { struct i2c_client *client; + struct pca9633_chipdef *chipdef; struct work_struct work; enum led_brightness brightness; struct led_classdev led_cdev; - int led_num; /* 0 .. 3 potentially */ + int led_num; /* 0 .. 7 potentially */ enum pca9633_cmd cmd; char name[32]; u8 gdc; @@ -78,24 +108,26 @@ struct pca9633_led { static void pca9633_brightness_work(struct pca9633_led *pca9633) { - u8 ledout = i2c_smbus_read_byte_data(pca9633->client, PCA9633_LEDOUT); - int shift = 2 * pca9633->led_num; + u8 ledout_addr = pca9633->chipdef->ledout_base + (pca9633->led_num / 4); + u8 ledout; + int shift = 2 * (pca9633->led_num % 4); u8 mask = 0x3 << shift; + ledout = i2c_smbus_read_byte_data(pca9633->client, ledout_addr); switch (pca9633->brightness) { case LED_FULL: - i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT, + i2c_smbus_write_byte_data(pca9633->client, ledout_addr, (ledout & ~mask) | (PCA9633_LED_ON << shift)); break; case LED_OFF: - i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT, + i2c_smbus_write_byte_data(pca9633->client, ledout_addr, ledout & ~mask); break; default: i2c_smbus_write_byte_data(pca9633->client, PCA9633_PWM_BASE + pca9633->led_num, pca9633->brightness); - i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT, + i2c_smbus_write_byte_data(pca9633->client, ledout_addr, (ledout & ~mask) | (PCA9633_LED_PWM << shift)); break; } @@ -103,15 +135,16 @@ static void pca9633_brightness_work(struct pca9633_led *pca9633) static void pca9633_blink_work(struct pca9633_led *pca9633) { - u8 ledout = i2c_smbus_read_byte_data(pca9633->client, PCA9633_LEDOUT); + u8 ledout_addr = pca9633->chipdef->ledout_base + (pca9633->led_num / 4); + u8 ledout = i2c_smbus_read_byte_data(pca9633->client, ledout_addr); u8 mode2 = i2c_smbus_read_byte_data(pca9633->client, PCA9633_MODE2); - int shift = 2 * pca9633->led_num; + int shift = 2 * (pca9633->led_num % 4); u8 mask = 0x3 << shift; - i2c_smbus_write_byte_data(pca9633->client, PCA9633_GRPPWM, + i2c_smbus_write_byte_data(pca9633->client, pca9633->chipdef->grppwm, pca9633->gdc); - i2c_smbus_write_byte_data(pca9633->client, PCA9633_GRPFREQ, + i2c_smbus_write_byte_data(pca9633->client, pca9633->chipdef->grpfreq, pca9633->gfrq); if (!(mode2 & PCA9633_MODE2_DMBLNK)) @@ -119,7 +152,7 @@ static void pca9633_blink_work(struct pca9633_led *pca9633) mode2 | PCA9633_MODE2_DMBLNK); if ((ledout & mask) != (PCA9633_LED_GRP_PWM << shift)) - i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT, + i2c_smbus_write_byte_data(pca9633->client, ledout_addr, (ledout & ~mask) | (PCA9633_LED_GRP_PWM << shift)); } @@ -215,7 +248,7 @@ static int pca9633_blink_set(struct led_classdev *led_cdev, #if IS_ENABLED(CONFIG_OF) static struct pca9633_platform_data * -pca9633_dt_init(struct i2c_client *client) +pca9633_dt_init(struct i2c_client *client, struct pca9633_chipdef *chip) { struct device_node *np = client->dev.of_node, *child; struct pca9633_platform_data *pdata; @@ -223,11 +256,11 @@ pca9633_dt_init(struct i2c_client *client) int count; count = of_get_child_count(np); - if (!count || count > 4) + if (!count || count > chip->n_leds) return ERR_PTR(-ENODEV); pca9633_leds = devm_kzalloc(&client->dev, - sizeof(struct led_info) * count, GFP_KERNEL); + sizeof(struct led_info) * chip->n_leds, GFP_KERNEL); if (!pca9633_leds) return ERR_PTR(-ENOMEM); @@ -269,12 +302,14 @@ pca9633_dt_init(struct i2c_client *client) } static const struct of_device_id of_pca9633_match[] = { - { .compatible = "nxp,pca963x", }, + { .compatible = "nxp,pca9632", }, + { .compatible = "nxp,pca9633", }, + { .compatible = "nxp,pca9634", }, {}, }; #else static struct pca9633_platform_data * -pca9633_dt_init(struct i2c_client *client) +pca9633_dt_init(struct i2c_client *client, struct pca9633_chipdef *chip) { return ERR_PTR(-ENODEV); } @@ -285,34 +320,38 @@ static int pca9633_probe(struct i2c_client *client, { struct pca9633_led *pca9633; struct pca9633_platform_data *pdata; + struct pca9633_chipdef *chip; int i, err; + chip = &pca9633_chipdefs[id->driver_data]; pdata = dev_get_platdata(&client->dev); if (!pdata) { - pdata = pca9633_dt_init(client); + pdata = pca9633_dt_init(client, chip); if (IS_ERR(pdata)) { dev_warn(&client->dev, "could not parse configuration\n"); pdata = NULL; } } - if (pdata) { - if (pdata->leds.num_leds <= 0 || pdata->leds.num_leds > 4) { - dev_err(&client->dev, "board info must claim at most 4 LEDs"); - return -EINVAL; - } + if (pdata && (pdata->leds.num_leds < 1 || + pdata->leds.num_leds > chip->n_leds)) { + dev_err(&client->dev, "board info must claim 1-%d LEDs", + chip->n_leds); + return -EINVAL; } - pca9633 = devm_kzalloc(&client->dev, 4 * sizeof(*pca9633), GFP_KERNEL); + pca9633 = devm_kzalloc(&client->dev, chip->n_leds * sizeof(*pca9633), + GFP_KERNEL); if (!pca9633) return -ENOMEM; i2c_set_clientdata(client, pca9633); - for (i = 0; i < 4; i++) { + for (i = 0; i < chip->n_leds; i++) { pca9633[i].client = client; pca9633[i].led_num = i; + pca9633[i].chipdef = chip; /* Platform data can specify LED names and default triggers */ if (pdata && i < pdata->leds.num_leds) { @@ -349,7 +388,9 @@ static int pca9633_probe(struct i2c_client *client, i2c_smbus_write_byte_data(client, PCA9633_MODE2, 0x01); /* Turn off LEDs */ - i2c_smbus_write_byte_data(client, PCA9633_LEDOUT, 0x00); + i2c_smbus_write_byte_data(client, chip->ledout_base, 0x00); + if (chip->n_leds > 4) + i2c_smbus_write_byte_data(client, chip->ledout_base + 1, 0x00); return 0; @@ -367,10 +408,11 @@ static int pca9633_remove(struct i2c_client *client) struct pca9633_led *pca9633 = i2c_get_clientdata(client); int i; - for (i = 0; i < 4; i++) { - led_classdev_unregister(&pca9633[i].led_cdev); - cancel_work_sync(&pca9633[i].work); - } + for (i = 0; i < pca9633->chipdef->n_leds; i++) + if (pca9633[i].client != NULL) { + led_classdev_unregister(&pca9633[i].led_cdev); + cancel_work_sync(&pca9633[i].work); + } return 0; } -- cgit v0.10.2 From a5cd98b796b6891edc324415034a7f89531c754f Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Wed, 14 Aug 2013 14:23:48 -0700 Subject: leds-pca9633: Unique naming of the LEDs If there is more than one pca963x chips on the system and there are some LEDs without platform_data names, the driver wont be able to provide unique naming to them. This will cause led_class_dev_register to fail, unregistering all the LEDs of the chip. This patch adds the i2c address to the name of the unnamed LEDs, making them unique. [ 555.346827] ------------[ cut here ]------------ [ 555.346844] WARNING: at /build/linux-voe0Su/linux-3.9.8/fs/sysfs/dir.c:536 sysfs_add_one+0x8b/0x9d() [ 555.346847] Hardware name: QT5022 [ 555.346850] sysfs: cannot create duplicate filename '/class/leds/pca9633:6' [ 555.346853] Modules linked in: qt5038_platform(O+) leds_pca9633(O) hid_generic ledtrig_default_on rfcomm bnep bluetooth binfmt_misc nfsd auth_rpcgss nfs_acl nfs lockd dns_resolver fscache sunrpc nls_utf8 nls_cp437 vfat fat loop fuse joydev hid_multitouch usbhid hid acpi_cpufreq mperf kvm_amd kvm evdev pn533 nfc arc4 microcode pcspkr efivars k10temp ath9k ath9k_common ath9k_hw ath fglrx(PO) mac80211 cfg80211 video rfkill processor thermal_sys sp5100_tco button i2c_piix4 ext4 crc16 jbd2 mbcache sg sd_mod crc_t10dif ahci libahci igb i2c_algo_bit i2c_core dca ptp pps_core ehci_pci ohci_hcd ehci_hcd libata usbcore usb_common scsi_mod [last unloaded: leds_pca963x] [ 555.346940] Pid: 4766, comm: insmod Tainted: P W O 3.9-1-amd64 #1 Debian 3.9.8-1 [ 555.346943] Call Trace: [ 555.346956] [] ? warn_slowpath_common+0x76/0x8c [ 555.346962] [] ? warn_slowpath_fmt+0x47/0x49 [ 555.346968] [] ? sysfs_pathname+0x3b/0x41 [ 555.346973] [] ? sysfs_add_one+0x8b/0x9d [ 555.346978] [] ? sysfs_do_create_link_sd+0xe8/0x174 [ 555.346985] [] ? device_add+0x243/0x5ab [ 555.346991] [] ? complete_all+0x31/0x40 [ 555.346998] [] ? init_timer_key+0xc/0x56 [ 555.347004] [] ? device_create_vargs+0x82/0xb6 [ 555.347009] [] ? device_create+0x2f/0x31 [ 555.347014] [] ? should_resched+0x5/0x23 [ 555.347021] [] ? led_classdev_register+0x24/0x103 [ 555.347028] [] ? pca9633_probe+0x173/0x239 [leds_pca9633] [ 555.347035] [] ? __driver_attach+0x73/0x73 [ 555.347049] [] ? i2c_device_probe+0x63/0x88 [i2c_core] [ 555.347057] [] ? driver_probe_device+0x92/0x1b0 [ 555.347064] [] ? bus_for_each_drv+0x43/0x7d [ 555.347070] [] ? device_attach+0x68/0x83 [ 555.347078] [] ? bus_probe_device+0x25/0x8d [ 555.347083] [] ? device_add+0x3ea/0x5ab [ 555.347088] [] ? complete_all+0x31/0x40 [ 555.347094] [] ? init_timer_key+0xc/0x56 [ 555.347104] [] ? i2c_new_device+0x10d/0x179 [i2c_core] [ 555.347112] [] ? qt5038_init+0x36/0x1000 [qt5038_platform] [ 555.347119] [] ? 0xffffffffa008efff [ 555.347125] [] ? do_one_initcall+0x74/0x128 [ 555.347131] [] ? 0xffffffffa008efff [ 555.347137] [] ? load_module+0x1af7/0x1dfc [ 555.347144] [] ? free_notes_attrs+0x3c/0x3c [ 555.347150] [] ? sys_init_module+0x9e/0xab [ 555.347157] [] ? system_call_fastpath+0x16/0x1b [ 555.347161] ---[ end trace ad00b85794e0de4d ]--- [ 555.347448] leds-pca9633: probe of 0-006b failed with error -17 Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c index e59ca0a..7dd48f8 100644 --- a/drivers/leds/leds-pca9633.c +++ b/drivers/leds/leds-pca9633.c @@ -362,10 +362,12 @@ static int pca9633_probe(struct i2c_client *client, if (pdata->leds.leds[i].default_trigger) pca9633[i].led_cdev.default_trigger = pdata->leds.leds[i].default_trigger; - } else { - snprintf(pca9633[i].name, sizeof(pca9633[i].name), - "pca9633:%d", i); } + if (!pdata || i >= pdata->leds.num_leds || + !pdata->leds.leds[i].name) + snprintf(pca9633[i].name, sizeof(pca9633[i].name), + "pca9633:%d:%.2x:%d", client->adapter->nr, + client->addr, i); pca9633[i].led_cdev.name = pca9633[i].name; pca9633[i].led_cdev.brightness_set = pca9633_led_set; -- cgit v0.10.2 From a7d0e9884fd7594d4de5066add5135ac6bb55bd4 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Wed, 14 Aug 2013 14:23:49 -0700 Subject: leds-pca9633: Add mutex to the ledout register To update an LED a register has to be read, updated and writen. If another LED whas been updated at the same time, this could lead into wrong updates. This patch adds a common mutex to all the leds of the same chip to protect the ledout register. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c index 7dd48f8..aaa1c4a 100644 --- a/drivers/leds/leds-pca9633.c +++ b/drivers/leds/leds-pca9633.c @@ -93,9 +93,17 @@ enum pca9633_cmd { BLINK_SET, }; -struct pca9633_led { - struct i2c_client *client; +struct pca9633_led; + +struct pca9633 { struct pca9633_chipdef *chipdef; + struct mutex mutex; + struct i2c_client *client; + struct pca9633_led *leds; +}; + +struct pca9633_led { + struct pca9633 *chip; struct work_struct work; enum led_brightness brightness; struct led_classdev led_cdev; @@ -108,52 +116,60 @@ struct pca9633_led { static void pca9633_brightness_work(struct pca9633_led *pca9633) { - u8 ledout_addr = pca9633->chipdef->ledout_base + (pca9633->led_num / 4); + u8 ledout_addr = pca9633->chip->chipdef->ledout_base + + (pca9633->led_num / 4); u8 ledout; int shift = 2 * (pca9633->led_num % 4); u8 mask = 0x3 << shift; - ledout = i2c_smbus_read_byte_data(pca9633->client, ledout_addr); + mutex_lock(&pca9633->chip->mutex); + ledout = i2c_smbus_read_byte_data(pca9633->chip->client, ledout_addr); switch (pca9633->brightness) { case LED_FULL: - i2c_smbus_write_byte_data(pca9633->client, ledout_addr, + i2c_smbus_write_byte_data(pca9633->chip->client, ledout_addr, (ledout & ~mask) | (PCA9633_LED_ON << shift)); break; case LED_OFF: - i2c_smbus_write_byte_data(pca9633->client, ledout_addr, + i2c_smbus_write_byte_data(pca9633->chip->client, ledout_addr, ledout & ~mask); break; default: - i2c_smbus_write_byte_data(pca9633->client, + i2c_smbus_write_byte_data(pca9633->chip->client, PCA9633_PWM_BASE + pca9633->led_num, pca9633->brightness); - i2c_smbus_write_byte_data(pca9633->client, ledout_addr, + i2c_smbus_write_byte_data(pca9633->chip->client, ledout_addr, (ledout & ~mask) | (PCA9633_LED_PWM << shift)); break; } + mutex_unlock(&pca9633->chip->mutex); } static void pca9633_blink_work(struct pca9633_led *pca9633) { - u8 ledout_addr = pca9633->chipdef->ledout_base + (pca9633->led_num / 4); - u8 ledout = i2c_smbus_read_byte_data(pca9633->client, ledout_addr); - u8 mode2 = i2c_smbus_read_byte_data(pca9633->client, PCA9633_MODE2); + u8 ledout_addr = pca9633->chip->chipdef->ledout_base + + (pca9633->led_num / 4); + u8 ledout; + u8 mode2 = i2c_smbus_read_byte_data(pca9633->chip->client, + PCA9633_MODE2); int shift = 2 * (pca9633->led_num % 4); u8 mask = 0x3 << shift; - i2c_smbus_write_byte_data(pca9633->client, pca9633->chipdef->grppwm, - pca9633->gdc); + i2c_smbus_write_byte_data(pca9633->chip->client, + pca9633->chip->chipdef->grppwm, pca9633->gdc); - i2c_smbus_write_byte_data(pca9633->client, pca9633->chipdef->grpfreq, - pca9633->gfrq); + i2c_smbus_write_byte_data(pca9633->chip->client, + pca9633->chip->chipdef->grpfreq, pca9633->gfrq); if (!(mode2 & PCA9633_MODE2_DMBLNK)) - i2c_smbus_write_byte_data(pca9633->client, PCA9633_MODE2, + i2c_smbus_write_byte_data(pca9633->chip->client, PCA9633_MODE2, mode2 | PCA9633_MODE2_DMBLNK); + mutex_lock(&pca9633->chip->mutex); + ledout = i2c_smbus_read_byte_data(pca9633->chip->client, ledout_addr); if ((ledout & mask) != (PCA9633_LED_GRP_PWM << shift)) - i2c_smbus_write_byte_data(pca9633->client, ledout_addr, + i2c_smbus_write_byte_data(pca9633->chip->client, ledout_addr, (ledout & ~mask) | (PCA9633_LED_GRP_PWM << shift)); + mutex_unlock(&pca9633->chip->mutex); } static void pca9633_work(struct work_struct *work) @@ -318,6 +334,7 @@ pca9633_dt_init(struct i2c_client *client, struct pca9633_chipdef *chip) static int pca9633_probe(struct i2c_client *client, const struct i2c_device_id *id) { + struct pca9633 *pca9633_chip; struct pca9633_led *pca9633; struct pca9633_platform_data *pdata; struct pca9633_chipdef *chip; @@ -341,17 +358,30 @@ static int pca9633_probe(struct i2c_client *client, return -EINVAL; } + pca9633_chip = devm_kzalloc(&client->dev, sizeof(*pca9633_chip), + GFP_KERNEL); + if (!pca9633_chip) + return -ENOMEM; pca9633 = devm_kzalloc(&client->dev, chip->n_leds * sizeof(*pca9633), GFP_KERNEL); if (!pca9633) return -ENOMEM; - i2c_set_clientdata(client, pca9633); + i2c_set_clientdata(client, pca9633_chip); + + mutex_init(&pca9633_chip->mutex); + pca9633_chip->chipdef = chip; + pca9633_chip->client = client; + pca9633_chip->leds = pca9633; + + /* Turn off LEDs by default*/ + i2c_smbus_write_byte_data(client, chip->ledout_base, 0x00); + if (chip->n_leds > 4) + i2c_smbus_write_byte_data(client, chip->ledout_base + 1, 0x00); for (i = 0; i < chip->n_leds; i++) { - pca9633[i].client = client; pca9633[i].led_num = i; - pca9633[i].chipdef = chip; + pca9633[i].chip = pca9633_chip; /* Platform data can specify LED names and default triggers */ if (pdata && i < pdata->leds.num_leds) { @@ -389,11 +419,6 @@ static int pca9633_probe(struct i2c_client *client, if (pdata && pdata->outdrv == PCA9633_OPEN_DRAIN) i2c_smbus_write_byte_data(client, PCA9633_MODE2, 0x01); - /* Turn off LEDs */ - i2c_smbus_write_byte_data(client, chip->ledout_base, 0x00); - if (chip->n_leds > 4) - i2c_smbus_write_byte_data(client, chip->ledout_base + 1, 0x00); - return 0; exit: @@ -407,14 +432,13 @@ exit: static int pca9633_remove(struct i2c_client *client) { - struct pca9633_led *pca9633 = i2c_get_clientdata(client); + struct pca9633 *pca9633 = i2c_get_clientdata(client); int i; - for (i = 0; i < pca9633->chipdef->n_leds; i++) - if (pca9633[i].client != NULL) { - led_classdev_unregister(&pca9633[i].led_cdev); - cancel_work_sync(&pca9633[i].work); - } + for (i = 0; i < pca9633->chipdef->n_leds; i++) { + led_classdev_unregister(&pca9633->leds[i].led_cdev); + cancel_work_sync(&pca9633->leds[i].work); + } return 0; } -- cgit v0.10.2 From 56a1740c21e4396164265c3ec80e29990ddcdc36 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Wed, 14 Aug 2013 14:23:50 -0700 Subject: leds-pca9633: Rename to leds-pca963x The driver now supports the chips pca9633 and pca9634, therefore we rename the files to more generic and meaningul names Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Bryan Wu diff --git a/Documentation/devicetree/bindings/leds/pca9633.txt b/Documentation/devicetree/bindings/leds/pca9633.txt deleted file mode 100644 index aece3ea..0000000 --- a/Documentation/devicetree/bindings/leds/pca9633.txt +++ /dev/null @@ -1,47 +0,0 @@ -LEDs connected to pca9632, pca9633 or pca9634 - -Required properties: -- compatible : should be : "nxp,pca9632", "nxp,pca9633" or "nxp,pca9634" - -Optional properties: -- nxp,totem-pole : use totem pole (push-pull) instead of default open-drain -- nxp,hw-blink : use hardware blinking instead of software blinking - -Each led is represented as a sub-node of the nxp,pca963x device. - -LED sub-node properties: -- label : (optional) see Documentation/devicetree/bindings/leds/common.txt -- reg : number of LED line (could be from 0 to 3 in pca9632 or pca9633 - or 0 to 7 in pca9634) -- linux,default-trigger : (optional) - see Documentation/devicetree/bindings/leds/common.txt - -Examples: - -pca9632: pca9632 { - compatible = "nxp,pca9632"; - #address-cells = <1>; - #size-cells = <0>; - reg = <0x62>; - - red@0 { - label = "red"; - reg = <0>; - linux,default-trigger = "none"; - }; - green@1 { - label = "green"; - reg = <1>; - linux,default-trigger = "none"; - }; - blue@2 { - label = "blue"; - reg = <2>; - linux,default-trigger = "none"; - }; - unused@3 { - label = "unused"; - reg = <3>; - linux,default-trigger = "none"; - }; -}; diff --git a/Documentation/devicetree/bindings/leds/pca963x.txt b/Documentation/devicetree/bindings/leds/pca963x.txt new file mode 100644 index 0000000..aece3ea --- /dev/null +++ b/Documentation/devicetree/bindings/leds/pca963x.txt @@ -0,0 +1,47 @@ +LEDs connected to pca9632, pca9633 or pca9634 + +Required properties: +- compatible : should be : "nxp,pca9632", "nxp,pca9633" or "nxp,pca9634" + +Optional properties: +- nxp,totem-pole : use totem pole (push-pull) instead of default open-drain +- nxp,hw-blink : use hardware blinking instead of software blinking + +Each led is represented as a sub-node of the nxp,pca963x device. + +LED sub-node properties: +- label : (optional) see Documentation/devicetree/bindings/leds/common.txt +- reg : number of LED line (could be from 0 to 3 in pca9632 or pca9633 + or 0 to 7 in pca9634) +- linux,default-trigger : (optional) + see Documentation/devicetree/bindings/leds/common.txt + +Examples: + +pca9632: pca9632 { + compatible = "nxp,pca9632"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x62>; + + red@0 { + label = "red"; + reg = <0>; + linux,default-trigger = "none"; + }; + green@1 { + label = "green"; + reg = <1>; + linux,default-trigger = "none"; + }; + blue@2 { + label = "blue"; + reg = <2>; + linux,default-trigger = "none"; + }; + unused@3 { + label = "unused"; + reg = <3>; + linux,default-trigger = "none"; + }; +}; diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index e7977aa..a1a52be 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -291,7 +291,7 @@ config LEDS_PCA955X LED driver chips accessed via the I2C bus. Supported devices include PCA9550, PCA9551, PCA9552, and PCA9553. -config LEDS_PCA9633 +config LEDS_PCA963X tristate "LED support for PCA963x I2C chip" depends on LEDS_CLASS depends on I2C diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 3013113..c7e3542 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -35,7 +35,7 @@ obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o obj-$(CONFIG_LEDS_OT200) += leds-ot200.o obj-$(CONFIG_LEDS_FSG) += leds-fsg.o obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o -obj-$(CONFIG_LEDS_PCA9633) += leds-pca9633.o +obj-$(CONFIG_LEDS_PCA963X) += leds-pca963x.o obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o obj-$(CONFIG_LEDS_DA9052) += leds-da9052.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c deleted file mode 100644 index aaa1c4a..0000000 --- a/drivers/leds/leds-pca9633.c +++ /dev/null @@ -1,461 +0,0 @@ -/* - * Copyright 2011 bct electronic GmbH - * Copyright 2013 Qtechnology/AS - * - * Author: Peter Meerwald - * Author: Ricardo Ribalda - * - * Based on leds-pca955x.c - * - * This file is subject to the terms and conditions of version 2 of - * the GNU General Public License. See the file COPYING in the main - * directory of this archive for more details. - * - * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62) - * LED driver for the PCA9634 I2C LED driver (7-bit slave address set by hw.) - * - * Note that hardware blinking violates the leds infrastructure driver - * interface since the hardware only supports blinking all LEDs with the - * same delay_on/delay_off rates. That is, only the LEDs that are set to - * blink will actually blink but all LEDs that are set to blink will blink - * in identical fashion. The delay_on/delay_off values of the last LED - * that is set to blink will be used for all of the blinking LEDs. - * Hardware blinking is disabled by default but can be enabled by setting - * the 'blink_type' member in the platform_data struct to 'PCA9633_HW_BLINK' - * or by adding the 'nxp,hw-blink' property to the DTS. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* LED select registers determine the source that drives LED outputs */ -#define PCA9633_LED_OFF 0x0 /* LED driver off */ -#define PCA9633_LED_ON 0x1 /* LED driver on */ -#define PCA9633_LED_PWM 0x2 /* Controlled through PWM */ -#define PCA9633_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */ - -#define PCA9633_MODE2_DMBLNK 0x20 /* Enable blinking */ - -#define PCA9633_MODE1 0x00 -#define PCA9633_MODE2 0x01 -#define PCA9633_PWM_BASE 0x02 - -enum pca9633_type { - pca9633, - pca9634, -}; - -struct pca9633_chipdef { - u8 grppwm; - u8 grpfreq; - u8 ledout_base; - int n_leds; -}; - -static struct pca9633_chipdef pca9633_chipdefs[] = { - [pca9633] = { - .grppwm = 0x6, - .grpfreq = 0x7, - .ledout_base = 0x8, - .n_leds = 4, - }, - [pca9634] = { - .grppwm = 0xa, - .grpfreq = 0xb, - .ledout_base = 0xc, - .n_leds = 8, - }, -}; - -/* Total blink period in milliseconds */ -#define PCA9632_BLINK_PERIOD_MIN 42 -#define PCA9632_BLINK_PERIOD_MAX 10667 - -static const struct i2c_device_id pca9633_id[] = { - { "pca9632", pca9633 }, - { "pca9633", pca9633 }, - { "pca9634", pca9634 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, pca9633_id); - -enum pca9633_cmd { - BRIGHTNESS_SET, - BLINK_SET, -}; - -struct pca9633_led; - -struct pca9633 { - struct pca9633_chipdef *chipdef; - struct mutex mutex; - struct i2c_client *client; - struct pca9633_led *leds; -}; - -struct pca9633_led { - struct pca9633 *chip; - struct work_struct work; - enum led_brightness brightness; - struct led_classdev led_cdev; - int led_num; /* 0 .. 7 potentially */ - enum pca9633_cmd cmd; - char name[32]; - u8 gdc; - u8 gfrq; -}; - -static void pca9633_brightness_work(struct pca9633_led *pca9633) -{ - u8 ledout_addr = pca9633->chip->chipdef->ledout_base - + (pca9633->led_num / 4); - u8 ledout; - int shift = 2 * (pca9633->led_num % 4); - u8 mask = 0x3 << shift; - - mutex_lock(&pca9633->chip->mutex); - ledout = i2c_smbus_read_byte_data(pca9633->chip->client, ledout_addr); - switch (pca9633->brightness) { - case LED_FULL: - i2c_smbus_write_byte_data(pca9633->chip->client, ledout_addr, - (ledout & ~mask) | (PCA9633_LED_ON << shift)); - break; - case LED_OFF: - i2c_smbus_write_byte_data(pca9633->chip->client, ledout_addr, - ledout & ~mask); - break; - default: - i2c_smbus_write_byte_data(pca9633->chip->client, - PCA9633_PWM_BASE + pca9633->led_num, - pca9633->brightness); - i2c_smbus_write_byte_data(pca9633->chip->client, ledout_addr, - (ledout & ~mask) | (PCA9633_LED_PWM << shift)); - break; - } - mutex_unlock(&pca9633->chip->mutex); -} - -static void pca9633_blink_work(struct pca9633_led *pca9633) -{ - u8 ledout_addr = pca9633->chip->chipdef->ledout_base + - (pca9633->led_num / 4); - u8 ledout; - u8 mode2 = i2c_smbus_read_byte_data(pca9633->chip->client, - PCA9633_MODE2); - int shift = 2 * (pca9633->led_num % 4); - u8 mask = 0x3 << shift; - - i2c_smbus_write_byte_data(pca9633->chip->client, - pca9633->chip->chipdef->grppwm, pca9633->gdc); - - i2c_smbus_write_byte_data(pca9633->chip->client, - pca9633->chip->chipdef->grpfreq, pca9633->gfrq); - - if (!(mode2 & PCA9633_MODE2_DMBLNK)) - i2c_smbus_write_byte_data(pca9633->chip->client, PCA9633_MODE2, - mode2 | PCA9633_MODE2_DMBLNK); - - mutex_lock(&pca9633->chip->mutex); - ledout = i2c_smbus_read_byte_data(pca9633->chip->client, ledout_addr); - if ((ledout & mask) != (PCA9633_LED_GRP_PWM << shift)) - i2c_smbus_write_byte_data(pca9633->chip->client, ledout_addr, - (ledout & ~mask) | (PCA9633_LED_GRP_PWM << shift)); - mutex_unlock(&pca9633->chip->mutex); -} - -static void pca9633_work(struct work_struct *work) -{ - struct pca9633_led *pca9633 = container_of(work, - struct pca9633_led, work); - - switch (pca9633->cmd) { - case BRIGHTNESS_SET: - pca9633_brightness_work(pca9633); - break; - case BLINK_SET: - pca9633_blink_work(pca9633); - break; - } -} - -static void pca9633_led_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - struct pca9633_led *pca9633; - - pca9633 = container_of(led_cdev, struct pca9633_led, led_cdev); - - pca9633->cmd = BRIGHTNESS_SET; - pca9633->brightness = value; - - /* - * Must use workqueue for the actual I/O since I2C operations - * can sleep. - */ - schedule_work(&pca9633->work); -} - -static int pca9633_blink_set(struct led_classdev *led_cdev, - unsigned long *delay_on, unsigned long *delay_off) -{ - struct pca9633_led *pca9633; - unsigned long time_on, time_off, period; - u8 gdc, gfrq; - - pca9633 = container_of(led_cdev, struct pca9633_led, led_cdev); - - time_on = *delay_on; - time_off = *delay_off; - - /* If both zero, pick reasonable defaults of 500ms each */ - if (!time_on && !time_off) { - time_on = 500; - time_off = 500; - } - - period = time_on + time_off; - - /* If period not supported by hardware, default to someting sane. */ - if ((period < PCA9632_BLINK_PERIOD_MIN) || - (period > PCA9632_BLINK_PERIOD_MAX)) { - time_on = 500; - time_off = 500; - period = time_on + time_off; - } - - /* - * From manual: duty cycle = (GDC / 256) -> - * (time_on / period) = (GDC / 256) -> - * GDC = ((time_on * 256) / period) - */ - gdc = (time_on * 256) / period; - - /* - * From manual: period = ((GFRQ + 1) / 24) in seconds. - * So, period (in ms) = (((GFRQ + 1) / 24) * 1000) -> - * GFRQ = ((period * 24 / 1000) - 1) - */ - gfrq = (period * 24 / 1000) - 1; - - pca9633->cmd = BLINK_SET; - pca9633->gdc = gdc; - pca9633->gfrq = gfrq; - - /* - * Must use workqueue for the actual I/O since I2C operations - * can sleep. - */ - schedule_work(&pca9633->work); - - *delay_on = time_on; - *delay_off = time_off; - - return 0; -} - -#if IS_ENABLED(CONFIG_OF) -static struct pca9633_platform_data * -pca9633_dt_init(struct i2c_client *client, struct pca9633_chipdef *chip) -{ - struct device_node *np = client->dev.of_node, *child; - struct pca9633_platform_data *pdata; - struct led_info *pca9633_leds; - int count; - - count = of_get_child_count(np); - if (!count || count > chip->n_leds) - return ERR_PTR(-ENODEV); - - pca9633_leds = devm_kzalloc(&client->dev, - sizeof(struct led_info) * chip->n_leds, GFP_KERNEL); - if (!pca9633_leds) - return ERR_PTR(-ENOMEM); - - for_each_child_of_node(np, child) { - struct led_info led; - u32 reg; - int res; - - led.name = - of_get_property(child, "label", NULL) ? : child->name; - led.default_trigger = - of_get_property(child, "linux,default-trigger", NULL); - res = of_property_read_u32(child, "reg", ®); - if (res != 0) - continue; - pca9633_leds[reg] = led; - } - pdata = devm_kzalloc(&client->dev, - sizeof(struct pca9633_platform_data), GFP_KERNEL); - if (!pdata) - return ERR_PTR(-ENOMEM); - - pdata->leds.leds = pca9633_leds; - pdata->leds.num_leds = count; - - /* default to open-drain unless totem pole (push-pull) is specified */ - if (of_property_read_bool(np, "nxp,totem-pole")) - pdata->outdrv = PCA9633_TOTEM_POLE; - else - pdata->outdrv = PCA9633_OPEN_DRAIN; - - /* default to software blinking unless hardware blinking is specified */ - if (of_property_read_bool(np, "nxp,hw-blink")) - pdata->blink_type = PCA9633_HW_BLINK; - else - pdata->blink_type = PCA9633_SW_BLINK; - - return pdata; -} - -static const struct of_device_id of_pca9633_match[] = { - { .compatible = "nxp,pca9632", }, - { .compatible = "nxp,pca9633", }, - { .compatible = "nxp,pca9634", }, - {}, -}; -#else -static struct pca9633_platform_data * -pca9633_dt_init(struct i2c_client *client, struct pca9633_chipdef *chip) -{ - return ERR_PTR(-ENODEV); -} -#endif - -static int pca9633_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct pca9633 *pca9633_chip; - struct pca9633_led *pca9633; - struct pca9633_platform_data *pdata; - struct pca9633_chipdef *chip; - int i, err; - - chip = &pca9633_chipdefs[id->driver_data]; - pdata = dev_get_platdata(&client->dev); - - if (!pdata) { - pdata = pca9633_dt_init(client, chip); - if (IS_ERR(pdata)) { - dev_warn(&client->dev, "could not parse configuration\n"); - pdata = NULL; - } - } - - if (pdata && (pdata->leds.num_leds < 1 || - pdata->leds.num_leds > chip->n_leds)) { - dev_err(&client->dev, "board info must claim 1-%d LEDs", - chip->n_leds); - return -EINVAL; - } - - pca9633_chip = devm_kzalloc(&client->dev, sizeof(*pca9633_chip), - GFP_KERNEL); - if (!pca9633_chip) - return -ENOMEM; - pca9633 = devm_kzalloc(&client->dev, chip->n_leds * sizeof(*pca9633), - GFP_KERNEL); - if (!pca9633) - return -ENOMEM; - - i2c_set_clientdata(client, pca9633_chip); - - mutex_init(&pca9633_chip->mutex); - pca9633_chip->chipdef = chip; - pca9633_chip->client = client; - pca9633_chip->leds = pca9633; - - /* Turn off LEDs by default*/ - i2c_smbus_write_byte_data(client, chip->ledout_base, 0x00); - if (chip->n_leds > 4) - i2c_smbus_write_byte_data(client, chip->ledout_base + 1, 0x00); - - for (i = 0; i < chip->n_leds; i++) { - pca9633[i].led_num = i; - pca9633[i].chip = pca9633_chip; - - /* Platform data can specify LED names and default triggers */ - if (pdata && i < pdata->leds.num_leds) { - if (pdata->leds.leds[i].name) - snprintf(pca9633[i].name, - sizeof(pca9633[i].name), "pca9633:%s", - pdata->leds.leds[i].name); - if (pdata->leds.leds[i].default_trigger) - pca9633[i].led_cdev.default_trigger = - pdata->leds.leds[i].default_trigger; - } - if (!pdata || i >= pdata->leds.num_leds || - !pdata->leds.leds[i].name) - snprintf(pca9633[i].name, sizeof(pca9633[i].name), - "pca9633:%d:%.2x:%d", client->adapter->nr, - client->addr, i); - - pca9633[i].led_cdev.name = pca9633[i].name; - pca9633[i].led_cdev.brightness_set = pca9633_led_set; - - if (pdata && pdata->blink_type == PCA9633_HW_BLINK) - pca9633[i].led_cdev.blink_set = pca9633_blink_set; - - INIT_WORK(&pca9633[i].work, pca9633_work); - - err = led_classdev_register(&client->dev, &pca9633[i].led_cdev); - if (err < 0) - goto exit; - } - - /* Disable LED all-call address and set normal mode */ - i2c_smbus_write_byte_data(client, PCA9633_MODE1, 0x00); - - /* Configure output: open-drain or totem pole (push-pull) */ - if (pdata && pdata->outdrv == PCA9633_OPEN_DRAIN) - i2c_smbus_write_byte_data(client, PCA9633_MODE2, 0x01); - - return 0; - -exit: - while (i--) { - led_classdev_unregister(&pca9633[i].led_cdev); - cancel_work_sync(&pca9633[i].work); - } - - return err; -} - -static int pca9633_remove(struct i2c_client *client) -{ - struct pca9633 *pca9633 = i2c_get_clientdata(client); - int i; - - for (i = 0; i < pca9633->chipdef->n_leds; i++) { - led_classdev_unregister(&pca9633->leds[i].led_cdev); - cancel_work_sync(&pca9633->leds[i].work); - } - - return 0; -} - -static struct i2c_driver pca9633_driver = { - .driver = { - .name = "leds-pca9633", - .owner = THIS_MODULE, - .of_match_table = of_match_ptr(of_pca9633_match), - }, - .probe = pca9633_probe, - .remove = pca9633_remove, - .id_table = pca9633_id, -}; - -module_i2c_driver(pca9633_driver); - -MODULE_AUTHOR("Peter Meerwald "); -MODULE_DESCRIPTION("PCA9633 LED driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c new file mode 100644 index 0000000..35d56a6 --- /dev/null +++ b/drivers/leds/leds-pca963x.c @@ -0,0 +1,461 @@ +/* + * Copyright 2011 bct electronic GmbH + * Copyright 2013 Qtechnology/AS + * + * Author: Peter Meerwald + * Author: Ricardo Ribalda + * + * Based on leds-pca955x.c + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62) + * LED driver for the PCA9634 I2C LED driver (7-bit slave address set by hw.) + * + * Note that hardware blinking violates the leds infrastructure driver + * interface since the hardware only supports blinking all LEDs with the + * same delay_on/delay_off rates. That is, only the LEDs that are set to + * blink will actually blink but all LEDs that are set to blink will blink + * in identical fashion. The delay_on/delay_off values of the last LED + * that is set to blink will be used for all of the blinking LEDs. + * Hardware blinking is disabled by default but can be enabled by setting + * the 'blink_type' member in the platform_data struct to 'PCA963X_HW_BLINK' + * or by adding the 'nxp,hw-blink' property to the DTS. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* LED select registers determine the source that drives LED outputs */ +#define PCA963X_LED_OFF 0x0 /* LED driver off */ +#define PCA963X_LED_ON 0x1 /* LED driver on */ +#define PCA963X_LED_PWM 0x2 /* Controlled through PWM */ +#define PCA963X_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */ + +#define PCA963X_MODE2_DMBLNK 0x20 /* Enable blinking */ + +#define PCA963X_MODE1 0x00 +#define PCA963X_MODE2 0x01 +#define PCA963X_PWM_BASE 0x02 + +enum pca963x_type { + pca9633, + pca9634, +}; + +struct pca963x_chipdef { + u8 grppwm; + u8 grpfreq; + u8 ledout_base; + int n_leds; +}; + +static struct pca963x_chipdef pca963x_chipdefs[] = { + [pca9633] = { + .grppwm = 0x6, + .grpfreq = 0x7, + .ledout_base = 0x8, + .n_leds = 4, + }, + [pca9634] = { + .grppwm = 0xa, + .grpfreq = 0xb, + .ledout_base = 0xc, + .n_leds = 8, + }, +}; + +/* Total blink period in milliseconds */ +#define PCA963X_BLINK_PERIOD_MIN 42 +#define PCA963X_BLINK_PERIOD_MAX 10667 + +static const struct i2c_device_id pca963x_id[] = { + { "pca9632", pca9633 }, + { "pca9633", pca9633 }, + { "pca9634", pca9634 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pca963x_id); + +enum pca963x_cmd { + BRIGHTNESS_SET, + BLINK_SET, +}; + +struct pca963x_led; + +struct pca963x { + struct pca963x_chipdef *chipdef; + struct mutex mutex; + struct i2c_client *client; + struct pca963x_led *leds; +}; + +struct pca963x_led { + struct pca963x *chip; + struct work_struct work; + enum led_brightness brightness; + struct led_classdev led_cdev; + int led_num; /* 0 .. 7 potentially */ + enum pca963x_cmd cmd; + char name[32]; + u8 gdc; + u8 gfrq; +}; + +static void pca963x_brightness_work(struct pca963x_led *pca963x) +{ + u8 ledout_addr = pca963x->chip->chipdef->ledout_base + + (pca963x->led_num / 4); + u8 ledout; + int shift = 2 * (pca963x->led_num % 4); + u8 mask = 0x3 << shift; + + mutex_lock(&pca963x->chip->mutex); + ledout = i2c_smbus_read_byte_data(pca963x->chip->client, ledout_addr); + switch (pca963x->brightness) { + case LED_FULL: + i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, + (ledout & ~mask) | (PCA963X_LED_ON << shift)); + break; + case LED_OFF: + i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, + ledout & ~mask); + break; + default: + i2c_smbus_write_byte_data(pca963x->chip->client, + PCA963X_PWM_BASE + pca963x->led_num, + pca963x->brightness); + i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, + (ledout & ~mask) | (PCA963X_LED_PWM << shift)); + break; + } + mutex_unlock(&pca963x->chip->mutex); +} + +static void pca963x_blink_work(struct pca963x_led *pca963x) +{ + u8 ledout_addr = pca963x->chip->chipdef->ledout_base + + (pca963x->led_num / 4); + u8 ledout; + u8 mode2 = i2c_smbus_read_byte_data(pca963x->chip->client, + PCA963X_MODE2); + int shift = 2 * (pca963x->led_num % 4); + u8 mask = 0x3 << shift; + + i2c_smbus_write_byte_data(pca963x->chip->client, + pca963x->chip->chipdef->grppwm, pca963x->gdc); + + i2c_smbus_write_byte_data(pca963x->chip->client, + pca963x->chip->chipdef->grpfreq, pca963x->gfrq); + + if (!(mode2 & PCA963X_MODE2_DMBLNK)) + i2c_smbus_write_byte_data(pca963x->chip->client, PCA963X_MODE2, + mode2 | PCA963X_MODE2_DMBLNK); + + mutex_lock(&pca963x->chip->mutex); + ledout = i2c_smbus_read_byte_data(pca963x->chip->client, ledout_addr); + if ((ledout & mask) != (PCA963X_LED_GRP_PWM << shift)) + i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, + (ledout & ~mask) | (PCA963X_LED_GRP_PWM << shift)); + mutex_unlock(&pca963x->chip->mutex); +} + +static void pca963x_work(struct work_struct *work) +{ + struct pca963x_led *pca963x = container_of(work, + struct pca963x_led, work); + + switch (pca963x->cmd) { + case BRIGHTNESS_SET: + pca963x_brightness_work(pca963x); + break; + case BLINK_SET: + pca963x_blink_work(pca963x); + break; + } +} + +static void pca963x_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct pca963x_led *pca963x; + + pca963x = container_of(led_cdev, struct pca963x_led, led_cdev); + + pca963x->cmd = BRIGHTNESS_SET; + pca963x->brightness = value; + + /* + * Must use workqueue for the actual I/O since I2C operations + * can sleep. + */ + schedule_work(&pca963x->work); +} + +static int pca963x_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + struct pca963x_led *pca963x; + unsigned long time_on, time_off, period; + u8 gdc, gfrq; + + pca963x = container_of(led_cdev, struct pca963x_led, led_cdev); + + time_on = *delay_on; + time_off = *delay_off; + + /* If both zero, pick reasonable defaults of 500ms each */ + if (!time_on && !time_off) { + time_on = 500; + time_off = 500; + } + + period = time_on + time_off; + + /* If period not supported by hardware, default to someting sane. */ + if ((period < PCA963X_BLINK_PERIOD_MIN) || + (period > PCA963X_BLINK_PERIOD_MAX)) { + time_on = 500; + time_off = 500; + period = time_on + time_off; + } + + /* + * From manual: duty cycle = (GDC / 256) -> + * (time_on / period) = (GDC / 256) -> + * GDC = ((time_on * 256) / period) + */ + gdc = (time_on * 256) / period; + + /* + * From manual: period = ((GFRQ + 1) / 24) in seconds. + * So, period (in ms) = (((GFRQ + 1) / 24) * 1000) -> + * GFRQ = ((period * 24 / 1000) - 1) + */ + gfrq = (period * 24 / 1000) - 1; + + pca963x->cmd = BLINK_SET; + pca963x->gdc = gdc; + pca963x->gfrq = gfrq; + + /* + * Must use workqueue for the actual I/O since I2C operations + * can sleep. + */ + schedule_work(&pca963x->work); + + *delay_on = time_on; + *delay_off = time_off; + + return 0; +} + +#if IS_ENABLED(CONFIG_OF) +static struct pca963x_platform_data * +pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip) +{ + struct device_node *np = client->dev.of_node, *child; + struct pca963x_platform_data *pdata; + struct led_info *pca963x_leds; + int count; + + count = of_get_child_count(np); + if (!count || count > chip->n_leds) + return ERR_PTR(-ENODEV); + + pca963x_leds = devm_kzalloc(&client->dev, + sizeof(struct led_info) * chip->n_leds, GFP_KERNEL); + if (!pca963x_leds) + return ERR_PTR(-ENOMEM); + + for_each_child_of_node(np, child) { + struct led_info led; + u32 reg; + int res; + + led.name = + of_get_property(child, "label", NULL) ? : child->name; + led.default_trigger = + of_get_property(child, "linux,default-trigger", NULL); + res = of_property_read_u32(child, "reg", ®); + if (res != 0) + continue; + pca963x_leds[reg] = led; + } + pdata = devm_kzalloc(&client->dev, + sizeof(struct pca963x_platform_data), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->leds.leds = pca963x_leds; + pdata->leds.num_leds = count; + + /* default to open-drain unless totem pole (push-pull) is specified */ + if (of_property_read_bool(np, "nxp,totem-pole")) + pdata->outdrv = PCA963X_TOTEM_POLE; + else + pdata->outdrv = PCA963X_OPEN_DRAIN; + + /* default to software blinking unless hardware blinking is specified */ + if (of_property_read_bool(np, "nxp,hw-blink")) + pdata->blink_type = PCA963X_HW_BLINK; + else + pdata->blink_type = PCA963X_SW_BLINK; + + return pdata; +} + +static const struct of_device_id of_pca963x_match[] = { + { .compatible = "nxp,pca9632", }, + { .compatible = "nxp,pca9633", }, + { .compatible = "nxp,pca9634", }, + {}, +}; +#else +static struct pca963x_platform_data * +pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip) +{ + return ERR_PTR(-ENODEV); +} +#endif + +static int pca963x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pca963x *pca963x_chip; + struct pca963x_led *pca963x; + struct pca963x_platform_data *pdata; + struct pca963x_chipdef *chip; + int i, err; + + chip = &pca963x_chipdefs[id->driver_data]; + pdata = dev_get_platdata(&client->dev); + + if (!pdata) { + pdata = pca963x_dt_init(client, chip); + if (IS_ERR(pdata)) { + dev_warn(&client->dev, "could not parse configuration\n"); + pdata = NULL; + } + } + + if (pdata && (pdata->leds.num_leds < 1 || + pdata->leds.num_leds > chip->n_leds)) { + dev_err(&client->dev, "board info must claim 1-%d LEDs", + chip->n_leds); + return -EINVAL; + } + + pca963x_chip = devm_kzalloc(&client->dev, sizeof(*pca963x_chip), + GFP_KERNEL); + if (!pca963x_chip) + return -ENOMEM; + pca963x = devm_kzalloc(&client->dev, chip->n_leds * sizeof(*pca963x), + GFP_KERNEL); + if (!pca963x) + return -ENOMEM; + + i2c_set_clientdata(client, pca963x_chip); + + mutex_init(&pca963x_chip->mutex); + pca963x_chip->chipdef = chip; + pca963x_chip->client = client; + pca963x_chip->leds = pca963x; + + /* Turn off LEDs by default*/ + i2c_smbus_write_byte_data(client, chip->ledout_base, 0x00); + if (chip->n_leds > 4) + i2c_smbus_write_byte_data(client, chip->ledout_base + 1, 0x00); + + for (i = 0; i < chip->n_leds; i++) { + pca963x[i].led_num = i; + pca963x[i].chip = pca963x_chip; + + /* Platform data can specify LED names and default triggers */ + if (pdata && i < pdata->leds.num_leds) { + if (pdata->leds.leds[i].name) + snprintf(pca963x[i].name, + sizeof(pca963x[i].name), "pca963x:%s", + pdata->leds.leds[i].name); + if (pdata->leds.leds[i].default_trigger) + pca963x[i].led_cdev.default_trigger = + pdata->leds.leds[i].default_trigger; + } + if (!pdata || i >= pdata->leds.num_leds || + !pdata->leds.leds[i].name) + snprintf(pca963x[i].name, sizeof(pca963x[i].name), + "pca963x:%d:%.2x:%d", client->adapter->nr, + client->addr, i); + + pca963x[i].led_cdev.name = pca963x[i].name; + pca963x[i].led_cdev.brightness_set = pca963x_led_set; + + if (pdata && pdata->blink_type == PCA963X_HW_BLINK) + pca963x[i].led_cdev.blink_set = pca963x_blink_set; + + INIT_WORK(&pca963x[i].work, pca963x_work); + + err = led_classdev_register(&client->dev, &pca963x[i].led_cdev); + if (err < 0) + goto exit; + } + + /* Disable LED all-call address and set normal mode */ + i2c_smbus_write_byte_data(client, PCA963X_MODE1, 0x00); + + /* Configure output: open-drain or totem pole (push-pull) */ + if (pdata && pdata->outdrv == PCA963X_OPEN_DRAIN) + i2c_smbus_write_byte_data(client, PCA963X_MODE2, 0x01); + + return 0; + +exit: + while (i--) { + led_classdev_unregister(&pca963x[i].led_cdev); + cancel_work_sync(&pca963x[i].work); + } + + return err; +} + +static int pca963x_remove(struct i2c_client *client) +{ + struct pca963x *pca963x = i2c_get_clientdata(client); + int i; + + for (i = 0; i < pca963x->chipdef->n_leds; i++) { + led_classdev_unregister(&pca963x->leds[i].led_cdev); + cancel_work_sync(&pca963x->leds[i].work); + } + + return 0; +} + +static struct i2c_driver pca963x_driver = { + .driver = { + .name = "leds-pca963x", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(of_pca963x_match), + }, + .probe = pca963x_probe, + .remove = pca963x_remove, + .id_table = pca963x_id, +}; + +module_i2c_driver(pca963x_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("PCA963X LED driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/platform_data/leds-pca9633.h b/include/linux/platform_data/leds-pca9633.h deleted file mode 100644 index 3c1037a..0000000 --- a/include/linux/platform_data/leds-pca9633.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * PCA9633 LED chip driver. - * - * Copyright 2012 bct electronic GmbH - * - * 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. - * - * 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., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA - */ - -#ifndef __LINUX_PCA9633_H -#define __LINUX_PCA9633_H -#include - -enum pca9633_outdrv { - PCA9633_OPEN_DRAIN, - PCA9633_TOTEM_POLE, /* aka push-pull */ -}; - -enum pca9633_blink_type { - PCA9633_SW_BLINK, - PCA9633_HW_BLINK, -}; - -struct pca9633_platform_data { - struct led_platform_data leds; - enum pca9633_outdrv outdrv; - enum pca9633_blink_type blink_type; -}; - -#endif /* __LINUX_PCA9633_H*/ diff --git a/include/linux/platform_data/leds-pca963x.h b/include/linux/platform_data/leds-pca963x.h new file mode 100644 index 0000000..e731f00 --- /dev/null +++ b/include/linux/platform_data/leds-pca963x.h @@ -0,0 +1,42 @@ +/* + * PCA963X LED chip driver. + * + * Copyright 2012 bct electronic GmbH + * Copyright 2013 Qtechnology A/S + * + * 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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef __LINUX_PCA963X_H +#define __LINUX_PCA963X_H +#include + +enum pca963x_outdrv { + PCA963X_OPEN_DRAIN, + PCA963X_TOTEM_POLE, /* aka push-pull */ +}; + +enum pca963x_blink_type { + PCA963X_SW_BLINK, + PCA963X_HW_BLINK, +}; + +struct pca963x_platform_data { + struct led_platform_data leds; + enum pca963x_outdrv outdrv; + enum pca963x_blink_type blink_type; +}; + +#endif /* __LINUX_PCA963X_H*/ -- cgit v0.10.2 From 8a6acd648c1dfdc82f1bbcd15f7777962054c268 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Wed, 14 Aug 2013 14:23:51 -0700 Subject: leds-pca963x: Fix device tree parsing A malformed device tree could lead into a segmentation fault if the reg value of a led is bigger than the number of leds. A valid device tree could have only information about the last led of the chip. Fix the device tree parsing to handle those cases. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index 35d56a6..82589c0 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -285,13 +285,13 @@ pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip) u32 reg; int res; + res = of_property_read_u32(child, "reg", ®); + if ((res != 0) || (reg >= chip->n_leds)) + continue; led.name = of_get_property(child, "label", NULL) ? : child->name; led.default_trigger = of_get_property(child, "linux,default-trigger", NULL); - res = of_property_read_u32(child, "reg", ®); - if (res != 0) - continue; pca963x_leds[reg] = led; } pdata = devm_kzalloc(&client->dev, @@ -300,7 +300,7 @@ pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip) return ERR_PTR(-ENOMEM); pdata->leds.leds = pca963x_leds; - pdata->leds.num_leds = count; + pdata->leds.num_leds = chip->n_leds; /* default to open-drain unless totem pole (push-pull) is specified */ if (of_property_read_bool(np, "nxp,totem-pole")) -- cgit v0.10.2 From c945cbcf453cb72dc2287fc4f7b63314f173b313 Mon Sep 17 00:00:00 2001 From: Manfred Schlaegl Date: Tue, 13 Aug 2013 04:17:05 -0700 Subject: leds: trigger: ledtrig-backlight: Fix invalid memory access in fb_event notification callback fb_notifier_callback is called on any event fired by fb_notifier_call_chain. Events may, or may not contain some data (fb_event.data). In case of FB_EVENT_BLANK fb_event.data contains a pointer to an integer holdingthe blank state. The Problem is, that in ledtrig-backlight.c - fb_notifier_callback the pointer to blank state is dereferenced BEFORE the event-type is checked. Obviously this leads to problems with other events than FB_EVENT_BLANK, where fb_event.data is undefined or NULL. It seems, that this problem existed ever since the driver was added. Like in drivers/video/backlight/backlight.c line 43 I would suggest to return immediately on events other than FB_EVENT_BLANK. Signed-off-by: Manfred Schlaegl Signed-off-by: Bryan Wu diff --git a/drivers/leds/trigger/ledtrig-backlight.c b/drivers/leds/trigger/ledtrig-backlight.c index 3c9c88a..47e55aa 100644 --- a/drivers/leds/trigger/ledtrig-backlight.c +++ b/drivers/leds/trigger/ledtrig-backlight.c @@ -36,26 +36,28 @@ static int fb_notifier_callback(struct notifier_block *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; + int *blank; + int new_status; - switch (event) { - case FB_EVENT_BLANK: - if (new_status == n->old_status) - break; + /* If we aren't interested in this event, skip it immediately ... */ + if (event != FB_EVENT_BLANK) + return 0; - if ((n->old_status == UNBLANK) ^ n->invert) { - n->brightness = led->brightness; - __led_set_brightness(led, LED_OFF); - } else { - __led_set_brightness(led, n->brightness); - } + blank = fb_event->data; + new_status = *blank ? BLANK : UNBLANK; - n->old_status = new_status; + if (new_status == n->old_status) + return 0; - 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; + return 0; } -- cgit v0.10.2 From 61abeba5222895d6900b13115f5d8eba7988d7d6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 29 Aug 2013 07:18:14 -0700 Subject: leds: wm831x-status: Request a REG resource The wm831x-status driver was not converted to use a REG resource when they were introduced and the rest of the wm831x drivers converted, causing it to fail to probe due to requesting the wrong resource type. Signed-off-by: Mark Brown Cc: stable@vger.kernel.org # v3.7+ Signed-off-by: Bryan Wu diff --git a/drivers/leds/leds-wm831x-status.c b/drivers/leds/leds-wm831x-status.c index c32dad4..0a1a13f 100644 --- a/drivers/leds/leds-wm831x-status.c +++ b/drivers/leds/leds-wm831x-status.c @@ -230,9 +230,9 @@ static int wm831x_status_probe(struct platform_device *pdev) int id = pdev->id % ARRAY_SIZE(chip_pdata->status); int ret; - res = platform_get_resource(pdev, IORESOURCE_IO, 0); + res = platform_get_resource(pdev, IORESOURCE_REG, 0); if (res == NULL) { - dev_err(&pdev->dev, "No I/O resource\n"); + dev_err(&pdev->dev, "No register resource\n"); ret = -EINVAL; goto err; } -- cgit v0.10.2