* [PATCH 0/4] Led driver support for LP5521 and LP5523 chips
@ 2010-09-16 10:12 Samu Onkalo
2010-09-16 10:12 ` [PATCH 2/4] leds: Driver for National Semiconductors LP5523 chip Samu Onkalo
` (2 more replies)
0 siblings, 3 replies; 13+ messages in thread
From: Samu Onkalo @ 2010-09-16 10:12 UTC (permalink / raw)
To: rpurdie; +Cc: linux-i2c, linux-kernel
Patch set provides support for LP5521 and LP5523 LED driver chips
from National Semicondutor. Both drivers supports programmable engines
and naturally LED class features.
Documentation is provided as a part of the patch set.
I created "leds" sub directory to Documentation.
Perhaps rest of the leds* documentation should be moved
there.
Datasheets are freely available at National Semiconductor www pages.
Tested to work in top of 2.6.36-RC4 kernel.
Samu Onkalo (4):
leds: driver for National Semiconductor LP5521 chip
leds: Driver for National Semiconductors LP5523 chip
leds: Update LP552x support Kconfig and Makefile
Documentation: led drivers lp5521 and lp5523
Documentation/leds/leds-lp5521.txt | 85 +++
Documentation/leds/leds-lp5523.txt | 81 +++
drivers/leds/Kconfig | 18 +
drivers/leds/Makefile | 2 +
drivers/leds/leds-lp5521.c | 820 +++++++++++++++++++++++++++
drivers/leds/leds-lp5523.c | 1063 ++++++++++++++++++++++++++++++++++++
include/linux/leds-lp5521.h | 46 ++
include/linux/leds-lp5523.h | 46 ++
8 files changed, 2161 insertions(+), 0 deletions(-)
create mode 100644 Documentation/leds/leds-lp5521.txt
create mode 100644 Documentation/leds/leds-lp5523.txt
create mode 100644 drivers/leds/leds-lp5521.c
create mode 100644 drivers/leds/leds-lp5523.c
create mode 100644 include/linux/leds-lp5521.h
create mode 100644 include/linux/leds-lp5523.h
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 2/4] leds: Driver for National Semiconductors LP5523 chip 2010-09-16 10:12 [PATCH 0/4] Led driver support for LP5521 and LP5523 chips Samu Onkalo @ 2010-09-16 10:12 ` Samu Onkalo 2010-09-27 10:54 ` Matti J. Aaltonen 2010-09-16 10:12 ` [PATCH 4/4] Documentation: led drivers lp5521 and lp5523 Samu Onkalo [not found] ` <1284631946-5350-1-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> 2 siblings, 1 reply; 13+ messages in thread From: Samu Onkalo @ 2010-09-16 10:12 UTC (permalink / raw) To: rpurdie; +Cc: linux-i2c, linux-kernel LP5523 chip is nine channel led driver with programmable engines. Driver provides support for that chip for direct access via led class or via programmable engines. Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> --- drivers/leds/leds-lp5523.c | 1063 +++++++++++++++++++++++++++++++++++++++++++ include/linux/leds-lp5523.h | 46 ++ 2 files changed, 1109 insertions(+), 0 deletions(-) create mode 100644 drivers/leds/leds-lp5523.c create mode 100644 include/linux/leds-lp5523.h diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c new file mode 100644 index 0000000..b255463 --- /dev/null +++ b/drivers/leds/leds-lp5523.c @@ -0,0 +1,1063 @@ +/* + * lp5523.c - LP5523 LED Driver + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> + * + * 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 + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/leds.h> +#include <linux/leds-lp5523.h> +#include <linux/workqueue.h> +#include <linux/slab.h> + +#define LP5523_REG_ENABLE 0x00 +#define LP5523_REG_OP_MODE 0x01 +#define LP5523_REG_RATIOMETRIC_MSB 0x02 +#define LP5523_REG_RATIOMETRIC_LSB 0x03 +#define LP5523_REG_ENABLE_LEDS_MSB 0x04 +#define LP5523_REG_ENABLE_LEDS_LSB 0x05 +#define LP5523_REG_LED_CNTRL_BASE 0x06 +#define LP5523_REG_LED_PWM_BASE 0x16 +#define LP5523_REG_LED_CURRENT_BASE 0x26 +#define LP5523_REG_CONFIG 0x36 +#define LP5523_REG_CHANNEL1_PC 0x37 +#define LP5523_REG_CHANNEL2_PC 0x38 +#define LP5523_REG_CHANNEL3_PC 0x39 +#define LP5523_REG_STATUS 0x3a +#define LP5523_REG_GPO 0x3b +#define LP5523_REG_VARIABLE 0x3c +#define LP5523_REG_RESET 0x3d +#define LP5523_REG_TEMP_CTRL 0x3e +#define LP5523_REG_TEMP_READ 0x3f +#define LP5523_REG_TEMP_WRITE 0x40 +#define LP5523_REG_LED_TEST_CTRL 0x41 +#define LP5523_REG_LED_TEST_ADC 0x42 +#define LP5523_REG_ENG1_VARIABLE 0x45 +#define LP5523_REG_ENG2_VARIABLE 0x46 +#define LP5523_REG_ENG3_VARIABLE 0x47 +#define LP5523_REG_MASTER_FADER1 0x48 +#define LP5523_REG_MASTER_FADER2 0x49 +#define LP5523_REG_MASTER_FADER3 0x4a +#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 + +#define LP5523_CMD_LOAD 0x15 /* 00010101 */ +#define LP5523_CMD_RUN 0x2a /* 00101010 */ +#define LP5523_CMD_DISABLED 0x00 /* 00000000 */ + +#define LP5523_ENABLE 0x40 +#define LP5523_AUTO_INC 0x40 +#define LP5523_PWR_SAVE 0x20 +#define LP5523_PWM_PWR_SAVE 0x04 +#define LP5523_CP_1 0x08 +#define LP5523_CP_1_5 0x10 +#define LP5523_CP_AUTO 0x18 +#define LP5523_INT_CLK 0x01 +#define LP5523_AUTO_CLK 0x02 +#define LP5523_EN_LEDTEST 0x80 +#define LP5523_LEDTEST_DONE 0x80 + +#define LP5523_DEFAULT_CURRENT 50 /* microAmps */ +#define LP5523_PROGRAM_LENGTH 32 /* in bytes */ +#define LP5523_PROGRAM_PAGES 6 +#define LP5523_ADC_SHORTCIRC_LIM 80 + +#define LP5523_LEDS 9 +#define LP5523_ENGINES 3 + +#define LP5523_ENG_MASK_BASE 0x30 /* 00110000 */ + +#define LP5523_ENG_STATUS_MASK 0x07 /* 00000111 */ + +#define LP5523_IRQ_FLAGS IRQF_TRIGGER_FALLING + +#define LP5523_EXT_CLK_USED 0x08 + +#define LED_ACTIVE(mux, led) (!!(mux & (0x0001 << led))) +#define SHIFT_MASK(id) (((id) - 1) * 2) + +struct lp5523_engine { + const struct attribute_group *attributes; + int id; + u8 mode; + u8 prog_page; + u8 mux_page; + u16 led_mux; + u8 engine_mask; +}; + +struct lp5523_led { + int id; + u8 chan_nr; + u8 led_current; + u8 max_current; + struct led_classdev cdev; + struct work_struct brightness_work; + u8 brightness; +}; + +struct lp5523_chip { + struct mutex lock; /* Serialize control */ + struct i2c_client *client; + struct lp5523_engine engines[LP5523_ENGINES]; + struct lp5523_led leds[LP5523_LEDS]; + struct lp5523_platform_data *pdata; + u8 num_channels; + u8 num_leds; +}; + +#define cdev_to_led(c) container_of(c, struct lp5523_led, cdev) + +static struct lp5523_chip *engine_to_lp5523(struct lp5523_engine *engine) +{ + return container_of(engine, struct lp5523_chip, + engines[engine->id - 1]); +} + +static struct lp5523_chip *led_to_lp5523(struct lp5523_led *led) +{ + return container_of(led, struct lp5523_chip, + leds[led->id]); +} + +static int lp5523_set_mode(struct lp5523_engine *engine, u8 mode); +static int lp5523_set_engine_mode(struct lp5523_engine *engine, u8 mode); +static int lp5523_load_program(struct lp5523_engine *engine, u8 *pattern); + +static void lp5523_led_brightness_work(struct work_struct *work); + +static int lp5523_write(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int lp5523_read(struct i2c_client *client, u8 reg, u8 *buf) +{ + s32 ret = i2c_smbus_read_byte_data(client, reg); + + if (ret < 0) + return -EIO; + + *buf = ret; + return 0; +} + +static int lp5523_detect(struct i2c_client *client) +{ + int ret; + u8 buf; + + ret = lp5523_write(client, LP5523_REG_ENABLE, 0x40); + if (ret) + return ret; + ret = lp5523_read(client, LP5523_REG_ENABLE, &buf); + if (ret) + return ret; + if (buf == 0x40) + return 0; + else + return -ENODEV; +} + +static int lp5523_configure(struct i2c_client *client) +{ + struct lp5523_chip *chip = i2c_get_clientdata(client); + int ret = 0; + u8 status; + + /* one pattern per engine setting led mux start and stop addresses */ + 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}, + }; + + lp5523_write(client, LP5523_REG_RESET, 0xff); + + usleep_range(10000, 100000); + + ret |= lp5523_write(client, LP5523_REG_ENABLE, LP5523_ENABLE); + /* Chip startup time after reset is 500 us */ + usleep_range(1000, 10000); + + ret |= lp5523_write(client, LP5523_REG_CONFIG, + LP5523_AUTO_INC | LP5523_PWR_SAVE | + LP5523_CP_AUTO | LP5523_AUTO_CLK | + LP5523_PWM_PWR_SAVE); + + /* turn on all leds */ + ret |= lp5523_write(client, LP5523_REG_ENABLE_LEDS_MSB, 0x01); + ret |= lp5523_write(client, LP5523_REG_ENABLE_LEDS_LSB, 0xff); + + /* hardcode 32 bytes of memory for each engine from program memory */ + ret |= lp5523_write(client, LP5523_REG_CH1_PROG_START, 0x00); + ret |= lp5523_write(client, LP5523_REG_CH2_PROG_START, 0x10); + ret |= lp5523_write(client, LP5523_REG_CH3_PROG_START, 0x20); + + /* write led mux address space for each channel */ + ret |= lp5523_load_program(&chip->engines[0], pattern[0]); + ret |= lp5523_load_program(&chip->engines[1], pattern[1]); + ret |= lp5523_load_program(&chip->engines[2], pattern[2]); + + if (ret) { + dev_err(&client->dev, "could not load mux programs\n"); + return -1; + } + + /* set all engines exec state and mode to run 00101010 */ + ret |= lp5523_write(client, LP5523_REG_ENABLE, + (LP5523_CMD_RUN | LP5523_ENABLE)); + + ret |= lp5523_write(client, LP5523_REG_OP_MODE, LP5523_CMD_RUN); + + if (ret) { + dev_err(&client->dev, "could not start mux programs\n"); + return -1; + } + + /* Wait 3ms and check the engine status */ + usleep_range(3000, 20000); + lp5523_read(client, LP5523_REG_STATUS, &status); + status &= LP5523_ENG_STATUS_MASK; + + if (status == LP5523_ENG_STATUS_MASK) { + dev_dbg(&client->dev, "all engines configured\n"); + } else { + dev_info(&client->dev, "status == %x\n", status); + dev_err(&client->dev, "cound not configure LED engine\n"); + return -1; + } + + dev_info(&client->dev, "disabling engines\n"); + + ret |= lp5523_write(client, LP5523_REG_OP_MODE, LP5523_CMD_DISABLED); + + return ret; +} + +static int lp5523_set_engine_mode(struct lp5523_engine *engine, u8 mode) +{ + struct lp5523_chip *chip = engine_to_lp5523(engine); + struct i2c_client *client = chip->client; + int ret; + u8 engine_state; + + ret = lp5523_read(client, LP5523_REG_OP_MODE, &engine_state); + if (ret) + goto fail; + + engine_state &= ~(engine->engine_mask); + + /* set mode only for this engine */ + mode &= engine->engine_mask; + + engine_state |= mode; + + ret |= lp5523_write(client, LP5523_REG_OP_MODE, engine_state); +fail: + return ret; +} + +static int lp5523_load_mux(struct lp5523_engine *engine, u16 mux) +{ + struct lp5523_chip *chip = engine_to_lp5523(engine); + struct i2c_client *client = chip->client; + int ret = 0; + + ret |= lp5523_set_engine_mode(engine, LP5523_CMD_LOAD); + + ret |= lp5523_write(client, LP5523_REG_PROG_PAGE_SEL, engine->mux_page); + ret |= lp5523_write(client, LP5523_REG_PROG_MEM, + (u8)(mux >> 8)); + ret |= lp5523_write(client, LP5523_REG_PROG_MEM + 1, (u8)(mux)); + engine->led_mux = mux; + + return ret; +} + +static int lp5523_load_program(struct lp5523_engine *engine, u8 *pattern) +{ + struct lp5523_chip *chip = engine_to_lp5523(engine); + struct i2c_client *client = chip->client; + + int ret = 0; + + ret |= lp5523_set_engine_mode(engine, LP5523_CMD_LOAD); + + ret |= lp5523_write(client, LP5523_REG_PROG_PAGE_SEL, + engine->prog_page); + ret |= i2c_smbus_write_i2c_block_data(client, LP5523_REG_PROG_MEM, + LP5523_PROGRAM_LENGTH, pattern); + + return ret; +} + +static int lp5523_run_program(struct lp5523_engine *engine) +{ + struct lp5523_chip *chip = engine_to_lp5523(engine); + struct i2c_client *client = chip->client; + int ret; + + ret = lp5523_write(client, LP5523_REG_ENABLE, + LP5523_CMD_RUN | LP5523_ENABLE); + if (ret) + goto fail; + + ret = lp5523_set_engine_mode(engine, LP5523_CMD_RUN); +fail: + return ret; +} + +static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len) +{ + int i; + u16 tmp_mux = 0; + len = len < LP5523_LEDS ? len : LP5523_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_LEDS; i++) + pos += sprintf(array + pos, "%x", LED_ACTIVE(led_mux, i)); + + array[pos] = '\0'; +} + +/*--------------------------------------------------------------*/ +/* Sysfs interface */ +/*--------------------------------------------------------------*/ + +static ssize_t show_engine_leds(struct device *dev, + struct device_attribute *attr, + char *buf, int nr) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lp5523_chip *chip = i2c_get_clientdata(client); + char mux[LP5523_LEDS + 1]; + + lp5523_mux_to_array(chip->engines[nr - 1].led_mux, mux); + + return sprintf(buf, "%s\n", mux); +} + +#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); \ +} +show_leds(1) +show_leds(2) +show_leds(3) + +static ssize_t store_engine_leds(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lp5523_chip *chip = i2c_get_clientdata(client); + u16 mux = 0; + + if (lp5523_mux_parse(buf, &mux, len)) + return -EINVAL; + + if (lp5523_load_mux(&chip->engines[nr - 1], mux)) + return -EINVAL; + + return len; +} + +#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); \ +} +store_leds(1) +store_leds(2) +store_leds(3) + +static ssize_t lp5523_selftest(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lp5523_chip *chip = i2c_get_clientdata(client); + int i, ret, pos = 0; + int led = 0; + u8 status, adc, vdd; + + mutex_lock(&chip->lock); + + ret = lp5523_read(chip->client, LP5523_REG_STATUS, &status); + if (ret < 0) + goto fail; + + /* Check that ext clock is really in use if requested */ + if ((chip->pdata) && (chip->pdata->clock_mode == LP5523_CLOCK_EXT)) + if ((status & LP5523_EXT_CLK_USED) == 0) + goto fail; + + /* Measure VDD (i.e. VBAT) first (channel 16 corresponds to VDD) */ + lp5523_write(chip->client, LP5523_REG_LED_TEST_CTRL, + LP5523_EN_LEDTEST | 16); + usleep_range(3000, 10000); + ret = lp5523_read(chip->client, LP5523_REG_STATUS, &status); + if (!(status & LP5523_LEDTEST_DONE)) + usleep_range(3000, 10000); + + ret |= lp5523_read(chip->client, LP5523_REG_LED_TEST_ADC, &vdd); + vdd--; /* There may be some fluctuation in measurement */ + + for (i = 0; i < LP5523_LEDS; i++) { + /* Skip non-existing channels */ + if (chip->pdata->led_config[i].led_current == 0) + continue; + + /* Set default current */ + lp5523_write(chip->client, + LP5523_REG_LED_CURRENT_BASE + i, + chip->pdata->led_config[i].led_current); + + lp5523_write(chip->client, LP5523_REG_LED_PWM_BASE + i, 0xff); + /* let current stabilize 2ms before measurements start */ + usleep_range(2000, 10000); + lp5523_write(chip->client, + LP5523_REG_LED_TEST_CTRL, + LP5523_EN_LEDTEST | i); + /* ledtest takes 2.7ms */ + usleep_range(3000, 10000); + ret = lp5523_read(chip->client, LP5523_REG_STATUS, &status); + if (!(status & LP5523_LEDTEST_DONE)) + usleep_range(3000, 10000); + ret |= lp5523_read(chip->client, LP5523_REG_LED_TEST_ADC, &adc); + + if (adc >= vdd || adc < LP5523_ADC_SHORTCIRC_LIM) + pos += sprintf(buf + pos, "LED %d FAIL\n", i); + + lp5523_write(chip->client, LP5523_REG_LED_PWM_BASE + i, 0x00); + + /* Restore current */ + lp5523_write(chip->client, + LP5523_REG_LED_CURRENT_BASE + i, + chip->leds[led].led_current); + led++; + } + if (pos == 0) + pos = sprintf(buf, "OK\n"); + goto release_lock; +fail: + pos = sprintf(buf, "FAIL\n"); + +release_lock: + mutex_unlock(&chip->lock); + + return pos; +} + +static void lp5523_set_brightness(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct lp5523_led *led = cdev_to_led(cdev); + + led->brightness = (u8)brightness; + + schedule_work(&led->brightness_work); +} + +static void lp5523_led_brightness_work(struct work_struct *work) +{ + struct lp5523_led *led = container_of(work, + struct lp5523_led, + brightness_work); + struct lp5523_chip *chip = led_to_lp5523(led); + struct i2c_client *client = chip->client; + + mutex_lock(&chip->lock); + + lp5523_write(client, LP5523_REG_LED_PWM_BASE + led->chan_nr, + led->brightness); + + mutex_unlock(&chip->lock); +} + +static int lp5523_do_store_load(struct lp5523_engine *engine, + const char *buf, size_t len) +{ + struct lp5523_chip *chip = engine_to_lp5523(engine); + struct i2c_client *client = chip->client; + int ret, nrchars, offset = 0, i = 0; + char c[3]; + unsigned cmd; + u8 pattern[LP5523_PROGRAM_LENGTH] = {0}; + + while ((offset < len - 1) && (i < LP5523_PROGRAM_LENGTH)) { + /* separate sscanfs because length is working only for %s */ + ret = sscanf(buf + offset, "%2s%n ", c, &nrchars); + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto fail; + pattern[i] = (u8)cmd; + + offset += nrchars; + i++; + } + + /* pattern commands are always two bytes long */ + if (i % 2) + goto fail; + + mutex_lock(&chip->lock); + + ret = lp5523_load_program(engine, pattern); + mutex_unlock(&chip->lock); + + if (ret) { + dev_err(&client->dev, "failed loading pattern\n"); + return ret; + } + + return len; +fail: + dev_err(&client->dev, "wrong pattern format\n"); + return -EINVAL; +} + +static ssize_t store_engine_load(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lp5523_chip *chip = i2c_get_clientdata(client); + return lp5523_do_store_load(&chip->engines[nr - 1], buf, len); +} + +#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); \ +} +store_load(1) +store_load(2) +store_load(3) + +static ssize_t show_engine_mode(struct device *dev, + struct device_attribute *attr, + char *buf, int nr) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lp5523_chip *chip = i2c_get_clientdata(client); + switch (chip->engines[nr - 1].mode) { + case LP5523_CMD_RUN: + return sprintf(buf, "run\n"); + case LP5523_CMD_LOAD: + return sprintf(buf, "load\n"); + case LP5523_CMD_DISABLED: + return sprintf(buf, "disabled\n"); + default: + return sprintf(buf, "disabled\n"); + } +} + +#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); \ +} +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 i2c_client *client = to_i2c_client(dev); + struct lp5523_chip *chip = i2c_get_clientdata(client); + struct lp5523_engine *engine = &chip->engines[nr - 1]; + mutex_lock(&chip->lock); + + if (!strncmp(buf, "run", 3)) + lp5523_set_mode(engine, LP5523_CMD_RUN); + else if (!strncmp(buf, "load", 4)) + lp5523_set_mode(engine, LP5523_CMD_LOAD); + else if (!strncmp(buf, "disabled", 8)) + lp5523_set_mode(engine, LP5523_CMD_DISABLED); + + mutex_unlock(&chip->lock); + return len; +} + +#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); \ +} +store_mode(1) +store_mode(2) +store_mode(3) + +static ssize_t show_max_current(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lp5523_led *led = cdev_to_led(led_cdev); + + return sprintf(buf, "%d\n", led->max_current); +} + +static ssize_t show_current(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lp5523_led *led = cdev_to_led(led_cdev); + + return sprintf(buf, "%d\n", led->led_current); +} + +static ssize_t store_current(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lp5523_led *led = cdev_to_led(led_cdev); + struct lp5523_chip *chip = led_to_lp5523(led); + ssize_t ret; + unsigned long curr; + + if (strict_strtoul(buf, 0, &curr)) + return -EINVAL; + + if (curr > led->max_current) + return -EINVAL; + + mutex_lock(&chip->lock); + ret = lp5523_write(chip->client, + LP5523_REG_LED_CURRENT_BASE + led->chan_nr, + (u8)curr); + mutex_unlock(&chip->lock); + + if (ret < 0) + return ret; + + led->led_current = (u8)curr; + + return len; +} + +/* led class device attributes */ +static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, show_current, store_current); +static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL); + +static struct attribute *lp5523_led_attributes[] = { + &dev_attr_led_current.attr, + &dev_attr_max_current.attr, + NULL, +}; + +static struct attribute_group lp5523_led_attribute_group = { + .attrs = lp5523_led_attributes +}; + +/* device attributes */ +static DEVICE_ATTR(engine1_mode, S_IRUGO | S_IWUGO, + show_engine1_mode, store_engine1_mode); +static DEVICE_ATTR(engine2_mode, S_IRUGO | S_IWUGO, + show_engine2_mode, store_engine2_mode); +static DEVICE_ATTR(engine3_mode, S_IRUGO | S_IWUGO, + show_engine3_mode, store_engine3_mode); +static DEVICE_ATTR(engine1_leds, S_IRUGO | S_IWUGO, + show_engine1_leds, store_engine1_leds); +static DEVICE_ATTR(engine2_leds, S_IRUGO | S_IWUGO, + show_engine2_leds, store_engine2_leds); +static DEVICE_ATTR(engine3_leds, S_IRUGO | S_IWUGO, + show_engine3_leds, store_engine3_leds); +static DEVICE_ATTR(engine1_load, S_IWUGO, NULL, store_engine1_load); +static DEVICE_ATTR(engine2_load, S_IWUGO, NULL, store_engine2_load); +static DEVICE_ATTR(engine3_load, S_IWUGO, NULL, store_engine3_load); +static DEVICE_ATTR(selftest, S_IRUGO, lp5523_selftest, NULL); + +static struct attribute *lp5523_attributes[] = { + &dev_attr_engine1_mode.attr, + &dev_attr_engine2_mode.attr, + &dev_attr_engine3_mode.attr, + &dev_attr_selftest.attr, + NULL +}; + +static struct attribute *lp5523_engine1_attributes[] = { + &dev_attr_engine1_load.attr, + &dev_attr_engine1_leds.attr, + NULL +}; + +static struct attribute *lp5523_engine2_attributes[] = { + &dev_attr_engine2_load.attr, + &dev_attr_engine2_leds.attr, + NULL +}; + +static struct attribute *lp5523_engine3_attributes[] = { + &dev_attr_engine3_load.attr, + &dev_attr_engine3_leds.attr, + NULL +}; + +static const struct attribute_group lp5523_group = { + .attrs = lp5523_attributes, +}; + +static const struct attribute_group lp5523_engine_group[] = { + {.attrs = lp5523_engine1_attributes }, + {.attrs = lp5523_engine2_attributes }, + {.attrs = lp5523_engine3_attributes }, +}; + +static int lp5523_register_sysfs(struct i2c_client *client) +{ + struct device *dev = &client->dev; + int ret; + + ret = sysfs_create_group(&dev->kobj, &lp5523_group); + if (ret < 0) + return ret; + + return 0; +} + +static void lp5523_unregister_sysfs(struct i2c_client *client) +{ + struct lp5523_chip *chip = i2c_get_clientdata(client); + struct device *dev = &client->dev; + int i; + + sysfs_remove_group(&dev->kobj, &lp5523_group); + + for (i = 0; i < LP5523_ENGINES; i++) { + if (chip->engines[i].mode == LP5523_CMD_LOAD) + sysfs_remove_group(&dev->kobj, &lp5523_engine_group[i]); + } + + for (i = 0; i < chip->num_leds; i++) + sysfs_remove_group(&chip->leds[i].cdev.dev->kobj, + &lp5523_led_attribute_group); +} + +/*--------------------------------------------------------------*/ +/* Set chip operating mode */ +/*--------------------------------------------------------------*/ +static int lp5523_set_mode(struct lp5523_engine *engine, u8 mode) +{ + /* engine to chip */ + struct lp5523_chip *chip = engine_to_lp5523(engine); + struct i2c_client *client = chip->client; + struct device *dev = &client->dev; + int ret = 0; + + /* if in that mode already do nothing, except for run */ + if (mode == engine->mode && mode != LP5523_CMD_RUN) + return 0; + + if (mode == LP5523_CMD_RUN) + ret = lp5523_run_program(engine); + + else if (mode == LP5523_CMD_LOAD) { + + lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED); + lp5523_set_engine_mode(engine, LP5523_CMD_LOAD); + + ret = sysfs_create_group(&dev->kobj, engine->attributes); + if (ret) + return ret; + } + + else if (mode == LP5523_CMD_DISABLED) + lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED); + + /* remove load attribute from sysfs if not in load mode */ + if (engine->mode == LP5523_CMD_LOAD && mode != LP5523_CMD_LOAD) + sysfs_remove_group(&dev->kobj, engine->attributes); + + engine->mode = mode; + + return ret; +} + +/*--------------------------------------------------------------*/ +/* Probe, Attach, Remove */ +/*--------------------------------------------------------------*/ +static int __init lp5523_init_engine(struct lp5523_engine *engine, int id) +{ + if (id < 1 || id > LP5523_ENGINES) + return -1; + engine->id = id; + engine->engine_mask = LP5523_ENG_MASK_BASE >> SHIFT_MASK(id); + engine->prog_page = id - 1; + engine->mux_page = id + 2; + engine->attributes = &lp5523_engine_group[id - 1]; + + return 0; +} + +static int __init lp5523_init_led(struct lp5523_led *led, struct device *dev, + int chan, struct lp5523_platform_data *pdata) +{ + char name[32]; + int res; + + if (chan >= LP5523_LEDS) + return -EINVAL; + + if (pdata->led_config[chan].led_current) { + led->led_current = pdata->led_config[chan].led_current; + led->max_current = pdata->led_config[chan].max_current; + led->chan_nr = pdata->led_config[chan].chan_nr; + + snprintf(name, 32, "lp5523:channel%d", chan); + + led->cdev.name = name; + led->cdev.brightness_set = lp5523_set_brightness; + res = led_classdev_register(dev, &led->cdev); + if (res < 0) { + dev_err(dev, "couldn't register led on channed %d\n", + chan); + return res; + } + res = sysfs_create_group(&led->cdev.dev->kobj, + &lp5523_led_attribute_group); + if (res < 0) { + dev_err(dev, "couldn't register current attribute\n"); + led_classdev_unregister(&led->cdev); + return res; + } + } else { + led->led_current = 0; + } + return 0; +} + +static struct i2c_driver lp5523_driver; + +static int lp5523_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lp5523_chip *chip; + struct lp5523_platform_data *pdata; + int ret, i, led; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + i2c_set_clientdata(client, chip); + chip->client = client; + + pdata = client->dev.platform_data; + + if (!pdata) { + dev_err(&client->dev, "no platform data\n"); + ret = -EINVAL; + goto fail1; + } + + mutex_init(&chip->lock); + + chip->pdata = pdata; + + if (pdata->setup_resources) { + ret = pdata->setup_resources(); + if (ret < 0) + goto fail1; + } + + if (pdata->enable) { + pdata->enable(0); + usleep_range(1000, 10000); + pdata->enable(1); + usleep_range(1000, 10000); /* Spec says min 500us */ + } + + ret = lp5523_detect(client); + if (ret) + goto fail2; + + dev_info(&client->dev, "LP5523 Programmable led chip found\n"); + + /* Initialize engines */ + for (i = 0; i < LP5523_ENGINES; i++) { + ret = lp5523_init_engine(&chip->engines[i], i + 1); + if (ret) { + dev_err(&client->dev, "error initializing engine\n"); + goto fail2; + } + } + ret = lp5523_configure(client); + if (ret < 0) { + dev_err(&client->dev, "error configuring chip\n"); + goto fail2; + } + + /* Initialize leds */ + chip->num_channels = pdata->num_channels; + chip->num_leds = 0; + led = 0; + for (i = 0; i < pdata->num_channels; i++) { + /* Do not initialize channels that are not connected */ + if (pdata->led_config[i].led_current == 0) + continue; + + chip->num_leds++; + ret = lp5523_init_led(&chip->leds[led], &client->dev, i, pdata); + if (ret) { + dev_err(&client->dev, "error initializing leds\n"); + goto fail3; + } + + chip->leds[led].id = led; + /* Set LED current */ + lp5523_write(client, + LP5523_REG_LED_CURRENT_BASE + chip->leds[led].chan_nr, + chip->leds[led].led_current); + + INIT_WORK(&(chip->leds[led].brightness_work), + lp5523_led_brightness_work); + + led++; + } + + ret = lp5523_register_sysfs(client); + if (ret) { + dev_err(&client->dev, "registering sysfs failed\n"); + goto fail3; + } + return ret; +fail3: + for (i = 0; i < chip->num_leds; i++) { + led_classdev_unregister(&chip->leds[i].cdev); + cancel_work_sync(&chip->leds[i].brightness_work); + } +fail2: + if (pdata->enable) + pdata->enable(0); + if (pdata->release_resources) + pdata->release_resources(); +fail1: + kfree(chip); + return ret; +} + +static int lp5523_remove(struct i2c_client *client) +{ + struct lp5523_chip *chip = i2c_get_clientdata(client); + int i; + + lp5523_unregister_sysfs(client); + + for (i = 0; i < chip->num_leds; i++) { + led_classdev_unregister(&chip->leds[i].cdev); + cancel_work_sync(&chip->leds[i].brightness_work); + } + + if (chip->pdata->enable) + chip->pdata->enable(0); + if (chip->pdata->release_resources) + chip->pdata->release_resources(); + kfree(chip); + return 0; +} + +static const struct i2c_device_id lp5523_id[] = { + { "lp5523", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, lp5523_id); + +static struct i2c_driver lp5523_driver = { + .driver = { + .name = "lp5523", + }, + .probe = lp5523_probe, + .remove = lp5523_remove, + .id_table = lp5523_id, +}; + +static int __init lp5523_init(void) +{ + int ret; + + ret = i2c_add_driver(&lp5523_driver); + + if (ret < 0) + printk(KERN_ALERT "Adding lp5523 driver failed\n"); + + return ret; +} + +static void __exit lp5523_exit(void) +{ + i2c_del_driver(&lp5523_driver); +} + +module_init(lp5523_init); +module_exit(lp5523_exit); + +MODULE_AUTHOR("Mathias Nyman <mathias.nyman@nokia.com>"); +MODULE_DESCRIPTION("LP5523 LED engine"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/leds-lp5523.h b/include/linux/leds-lp5523.h new file mode 100644 index 0000000..12979a9 --- /dev/null +++ b/include/linux/leds-lp5523.h @@ -0,0 +1,46 @@ +/* + * LP5523 LED Driver + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> + * + * 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_LP5523_H +#define __LINUX_LP5523_H + +struct lp5523_led_config { + u8 chan_nr; + u8 led_current; /* mA x10, 0 if led is not connected */ + u8 max_current; +}; + +#define LP5523_CLOCK_AUTO 0 +#define LP5523_CLOCK_INT 1 +#define LP5523_CLOCK_EXT 2 + +struct lp5523_platform_data { + struct lp5523_led_config *led_config; + u8 num_channels; + u8 clock_mode; + int (*setup_resources)(void); + void (*release_resources)(void); + void (*enable)(bool state); +}; + +#endif /* __LINUX_LP5523_H */ + -- 1.6.0.4 ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH 2/4] leds: Driver for National Semiconductors LP5523 chip 2010-09-16 10:12 ` [PATCH 2/4] leds: Driver for National Semiconductors LP5523 chip Samu Onkalo @ 2010-09-27 10:54 ` Matti J. Aaltonen [not found] ` <1285584868.8182.194.camel-U1ola594hmgZeDAa2SinrdBPR1lH4CV8@public.gmane.org> 0 siblings, 1 reply; 13+ messages in thread From: Matti J. Aaltonen @ 2010-09-27 10:54 UTC (permalink / raw) To: Onkalo Samu.P (Nokia-MS/Tampere) Cc: rpurdie@linux.intel.com, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org Moi. On Thu, 2010-09-16 at 12:12 +0200, Onkalo Samu.P (Nokia-MS/Tampere) wrote: > + > + for (i = 0; i < LP5523_ENGINES; i++) { > + if (chip->engines[i].mode == LP5523_CMD_LOAD) > + sysfs_remove_group(&dev->kobj, &lp5523_engine_group[i]); > + } > + > + for (i = 0; i < chip->num_leds; i++) > + sysfs_remove_group(&chip->leds[i].cdev.dev->kobj, > + &lp5523_led_attribute_group); > +} Tossa ylla on se asia, josta puhuttiin, style guide sanoo: Do not unnecessarily use braces where a single statement will do. if (condition) action(); Tossa ylla on kummankin for:in jalkeen single statement joista kumpikaan ei mahdu yhdelle riville... Mun nakemyksen mukaan ne molemmat olis yhta selkeita ilman sulkuja... > + if (mode == LP5523_CMD_RUN) > + ret = lp5523_run_program(engine); > + > + else if (mode == LP5523_CMD_LOAD) { > + > + lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED); > + lp5523_set_engine_mode(engine, LP5523_CMD_LOAD); > + > + ret = sysfs_create_group(&dev->kobj, engine->attributes); > + if (ret) > + return ret; > + } > + Aaltosulut myos if:n jalkeiseen statementtiin.... t.m.a. ^ permalink raw reply [flat|nested] 13+ messages in thread
[parent not found: <1285584868.8182.194.camel-U1ola594hmgZeDAa2SinrdBPR1lH4CV8@public.gmane.org>]
* Re: [PATCH 2/4] leds: Driver for National Semiconductors LP5523 chip [not found] ` <1285584868.8182.194.camel-U1ola594hmgZeDAa2SinrdBPR1lH4CV8@public.gmane.org> @ 2010-09-27 11:03 ` Matti J. Aaltonen 0 siblings, 0 replies; 13+ messages in thread From: Matti J. Aaltonen @ 2010-09-27 11:03 UTC (permalink / raw) To: Onkalo Samu.P (Nokia-MS/Tampere) Cc: rpurdie-VuQAYsv1563Yd54FQh9/CA@public.gmane.org, linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Hello. My original intention was to send my comment directly to Samu. So an apology to all of you who don't understand Finnish. Basically I was commenting the use braces in the code fragments below... Regards, Matti On Mon, 2010-09-27 at 13:54 +0300, Matti J. Aaltonen wrote: > Moi. > > On Thu, 2010-09-16 at 12:12 +0200, Onkalo Samu.P (Nokia-MS/Tampere) > wrote: > > + > > + for (i = 0; i < LP5523_ENGINES; i++) { > > + if (chip->engines[i].mode == LP5523_CMD_LOAD) > > + sysfs_remove_group(&dev->kobj, &lp5523_engine_group[i]); > > + } > > + > > + for (i = 0; i < chip->num_leds; i++) > > + sysfs_remove_group(&chip->leds[i].cdev.dev->kobj, > > + &lp5523_led_attribute_group); > > +} > > Tossa ylla on se asia, josta puhuttiin, style guide sanoo: > > Do not unnecessarily use braces where a single statement will do. > > if (condition) > action(); > > Tossa ylla on kummankin for:in jalkeen single statement joista kumpikaan > ei mahdu yhdelle riville... Mun nakemyksen mukaan ne molemmat olis yhta > selkeita ilman sulkuja... > > > > + if (mode == LP5523_CMD_RUN) > > + ret = lp5523_run_program(engine); > > + > > + else if (mode == LP5523_CMD_LOAD) { > > + > > + lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED); > > + lp5523_set_engine_mode(engine, LP5523_CMD_LOAD); > > + > > + ret = sysfs_create_group(&dev->kobj, engine->attributes); > > + if (ret) > > + return ret; > > + } > > + > > Aaltosulut myos if:n jalkeiseen statementtiin.... > > t.m.a. > > ^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 4/4] Documentation: led drivers lp5521 and lp5523 2010-09-16 10:12 [PATCH 0/4] Led driver support for LP5521 and LP5523 chips Samu Onkalo 2010-09-16 10:12 ` [PATCH 2/4] leds: Driver for National Semiconductors LP5523 chip Samu Onkalo @ 2010-09-16 10:12 ` Samu Onkalo [not found] ` <1284631946-5350-5-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> [not found] ` <1284631946-5350-1-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> 2 siblings, 1 reply; 13+ messages in thread From: Samu Onkalo @ 2010-09-16 10:12 UTC (permalink / raw) To: rpurdie; +Cc: linux-i2c, linux-kernel Create sub directory Documentation/leds and add short documentation for LP5521 and LP5523 drivers. Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com> --- Documentation/leds/leds-lp5521.txt | 85 ++++++++++++++++++++++++++++++++++++ Documentation/leds/leds-lp5523.txt | 81 ++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 0 deletions(-) create mode 100644 Documentation/leds/leds-lp5521.txt create mode 100644 Documentation/leds/leds-lp5523.txt diff --git a/Documentation/leds/leds-lp5521.txt b/Documentation/leds/leds-lp5521.txt new file mode 100644 index 0000000..62a8066 --- /dev/null +++ b/Documentation/leds/leds-lp5521.txt @@ -0,0 +1,85 @@ +Kernel driver for lp5521 +======================== + +* National Semiconductor LP5521 led driver chip +* Datasheet: http://www.national.com/pf/LP/LP5521.html + +Authors: Mathias Nyman, Yuri Zaporozhets, Samu Onkalo +Contact: Samu Onkalo (samu.p.onkalo-at-nokia.com) + +Description +----------- + +LP5521 can drive up to 3 channels. Leds can be controlled directly via +led class control interface. Channels have a generic names: +lp5521:channelx, where x is 0 .. 2 + +All three channels can be also controlled using engine micro programs. +More details of the instructions can be found from public data sheet. + +Control interface for 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 + +stop the engine: +echo "disabled" > engine3_mode + +sysfs contains also selftest entry. +It communicates with the chip and checks that +clock mode is automatically set to requested one. + +Each channel contains led current settings. +/sys/class/leds/lp5521:channel0/led_current - RW +/sys/class/leds/lp5521:channel0/max_current - RO +Format: 10x mA i.e 10 means 1.0 mA + +example platform data: +static struct lp5521_led_config lp5521_led_config[] = { + { + .chan_nr = 0, + .led_current = 50, + .max_current = 130, + }, { + .chan_nr = 1, + .led_current = 0, + .max_current = 130, + }, { + .chan_nr = 2, + .led_current = 0, + .max_current = 130, + } +}; + +static int lp5521_setup(void) +{ + /* setup HW resources */ +} + +static void lp5521_release(void) +{ + /* Release HW resources */ +} + +static void lp5521_enable(bool state) +{ + /* Control of chip enable signal */ +} + +static struct lp5521_platform_data lp5521_platform_data = { + .led_config = lp5521_led_config, + .num_channels = ARRAY_SIZE(lp5521_led_config), + .clock_mode = LP5521_CLOCK_EXT, + .setup_resources = lp5521_setup, + .release_resources = lp5521_release, + .enable = lp5521_enable, +}; + +If the current is set to 0 in the platform data, that channel is +disabled and it is not visible in the sysfs. diff --git a/Documentation/leds/leds-lp5523.txt b/Documentation/leds/leds-lp5523.txt new file mode 100644 index 0000000..915298d --- /dev/null +++ b/Documentation/leds/leds-lp5523.txt @@ -0,0 +1,81 @@ +Kernel driver for lp5523 +======================== + +* National Semiconductor LP5523 led driver chip +* Datasheet: http://www.national.com/pf/LP/LP5523.html + +Authors: Mathias Nyman, Yuri Zaporozhets, Samu Onkalo +Contact: Samu Onkalo (samu.p.onkalo-at-nokia.com) + +Description +----------- +LP5523 can drive up to 9 channels. Leds can be controlled directly via +led class control interface. Channels have a generic names: +lp5523:channelx where x is 0...8 + +Chip provides 3 engines which can be control channels without main CPU +interaction. Details of the micro engine code can be found from the +public data sheet. Leds can be muxed to different channels. + +Control interface for 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 + +sysfs contains also selftest entry. It measures each channel +voltage level and checks if it looks reasonable. Too high +level means missing led and to low value means short circuit. +Selftest uses always current from platform data. + +Each channel contains led current settings. +/sys/class/leds/lp5523:channel2/led_current - RW +/sys/class/leds/lp5523:channel2/max_current - RO +Format: 10x mA i.e 10 means 1.0 mA + +Example platform data: + +static struct lp5523_led_config lp5523_led_config[] = { + { + .chan_nr = 0, + .led_current = 50, + .max_current = 130, + }, +... + }, { + .chan_nr = 8, + .led_current = 50, + .max_current = 130, + } +}; + +static int lp5523_setup(void) +{ + /* Setup HW resources */ +} + +static void lp5523_release(void) +{ + /* Release HW resources */ +} + +static void lp5523_enable(bool state) +{ + /* Control chip enable signal */ +} + +static struct lp5523_platform_data lp5523_platform_data = { + .led_config = lp5523_led_config, + .num_channels = ARRAY_SIZE(lp5523_led_config), + .clock_mode = LP5523_CLOCK_EXT, + .setup_resources = lp5523_setup, + .release_resources = lp5523_release, + .enable = lp5523_enable, +}; + -- 1.6.0.4 ^ permalink raw reply related [flat|nested] 13+ messages in thread
[parent not found: <1284631946-5350-5-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>]
* Re: [PATCH 4/4] Documentation: led drivers lp5521 and lp5523 [not found] ` <1284631946-5350-5-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> @ 2010-09-22 18:14 ` matt mooney 0 siblings, 0 replies; 13+ messages in thread From: matt mooney @ 2010-09-22 18:14 UTC (permalink / raw) To: Samu Onkalo Cc: rpurdie-VuQAYsv1563Yd54FQh9/CA, linux-i2c-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA On 13:12 Thu 16 Sep , Samu Onkalo wrote: > + > +LP5521 can drive up to 3 channels. Leds can be controlled directly via [the] led class control interface. Channels have generic names: * The "a" after "have" was removed too. > +lp5521:channelx, where x is 0 .. 2 > + > +All three channels can be also controlled using engine micro programs. > +More details of the instructions can be found from public data sheet. Also, all three channels can be controlled using [the] micro-engine programs. More details of the instructions can be found from [the] public datasheet. > +Control interface for engines: Control interface for [the] engines: > + > +sysfs contains also selftest entry. sysfs contains a selftest entry. > +It communicates with the chip and checks that > +clock mode is automatically set to requested one. The test communicates with the chip and checks that [the] clock mode is automatically set to [the] requested one. > + > +Each channel contains led current settings. Each channel has its own led current settings. > +/sys/class/leds/lp5521:channel0/led_current - RW > +/sys/class/leds/lp5521:channel0/max_current - RO > +Format: 10x mA i.e 10 means 1.0 mA > + > +example platform data: > +static struct lp5521_led_config lp5521_led_config[] = { > + { > + .chan_nr = 0, > + .led_current = 50, > + .max_current = 130, > + }, { > + .chan_nr = 1, > + .led_current = 0, > + .max_current = 130, > + }, { > + .chan_nr = 2, > + .led_current = 0, > + .max_current = 130, > + } > +}; > + > +static int lp5521_setup(void) > +{ > + /* setup HW resources */ > +} > + > +static void lp5521_release(void) > +{ > + /* Release HW resources */ > +} > + > +static void lp5521_enable(bool state) > +{ > + /* Control of chip enable signal */ > +} > + > +static struct lp5521_platform_data lp5521_platform_data = { > + .led_config = lp5521_led_config, > + .num_channels = ARRAY_SIZE(lp5521_led_config), > + .clock_mode = LP5521_CLOCK_EXT, > + .setup_resources = lp5521_setup, > + .release_resources = lp5521_release, > + .enable = lp5521_enable, > +}; > + > +If the current is set to 0 in the platform data, that channel is > +disabled and it is not visible in the sysfs. > diff --git a/Documentation/leds/leds-lp5523.txt b/Documentation/leds/leds-lp5523.txt > new file mode 100644 > index 0000000..915298d > --- /dev/null > +++ b/Documentation/leds/leds-lp5523.txt > @@ -0,0 +1,81 @@ > +----------- > +LP5523 can drive up to 9 channels. Leds can be controlled directly via > +led class control interface. Channels have a generic names: > +lp5523:channelx where x is 0...8 Same as above > +Chip provides 3 engines which can be control channels without main CPU * The antecedent that "which" refers to above is the "chip." [The] chip provides 3 engines. Each engine can control a channel without interaction from the main CPU. > +interaction. Details of the micro engine code can be found from the > +public data sheet. Leds can be muxed to different channels. > + > +Control interface for 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 > + > +sysfs contains also selftest entry. It measures each channel > +voltage level and checks if it looks reasonable. Too high > +level means missing led and to low value means short circuit. If the level is too high, the led is missing; if the level is too low, there is a short circuit. > +Selftest uses always current from platform data. Selftest always uses [the] current from [the] platform data. -mfm ^ permalink raw reply [flat|nested] 13+ messages in thread
[parent not found: <1284631946-5350-1-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>]
* [PATCH 1/4] leds: driver for National Semiconductor LP5521 chip [not found] ` <1284631946-5350-1-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> @ 2010-09-16 10:12 ` Samu Onkalo [not found] ` <1284631946-5350-2-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> 2010-09-16 10:12 ` [PATCH 3/4] leds: Update LP552x support Kconfig and Makefile Samu Onkalo 2010-09-27 9:21 ` [PATCH 0/4] Led driver support for LP5521 and LP5523 chips samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w 2 siblings, 1 reply; 13+ messages in thread From: Samu Onkalo @ 2010-09-16 10:12 UTC (permalink / raw) To: rpurdie-VuQAYsv1563Yd54FQh9/CA Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA LP5521 chip is three channel led driver with programmable engines. Driver provides support for that chip for direct access via led class or via programmable engines. Signed-off-by: Samu Onkalo <samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> --- drivers/leds/leds-lp5521.c | 820 +++++++++++++++++++++++++++++++++++++++++++ include/linux/leds-lp5521.h | 46 +++ 2 files changed, 866 insertions(+), 0 deletions(-) create mode 100644 drivers/leds/leds-lp5521.c create mode 100644 include/linux/leds-lp5521.h diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c new file mode 100644 index 0000000..f57adb0 --- /dev/null +++ b/drivers/leds/leds-lp5521.c @@ -0,0 +1,820 @@ +/* + * LP5521 LED chip driver. + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Samu Onkalo <samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> + * + * 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 + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/leds.h> +#include <linux/leds-lp5521.h> +#include <linux/workqueue.h> +#include <linux/slab.h> + +#define LP5521_PROGRAM_LENGTH 32 /* in bytes */ + +#define LP5521_MAX_LEDS 3 /* Maximum number of LEDs */ +#define LP5521_MAX_ENGINES 3 /* Maximum number of engines */ + +#define LP5521_ENG_MASK_BASE 0x30 /* 00110000 */ +#define LP5521_ENG_STATUS_MASK 0x07 /* 00000111 */ + +#define LP5521_CMD_LOAD 0x15 /* 00010101 */ +#define LP5521_CMD_RUN 0x2a /* 00101010 */ +#define LP5521_CMD_DIRECT 0x3f /* 00111111 */ +#define LP5521_CMD_DISABLED 0x00 /* 00000000 */ + +/* Registers */ +#define LP5521_REG_ENABLE 0x00 +#define LP5521_REG_OP_MODE 0x01 +#define LP5521_REG_R_PWM 0x02 +#define LP5521_REG_G_PWM 0x03 +#define LP5521_REG_B_PWM 0x04 +#define LP5521_REG_R_CURRENT 0x05 +#define LP5521_REG_G_CURRENT 0x06 +#define LP5521_REG_B_CURRENT 0x07 +#define LP5521_REG_CONFIG 0x08 +#define LP5521_REG_R_CHANNEL_PC 0x09 +#define LP5521_REG_G_CHANNEL_PC 0x0A +#define LP5521_REG_B_CHANNEL_PC 0x0B +#define LP5521_REG_STATUS 0x0C +#define LP5521_REG_RESET 0x0D +#define LP5521_REG_GPO 0x0E +#define LP5521_REG_R_PROG_MEM 0x10 +#define LP5521_REG_G_PROG_MEM 0x30 +#define LP5521_REG_B_PROG_MEM 0x50 + +#define LP5521_PROG_MEM_BASE LP5521_REG_R_PROG_MEM +#define LP5521_PROG_MEM_SIZE 0x20 + +/* Base register to set LED current */ +#define LP5521_REG_LED_CURRENT_BASE LP5521_REG_R_CURRENT + +/* Base register to set the brightness */ +#define LP5521_REG_LED_PWM_BASE LP5521_REG_R_PWM + +/* Bits in ENABLE register */ +#define LP5521_MASTER_ENABLE 0x40 /* Chip master enable */ +#define LP5521_LOGARITHMIC_PWM 0x80 /* Logarithmic PWM adjustment */ +#define LP5521_EXEC_RUN 0x2A + +/* Bits in CONFIG register */ +#define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */ +#define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */ +#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */ +#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */ +#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */ +#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */ +#define LP5521_R_TO_BATT 4 /* R out: 0 = CP, 1 = Vbat */ +#define LP5521_CLK_SRC_EXT 0 /* Ext-clk source (CLK_32K) */ +#define LP5521_CLK_INT 1 /* Internal clock */ +#define LP5521_CLK_AUTO 2 /* Automatic clock selection */ + +/* Status */ +#define LP5521_EXT_CLK_USED 0x08 + +struct lp5521_engine { + const struct attribute_group *attributes; + int id; + u8 mode; + u8 prog_page; + u8 engine_mask; +}; + +struct lp5521_led { + int id; + u8 chan_nr; + u8 led_current; + u8 max_current; + struct led_classdev cdev; + struct work_struct brightness_work; + u8 brightness; +}; + +struct lp5521_chip { + struct lp5521_platform_data *pdata; + struct mutex lock; /* Serialize control */ + struct i2c_client *client; + struct lp5521_engine engines[LP5521_MAX_ENGINES]; + struct lp5521_led leds[LP5521_MAX_LEDS]; + u8 num_channels; + u8 num_leds; +}; + +#define cdev_to_led(c) container_of(c, struct lp5521_led, cdev) +#define engine_to_lp5521(eng) container_of((eng), struct lp5521_chip, \ + engines[(eng)->id - 1]) +#define led_to_lp5521(led) container_of((led), struct lp5521_chip, \ + leds[(led)->id]) + +static void lp5521_led_brightness_work(struct work_struct *work); + +static inline int lp5521_write(struct i2c_client *client, u8 reg, u8 value) +{ + return i2c_smbus_write_byte_data(client, reg, value); +} + +static int lp5521_read(struct i2c_client *client, u8 reg, u8 *buf) +{ + s32 ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + return -EIO; + + *buf = ret; + return 0; +} + +static int lp5521_set_engine_mode(struct lp5521_engine *engine, u8 mode) +{ + struct lp5521_chip *chip = engine_to_lp5521(engine); + struct i2c_client *client = chip->client; + int ret; + u8 engine_state; + + /* Only transition between RUN and DIRECT mode are handled here */ + if (mode == LP5521_CMD_LOAD) + return 0; + + if (mode == LP5521_CMD_DISABLED) + mode = LP5521_CMD_DIRECT; + + ret = lp5521_read(client, LP5521_REG_OP_MODE, &engine_state); + + /* set mode only for this engine */ + engine_state &= ~(engine->engine_mask); + mode &= engine->engine_mask; + engine_state |= mode; + ret |= lp5521_write(client, LP5521_REG_OP_MODE, engine_state); + + return ret; +} + +static int lp5521_load_program(struct lp5521_engine *eng, const u8 *pattern) +{ + struct lp5521_chip *chip = engine_to_lp5521(eng); + struct i2c_client *client = chip->client; + int ret; + int addr; + u8 mode; + + /* move current engine to direct mode and remember the state */ + ret = lp5521_set_engine_mode(eng, LP5521_CMD_DIRECT); + usleep_range(1000, 10000); + ret |= lp5521_read(client, LP5521_REG_OP_MODE, &mode); + + /* For loading, all the engines to load mode */ + lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); + usleep_range(1000, 10000); + lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_LOAD); + usleep_range(1000, 10000); + + addr = LP5521_PROG_MEM_BASE + eng->prog_page * LP5521_PROG_MEM_SIZE; + i2c_smbus_write_i2c_block_data(client, + addr, + LP5521_PROG_MEM_SIZE, + pattern); + + ret |= lp5521_write(client, LP5521_REG_OP_MODE, mode); + return ret; +} + +static int lp5521_set_led_current(struct lp5521_chip *chip, int led, u8 curr) +{ + return lp5521_write(chip->client, + LP5521_REG_LED_CURRENT_BASE + chip->leds[led].chan_nr, + curr); +} + +static void lp5521_init_engine(struct lp5521_chip *chip, + const struct attribute_group *attr_group) +{ + int i; + for (i = 0; i < LP5521_MAX_ENGINES; i++) { + chip->engines[i].id = i + 1; + chip->engines[i].engine_mask = LP5521_ENG_MASK_BASE >> (i * 2); + chip->engines[i].prog_page = i; + chip->engines[i].attributes = &attr_group[i]; + } +} + +static int lp5521_configure(struct i2c_client *client, + const struct attribute_group *attr_group) +{ + struct lp5521_chip *chip = i2c_get_clientdata(client); + int ret; + + lp5521_init_engine(chip, attr_group); + + lp5521_write(client, LP5521_REG_RESET, 0xff); + + usleep_range(10000, 20000); + + /* Set all PWMs to direct control mode */ + ret = lp5521_write(client, LP5521_REG_OP_MODE, 0x3F); + + /* Enable auto-powersave, set charge pump to auto, red to battery */ + ret |= lp5521_write(client, LP5521_REG_CONFIG, + LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT); + + /* Initialize all channels PWM to zero -> leds off */ + ret |= lp5521_write(client, LP5521_REG_R_PWM, 0); + ret |= lp5521_write(client, LP5521_REG_G_PWM, 0); + ret |= lp5521_write(client, LP5521_REG_B_PWM, 0); + + /* Set engines are set to run state when OP_MODE enables engines */ + ret |= lp5521_write(client, LP5521_REG_ENABLE, + LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM | + LP5521_EXEC_RUN); + /* enable takes 500us */ + usleep_range(500, 20000); + + return ret; +} + +static ssize_t lp5521_run_selftest(struct lp5521_chip *chip, char *buf) +{ + int ret; + u8 status; + + ret = lp5521_read(chip->client, LP5521_REG_STATUS, &status); + if (ret < 0) + goto fail; + + /* Check that ext clock is really in use if requested */ + if (chip->pdata && chip->pdata->clock_mode == LP5521_CLOCK_EXT) + if ((status & LP5521_EXT_CLK_USED) == 0) + goto fail; + + return sprintf(buf, "OK\n"); +fail: + return sprintf(buf, "FAIL\n"); +} + +static void lp5521_set_brightness(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct lp5521_led *led = cdev_to_led(cdev); + led->brightness = (u8)brightness; + schedule_work(&led->brightness_work); +} + +static void lp5521_led_brightness_work(struct work_struct *work) +{ + struct lp5521_led *led = container_of(work, + struct lp5521_led, + brightness_work); + struct lp5521_chip *chip = led_to_lp5521(led); + struct i2c_client *client = chip->client; + + mutex_lock(&chip->lock); + lp5521_write(client, LP5521_REG_LED_PWM_BASE + led->chan_nr, + led->brightness); + mutex_unlock(&chip->lock); +} + +/* Detect the chip by setting its ENABLE register and reading it back. */ +static int lp5521_detect(struct i2c_client *client) +{ + int ret; + u8 buf; + + ret = lp5521_write(client, LP5521_REG_ENABLE, + LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM); + if (ret) + return ret; + usleep_range(1000, 10000); + ret = lp5521_read(client, LP5521_REG_ENABLE, &buf); + if (ret) + return ret; + if (buf != (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM)) + return -ENODEV; + + return 0; +} + +/* Set engine mode and create appropriate sysfs attributes, if required. */ +static int lp5521_set_mode(struct lp5521_engine *engine, u8 mode) +{ + struct lp5521_chip *chip = engine_to_lp5521(engine); + struct i2c_client *client = chip->client; + struct device *dev = &client->dev; + int ret = 0; + + /* if in that mode already do nothing, except for run */ + if (mode == engine->mode && mode != LP5521_CMD_RUN) + return 0; + + if (mode == LP5521_CMD_RUN) + ret = lp5521_set_engine_mode(engine, LP5521_CMD_RUN); + + else if (mode == LP5521_CMD_LOAD) { + lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED); + lp5521_set_engine_mode(engine, LP5521_CMD_LOAD); + + ret = sysfs_create_group(&dev->kobj, engine->attributes); + if (ret) + return ret; + } + + else if (mode == LP5521_CMD_DISABLED) + lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED); + + /* remove load attribute from sysfs if not in load mode */ + if (engine->mode == LP5521_CMD_LOAD && mode != LP5521_CMD_LOAD) + sysfs_remove_group(&dev->kobj, engine->attributes); + + engine->mode = mode; + + return ret; +} + +static int lp5521_do_store_load(struct lp5521_engine *engine, + const char *buf, size_t len) +{ + struct lp5521_chip *chip = engine_to_lp5521(engine); + struct i2c_client *client = chip->client; + int ret, nrchars, offset = 0, i = 0; + char c[3]; + unsigned cmd; + u8 pattern[LP5521_PROGRAM_LENGTH] = {0}; + + while ((offset < len - 1) && (i < LP5521_PROGRAM_LENGTH)) { + /* separate sscanfs because length is working only for %s */ + ret = sscanf(buf + offset, "%2s%n ", c, &nrchars); + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto fail; + pattern[i] = (u8)cmd; + + offset += nrchars; + i++; + } + + /* Pattern commands are always two bytes long */ + if (i % 2) + goto fail; + + mutex_lock(&chip->lock); + ret = lp5521_load_program(engine, pattern); + mutex_unlock(&chip->lock); + + if (ret) { + dev_err(&client->dev, "failed loading pattern\n"); + return ret; + } + + return len; +fail: + dev_err(&client->dev, "wrong pattern format\n"); + return -EINVAL; +} + +static ssize_t store_engine_load(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lp5521_chip *chip = i2c_get_clientdata(client); + return lp5521_do_store_load(&chip->engines[nr - 1], buf, len); +} + +#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); \ +} +store_load(1) +store_load(2) +store_load(3) + +static ssize_t show_engine_mode(struct device *dev, + struct device_attribute *attr, + char *buf, int nr) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lp5521_chip *chip = i2c_get_clientdata(client); + switch (chip->engines[nr - 1].mode) { + case LP5521_CMD_RUN: + return sprintf(buf, "run\n"); + case LP5521_CMD_LOAD: + return sprintf(buf, "load\n"); + case LP5521_CMD_DISABLED: + return sprintf(buf, "disabled\n"); + default: + return sprintf(buf, "disabled\n"); + } +} + +#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); \ +} +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 i2c_client *client = to_i2c_client(dev); + struct lp5521_chip *chip = i2c_get_clientdata(client); + struct lp5521_engine *engine = &chip->engines[nr - 1]; + mutex_lock(&chip->lock); + + if (!strncmp(buf, "run", 3)) + lp5521_set_mode(engine, LP5521_CMD_RUN); + else if (!strncmp(buf, "load", 4)) + lp5521_set_mode(engine, LP5521_CMD_LOAD); + else if (!strncmp(buf, "disabled", 8)) + lp5521_set_mode(engine, LP5521_CMD_DISABLED); + + mutex_unlock(&chip->lock); + return len; +} + +#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); \ +} +store_mode(1) +store_mode(2) +store_mode(3) + +static ssize_t show_max_current(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lp5521_led *led = cdev_to_led(led_cdev); + + return sprintf(buf, "%d\n", led->max_current); +} + +static ssize_t show_current(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lp5521_led *led = cdev_to_led(led_cdev); + + return sprintf(buf, "%d\n", led->led_current); +} + +static ssize_t store_current(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lp5521_led *led = cdev_to_led(led_cdev); + struct lp5521_chip *chip = led_to_lp5521(led); + ssize_t ret; + unsigned long curr; + + if (strict_strtoul(buf, 0, &curr)) + return -EINVAL; + + if (curr > led->max_current) + return -EINVAL; + + mutex_lock(&chip->lock); + ret = lp5521_set_led_current(chip, led->id, curr); + mutex_unlock(&chip->lock); + + if (ret < 0) + return ret; + + led->led_current = (u8)curr; + + return len; +} + +static ssize_t lp5521_selftest(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct lp5521_chip *chip = i2c_get_clientdata(client); + ssize_t ret = 0; + + mutex_lock(&chip->lock); + ret = lp5521_run_selftest(chip, buf); + mutex_unlock(&chip->lock); + return ret; +} + +/* led class device attributes */ +static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, show_current, store_current); +static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL); + +static struct attribute *lp5521_led_attributes[] = { + &dev_attr_led_current.attr, + &dev_attr_max_current.attr, + NULL, +}; + +static struct attribute_group lp5521_led_attribute_group = { + .attrs = lp5521_led_attributes +}; + +/* device attributes */ +static DEVICE_ATTR(engine1_mode, S_IRUGO | S_IWUGO, + show_engine1_mode, store_engine1_mode); +static DEVICE_ATTR(engine2_mode, S_IRUGO | S_IWUGO, + show_engine2_mode, store_engine2_mode); +static DEVICE_ATTR(engine3_mode, S_IRUGO | S_IWUGO, + show_engine3_mode, store_engine3_mode); +static DEVICE_ATTR(engine1_load, S_IWUGO, NULL, store_engine1_load); +static DEVICE_ATTR(engine2_load, S_IWUGO, NULL, store_engine2_load); +static DEVICE_ATTR(engine3_load, S_IWUGO, NULL, store_engine3_load); +static DEVICE_ATTR(selftest, S_IRUGO, lp5521_selftest, NULL); + +static struct attribute *lp5521_attributes[] = { + &dev_attr_engine1_mode.attr, + &dev_attr_engine2_mode.attr, + &dev_attr_engine3_mode.attr, + &dev_attr_selftest.attr, + NULL +}; + +static struct attribute *lp5521_engine1_attributes[] = { + &dev_attr_engine1_load.attr, + NULL +}; + +static struct attribute *lp5521_engine2_attributes[] = { + &dev_attr_engine2_load.attr, + NULL +}; + +static struct attribute *lp5521_engine3_attributes[] = { + &dev_attr_engine3_load.attr, + NULL +}; + +static const struct attribute_group lp5521_group = { + .attrs = lp5521_attributes, +}; + +static const struct attribute_group lp5521_engine_group[] = { + {.attrs = lp5521_engine1_attributes }, + {.attrs = lp5521_engine2_attributes }, + {.attrs = lp5521_engine3_attributes }, +}; + +static int lp5521_register_sysfs(struct i2c_client *client) +{ + struct device *dev = &client->dev; + return sysfs_create_group(&dev->kobj, &lp5521_group); +} + +static void lp5521_unregister_sysfs(struct i2c_client *client) +{ + struct lp5521_chip *chip = i2c_get_clientdata(client); + struct device *dev = &client->dev; + int i; + + sysfs_remove_group(&dev->kobj, &lp5521_group); + + for (i = 0; i < LP5521_MAX_ENGINES; i++) { + if (chip->engines[i].mode == LP5521_CMD_LOAD) + sysfs_remove_group(&dev->kobj, + chip->engines[i].attributes); + } + + for (i = 0; i < chip->num_leds; i++) + sysfs_remove_group(&chip->leds[i].cdev.dev->kobj, + &lp5521_led_attribute_group); +} + +static int __init lp5521_init_led(struct lp5521_led *led, + struct i2c_client *client, + int chan, struct lp5521_platform_data *pdata) +{ + struct device *dev = &client->dev; + char name[32]; + int res; + + if (chan >= LP5521_MAX_LEDS) + return -EINVAL; + + if (pdata->led_config[chan].led_current == 0) + return 0; + + led->led_current = pdata->led_config[chan].led_current; + led->max_current = pdata->led_config[chan].max_current; + led->chan_nr = pdata->led_config[chan].chan_nr; + + snprintf(name, sizeof(name), "%s:channel%d", client->name, chan); + led->cdev.brightness_set = lp5521_set_brightness; + led->cdev.name = name; + res = led_classdev_register(dev, &led->cdev); + if (res < 0) { + dev_err(dev, "couldn't register led on channel %d\n", chan); + return res; + } + + res = sysfs_create_group(&led->cdev.dev->kobj, + &lp5521_led_attribute_group); + if (res < 0) { + dev_err(dev, "couldn't register current attribute\n"); + led_classdev_unregister(&led->cdev); + return res; + } + return 0; +} + +static int lp5521_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lp5521_chip *chip; + struct lp5521_platform_data *pdata; + int ret, i, led; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + i2c_set_clientdata(client, chip); + chip->client = client; + + pdata = client->dev.platform_data; + + if (!pdata) { + dev_err(&client->dev, "no platform data\n"); + ret = -EINVAL; + goto fail1; + } + + mutex_init(&chip->lock); + + chip->pdata = pdata; + + if (pdata->setup_resources) { + ret = pdata->setup_resources(); + if (ret < 0) + goto fail1; + } + + if (pdata->enable) { + pdata->enable(0); + usleep_range(1000, 10000); + pdata->enable(1); + usleep_range(1000, 10000); /* Spec says min 500us */ + } + + ret = lp5521_detect(client); + + if (ret) { + dev_err(&client->dev, "Chip not found\n"); + goto fail2; + } + + dev_info(&client->dev, "%s programmable led chip found\n", id->name); + + ret = lp5521_configure(client, lp5521_engine_group); + if (ret < 0) { + dev_err(&client->dev, "error configuring chip\n"); + goto fail2; + } + + /* Initialize leds */ + chip->num_channels = pdata->num_channels; + chip->num_leds = 0; + led = 0; + for (i = 0; i < pdata->num_channels; i++) { + /* Do not initialize channels that are not connected */ + if (pdata->led_config[i].led_current == 0) + continue; + + chip->num_leds++; + ret = lp5521_init_led(&chip->leds[led], client, i, pdata); + if (ret) { + dev_err(&client->dev, "error initializing leds\n"); + goto fail3; + } + + chip->leds[led].id = led; + /* Set initial LED current */ + lp5521_set_led_current(chip, led, + chip->leds[led].led_current); + + INIT_WORK(&(chip->leds[led].brightness_work), + lp5521_led_brightness_work); + + led++; + } + + ret = lp5521_register_sysfs(client); + if (ret) { + dev_err(&client->dev, "registering sysfs failed\n"); + goto fail3; + } + return ret; +fail3: + for (i = 0; i < chip->num_leds; i++) { + led_classdev_unregister(&chip->leds[i].cdev); + cancel_work_sync(&chip->leds[i].brightness_work); + } +fail2: + if (pdata->enable) + pdata->enable(0); + if (pdata->release_resources) + pdata->release_resources(); +fail1: + kfree(chip); + return ret; +} + +static int lp5521_remove(struct i2c_client *client) +{ + struct lp5521_chip *chip = i2c_get_clientdata(client); + int i; + + lp5521_unregister_sysfs(client); + + for (i = 0; i < chip->num_leds; i++) { + led_classdev_unregister(&chip->leds[i].cdev); + cancel_work_sync(&chip->leds[i].brightness_work); + } + + if (chip->pdata->enable) + chip->pdata->enable(0); + if (chip->pdata->release_resources) + chip->pdata->release_resources(); + kfree(chip); + return 0; +} + +static const struct i2c_device_id lp5521_id[] = { + { "lp5521", 0 }, /* Three channel chip */ + { } +}; +MODULE_DEVICE_TABLE(i2c, lp5521_id); + +static struct i2c_driver lp5521_driver = { + .driver = { + .name = "lp5521", + }, + .probe = lp5521_probe, + .remove = lp5521_remove, + .id_table = lp5521_id, +}; + +static int __init lp5521_init(void) +{ + int ret; + + ret = i2c_add_driver(&lp5521_driver); + + if (ret < 0) + printk(KERN_ALERT "Adding lp5521 driver failed\n"); + + return ret; +} + +static void __exit lp5521_exit(void) +{ + i2c_del_driver(&lp5521_driver); +} + +module_init(lp5521_init); +module_exit(lp5521_exit); + +MODULE_AUTHOR("Mathias Nyman, Yuri Zaporozhets, Samu Onkalo"); +MODULE_DESCRIPTION("LP5521 LED engine"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/leds-lp5521.h b/include/linux/leds-lp5521.h new file mode 100644 index 0000000..2d3aca2 --- /dev/null +++ b/include/linux/leds-lp5521.h @@ -0,0 +1,46 @@ +/* + * LP5521 LED chip driver. + * + * Copyright (C) 2010 Nokia Corporation + * + * Contact: Samu Onkalo <samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> + * + * 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_LP5521_H +#define __LINUX_LP5521_H + +struct lp5521_led_config { + u8 chan_nr; + u8 led_current; /* mA x10, 0 if led is not connected */ + u8 max_current; +}; + +#define LP5521_CLOCK_AUTO 0 +#define LP5521_CLOCK_INT 1 +#define LP5521_CLOCK_EXT 2 + +struct lp5521_platform_data { + struct lp5521_led_config *led_config; + u8 num_channels; + u8 clock_mode; + int (*setup_resources)(void); + void (*release_resources)(void); + void (*enable)(bool state); +}; + +#endif /* __LINUX_LP5521_H */ + -- 1.6.0.4 ^ permalink raw reply related [flat|nested] 13+ messages in thread
[parent not found: <1284631946-5350-2-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>]
* Re: [PATCH 1/4] leds: driver for National Semiconductor LP5521 chip [not found] ` <1284631946-5350-2-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> @ 2010-09-22 0:14 ` Ben Dooks 0 siblings, 0 replies; 13+ messages in thread From: Ben Dooks @ 2010-09-22 0:14 UTC (permalink / raw) To: Samu Onkalo Cc: rpurdie-VuQAYsv1563Yd54FQh9/CA, linux-i2c-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA On Thu, Sep 16, 2010 at 01:12:23PM +0300, Samu Onkalo wrote: > LP5521 chip is three channel led driver with programmable engines. > Driver provides support for that chip for direct access via led class or > via programmable engines. > > Signed-off-by: Samu Onkalo <samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> > --- > drivers/leds/leds-lp5521.c | 820 +++++++++++++++++++++++++++++++++++++++++++ > include/linux/leds-lp5521.h | 46 +++ > 2 files changed, 866 insertions(+), 0 deletions(-) > create mode 100644 drivers/leds/leds-lp5521.c > create mode 100644 include/linux/leds-lp5521.h > > diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c > new file mode 100644 > index 0000000..f57adb0 > --- /dev/null > +++ b/drivers/leds/leds-lp5521.c > @@ -0,0 +1,820 @@ > +/* > + * LP5521 LED chip driver. > + * > + * Copyright (C) 2010 Nokia Corporation > + * > + * Contact: Samu Onkalo <samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> > + * > + * 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 > + */ > + > +#include <linux/module.h> > +#include <linux/init.h> > +#include <linux/i2c.h> > +#include <linux/mutex.h> > +#include <linux/gpio.h> > +#include <linux/interrupt.h> > +#include <linux/delay.h> > +#include <linux/ctype.h> > +#include <linux/spinlock.h> > +#include <linux/wait.h> > +#include <linux/leds.h> > +#include <linux/leds-lp5521.h> > +#include <linux/workqueue.h> > +#include <linux/slab.h> > + > +#define LP5521_PROGRAM_LENGTH 32 /* in bytes */ > + > +#define LP5521_MAX_LEDS 3 /* Maximum number of LEDs */ > +#define LP5521_MAX_ENGINES 3 /* Maximum number of engines */ > + > +#define LP5521_ENG_MASK_BASE 0x30 /* 00110000 */ > +#define LP5521_ENG_STATUS_MASK 0x07 /* 00000111 */ > + > +#define LP5521_CMD_LOAD 0x15 /* 00010101 */ > +#define LP5521_CMD_RUN 0x2a /* 00101010 */ > +#define LP5521_CMD_DIRECT 0x3f /* 00111111 */ > +#define LP5521_CMD_DISABLED 0x00 /* 00000000 */ > + > +/* Registers */ > +#define LP5521_REG_ENABLE 0x00 > +#define LP5521_REG_OP_MODE 0x01 > +#define LP5521_REG_R_PWM 0x02 > +#define LP5521_REG_G_PWM 0x03 > +#define LP5521_REG_B_PWM 0x04 > +#define LP5521_REG_R_CURRENT 0x05 > +#define LP5521_REG_G_CURRENT 0x06 > +#define LP5521_REG_B_CURRENT 0x07 > +#define LP5521_REG_CONFIG 0x08 > +#define LP5521_REG_R_CHANNEL_PC 0x09 > +#define LP5521_REG_G_CHANNEL_PC 0x0A > +#define LP5521_REG_B_CHANNEL_PC 0x0B > +#define LP5521_REG_STATUS 0x0C > +#define LP5521_REG_RESET 0x0D > +#define LP5521_REG_GPO 0x0E > +#define LP5521_REG_R_PROG_MEM 0x10 > +#define LP5521_REG_G_PROG_MEM 0x30 > +#define LP5521_REG_B_PROG_MEM 0x50 > + > +#define LP5521_PROG_MEM_BASE LP5521_REG_R_PROG_MEM > +#define LP5521_PROG_MEM_SIZE 0x20 > + > +/* Base register to set LED current */ > +#define LP5521_REG_LED_CURRENT_BASE LP5521_REG_R_CURRENT > + > +/* Base register to set the brightness */ > +#define LP5521_REG_LED_PWM_BASE LP5521_REG_R_PWM > + > +/* Bits in ENABLE register */ > +#define LP5521_MASTER_ENABLE 0x40 /* Chip master enable */ > +#define LP5521_LOGARITHMIC_PWM 0x80 /* Logarithmic PWM adjustment */ > +#define LP5521_EXEC_RUN 0x2A > + > +/* Bits in CONFIG register */ > +#define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */ > +#define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */ > +#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */ > +#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */ > +#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */ > +#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */ > +#define LP5521_R_TO_BATT 4 /* R out: 0 = CP, 1 = Vbat */ > +#define LP5521_CLK_SRC_EXT 0 /* Ext-clk source (CLK_32K) */ > +#define LP5521_CLK_INT 1 /* Internal clock */ > +#define LP5521_CLK_AUTO 2 /* Automatic clock selection */ > + > +/* Status */ > +#define LP5521_EXT_CLK_USED 0x08 > + > +struct lp5521_engine { > + const struct attribute_group *attributes; > + int id; > + u8 mode; > + u8 prog_page; > + u8 engine_mask; > +}; > + > +struct lp5521_led { > + int id; > + u8 chan_nr; > + u8 led_current; > + u8 max_current; > + struct led_classdev cdev; > + struct work_struct brightness_work; > + u8 brightness; > +}; > + > +struct lp5521_chip { > + struct lp5521_platform_data *pdata; > + struct mutex lock; /* Serialize control */ > + struct i2c_client *client; > + struct lp5521_engine engines[LP5521_MAX_ENGINES]; > + struct lp5521_led leds[LP5521_MAX_LEDS]; > + u8 num_channels; > + u8 num_leds; > +}; > + > +#define cdev_to_led(c) container_of(c, struct lp5521_led, cdev) > +#define engine_to_lp5521(eng) container_of((eng), struct lp5521_chip, \ > + engines[(eng)->id - 1]) > +#define led_to_lp5521(led) container_of((led), struct lp5521_chip, \ > + leds[(led)->id]) > + > +static void lp5521_led_brightness_work(struct work_struct *work); > + > +static inline int lp5521_write(struct i2c_client *client, u8 reg, u8 value) > +{ > + return i2c_smbus_write_byte_data(client, reg, value); > +} > + > +static int lp5521_read(struct i2c_client *client, u8 reg, u8 *buf) > +{ > + s32 ret; > + > + ret = i2c_smbus_read_byte_data(client, reg); > + if (ret < 0) > + return -EIO; > + > + *buf = ret; > + return 0; > +} > + > +static int lp5521_set_engine_mode(struct lp5521_engine *engine, u8 mode) > +{ > + struct lp5521_chip *chip = engine_to_lp5521(engine); > + struct i2c_client *client = chip->client; > + int ret; > + u8 engine_state; > + > + /* Only transition between RUN and DIRECT mode are handled here */ > + if (mode == LP5521_CMD_LOAD) > + return 0; > + > + if (mode == LP5521_CMD_DISABLED) > + mode = LP5521_CMD_DIRECT; > + > + ret = lp5521_read(client, LP5521_REG_OP_MODE, &engine_state); > + > + /* set mode only for this engine */ > + engine_state &= ~(engine->engine_mask); > + mode &= engine->engine_mask; > + engine_state |= mode; > + ret |= lp5521_write(client, LP5521_REG_OP_MODE, engine_state); > + > + return ret; > +} > + > +static int lp5521_load_program(struct lp5521_engine *eng, const u8 *pattern) > +{ > + struct lp5521_chip *chip = engine_to_lp5521(eng); > + struct i2c_client *client = chip->client; > + int ret; > + int addr; > + u8 mode; > + > + /* move current engine to direct mode and remember the state */ > + ret = lp5521_set_engine_mode(eng, LP5521_CMD_DIRECT); > + usleep_range(1000, 10000); > + ret |= lp5521_read(client, LP5521_REG_OP_MODE, &mode); > + > + /* For loading, all the engines to load mode */ > + lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); > + usleep_range(1000, 10000); > + lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_LOAD); > + usleep_range(1000, 10000); > + > + addr = LP5521_PROG_MEM_BASE + eng->prog_page * LP5521_PROG_MEM_SIZE; > + i2c_smbus_write_i2c_block_data(client, > + addr, > + LP5521_PROG_MEM_SIZE, > + pattern); > + > + ret |= lp5521_write(client, LP5521_REG_OP_MODE, mode); > + return ret; > +} > + > +static int lp5521_set_led_current(struct lp5521_chip *chip, int led, u8 curr) > +{ > + return lp5521_write(chip->client, > + LP5521_REG_LED_CURRENT_BASE + chip->leds[led].chan_nr, > + curr); > +} > + > +static void lp5521_init_engine(struct lp5521_chip *chip, > + const struct attribute_group *attr_group) > +{ > + int i; > + for (i = 0; i < LP5521_MAX_ENGINES; i++) { you could use ARRAY_SIZE(chip->engines) ? > + chip->engines[i].id = i + 1; > + chip->engines[i].engine_mask = LP5521_ENG_MASK_BASE >> (i * 2); > + chip->engines[i].prog_page = i; > + chip->engines[i].attributes = &attr_group[i]; > + } > +} > + > +static int lp5521_configure(struct i2c_client *client, > + const struct attribute_group *attr_group) > +{ > + struct lp5521_chip *chip = i2c_get_clientdata(client); > + int ret; > + > + lp5521_init_engine(chip, attr_group); > + > + lp5521_write(client, LP5521_REG_RESET, 0xff); > + > + usleep_range(10000, 20000); > + > + /* Set all PWMs to direct control mode */ > + ret = lp5521_write(client, LP5521_REG_OP_MODE, 0x3F); > + > + /* Enable auto-powersave, set charge pump to auto, red to battery */ > + ret |= lp5521_write(client, LP5521_REG_CONFIG, > + LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT); > + > + /* Initialize all channels PWM to zero -> leds off */ > + ret |= lp5521_write(client, LP5521_REG_R_PWM, 0); > + ret |= lp5521_write(client, LP5521_REG_G_PWM, 0); > + ret |= lp5521_write(client, LP5521_REG_B_PWM, 0); > + > + /* Set engines are set to run state when OP_MODE enables engines */ > + ret |= lp5521_write(client, LP5521_REG_ENABLE, > + LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM | > + LP5521_EXEC_RUN); > + /* enable takes 500us */ > + usleep_range(500, 20000); > + > + return ret; > +} > + > +static ssize_t lp5521_run_selftest(struct lp5521_chip *chip, char *buf) > +{ > + int ret; > + u8 status; > + > + ret = lp5521_read(chip->client, LP5521_REG_STATUS, &status); > + if (ret < 0) > + goto fail; > + > + /* Check that ext clock is really in use if requested */ > + if (chip->pdata && chip->pdata->clock_mode == LP5521_CLOCK_EXT) > + if ((status & LP5521_EXT_CLK_USED) == 0) > + goto fail; > + > + return sprintf(buf, "OK\n"); > +fail: > + return sprintf(buf, "FAIL\n"); > +} why not return an error code here? > +static void lp5521_set_brightness(struct led_classdev *cdev, > + enum led_brightness brightness) > +{ > + struct lp5521_led *led = cdev_to_led(cdev); > + led->brightness = (u8)brightness; > + schedule_work(&led->brightness_work); > +} > + > +static void lp5521_led_brightness_work(struct work_struct *work) > +{ > + struct lp5521_led *led = container_of(work, > + struct lp5521_led, > + brightness_work); > + struct lp5521_chip *chip = led_to_lp5521(led); > + struct i2c_client *client = chip->client; > + > + mutex_lock(&chip->lock); > + lp5521_write(client, LP5521_REG_LED_PWM_BASE + led->chan_nr, > + led->brightness); > + mutex_unlock(&chip->lock); > +} > + > +/* Detect the chip by setting its ENABLE register and reading it back. */ > +static int lp5521_detect(struct i2c_client *client) > +{ > + int ret; > + u8 buf; > + > + ret = lp5521_write(client, LP5521_REG_ENABLE, > + LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM); > + if (ret) > + return ret; > + usleep_range(1000, 10000); > + ret = lp5521_read(client, LP5521_REG_ENABLE, &buf); > + if (ret) > + return ret; > + if (buf != (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM)) > + return -ENODEV; > + > + return 0; > +} > + > +/* Set engine mode and create appropriate sysfs attributes, if required. */ > +static int lp5521_set_mode(struct lp5521_engine *engine, u8 mode) > +{ > + struct lp5521_chip *chip = engine_to_lp5521(engine); > + struct i2c_client *client = chip->client; > + struct device *dev = &client->dev; > + int ret = 0; > + > + /* if in that mode already do nothing, except for run */ > + if (mode == engine->mode && mode != LP5521_CMD_RUN) > + return 0; > + > + if (mode == LP5521_CMD_RUN) > + ret = lp5521_set_engine_mode(engine, LP5521_CMD_RUN); > + > + else if (mode == LP5521_CMD_LOAD) { > + lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED); > + lp5521_set_engine_mode(engine, LP5521_CMD_LOAD); > + > + ret = sysfs_create_group(&dev->kobj, engine->attributes); > + if (ret) > + return ret; > + } > + > + else if (mode == LP5521_CMD_DISABLED) > + lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED); > + > + /* remove load attribute from sysfs if not in load mode */ > + if (engine->mode == LP5521_CMD_LOAD && mode != LP5521_CMD_LOAD) > + sysfs_remove_group(&dev->kobj, engine->attributes); > + > + engine->mode = mode; > + > + return ret; > +} > + > +static int lp5521_do_store_load(struct lp5521_engine *engine, > + const char *buf, size_t len) > +{ > + struct lp5521_chip *chip = engine_to_lp5521(engine); > + struct i2c_client *client = chip->client; > + int ret, nrchars, offset = 0, i = 0; > + char c[3]; > + unsigned cmd; > + u8 pattern[LP5521_PROGRAM_LENGTH] = {0}; > + > + while ((offset < len - 1) && (i < LP5521_PROGRAM_LENGTH)) { > + /* separate sscanfs because length is working only for %s */ > + ret = sscanf(buf + offset, "%2s%n ", c, &nrchars); > + ret = sscanf(c, "%2x", &cmd); > + if (ret != 1) > + goto fail; > + pattern[i] = (u8)cmd; > + > + offset += nrchars; > + i++; > + } > + > + /* Pattern commands are always two bytes long */ > + if (i % 2) > + goto fail; > + > + mutex_lock(&chip->lock); > + ret = lp5521_load_program(engine, pattern); > + mutex_unlock(&chip->lock); > + > + if (ret) { > + dev_err(&client->dev, "failed loading pattern\n"); > + return ret; > + } > + > + return len; > +fail: > + dev_err(&client->dev, "wrong pattern format\n"); > + return -EINVAL; > +} > + > +static ssize_t store_engine_load(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len, int nr) > +{ > + struct i2c_client *client = to_i2c_client(dev); > + struct lp5521_chip *chip = i2c_get_clientdata(client); > + return lp5521_do_store_load(&chip->engines[nr - 1], buf, len); > +} > + > +#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); \ > +} > +store_load(1) > +store_load(2) > +store_load(3) > + > +static ssize_t show_engine_mode(struct device *dev, > + struct device_attribute *attr, > + char *buf, int nr) > +{ > + struct i2c_client *client = to_i2c_client(dev); > + struct lp5521_chip *chip = i2c_get_clientdata(client); > + switch (chip->engines[nr - 1].mode) { > + case LP5521_CMD_RUN: > + return sprintf(buf, "run\n"); > + case LP5521_CMD_LOAD: > + return sprintf(buf, "load\n"); > + case LP5521_CMD_DISABLED: > + return sprintf(buf, "disabled\n"); > + default: > + return sprintf(buf, "disabled\n"); > + } > +} > + > +#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); \ > +} > +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 i2c_client *client = to_i2c_client(dev); > + struct lp5521_chip *chip = i2c_get_clientdata(client); > + struct lp5521_engine *engine = &chip->engines[nr - 1]; > + mutex_lock(&chip->lock); > + > + if (!strncmp(buf, "run", 3)) > + lp5521_set_mode(engine, LP5521_CMD_RUN); > + else if (!strncmp(buf, "load", 4)) > + lp5521_set_mode(engine, LP5521_CMD_LOAD); > + else if (!strncmp(buf, "disabled", 8)) > + lp5521_set_mode(engine, LP5521_CMD_DISABLED); > + > + mutex_unlock(&chip->lock); > + return len; > +} > + > +#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); \ > +} > +store_mode(1) > +store_mode(2) > +store_mode(3) > + > +static ssize_t show_max_current(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct led_classdev *led_cdev = dev_get_drvdata(dev); > + struct lp5521_led *led = cdev_to_led(led_cdev); > + > + return sprintf(buf, "%d\n", led->max_current); > +} > + > +static ssize_t show_current(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct led_classdev *led_cdev = dev_get_drvdata(dev); > + struct lp5521_led *led = cdev_to_led(led_cdev); > + > + return sprintf(buf, "%d\n", led->led_current); > +} > + > +static ssize_t store_current(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t len) > +{ > + struct led_classdev *led_cdev = dev_get_drvdata(dev); > + struct lp5521_led *led = cdev_to_led(led_cdev); > + struct lp5521_chip *chip = led_to_lp5521(led); > + ssize_t ret; > + unsigned long curr; > + > + if (strict_strtoul(buf, 0, &curr)) > + return -EINVAL; > + > + if (curr > led->max_current) > + return -EINVAL; > + > + mutex_lock(&chip->lock); > + ret = lp5521_set_led_current(chip, led->id, curr); > + mutex_unlock(&chip->lock); > + > + if (ret < 0) > + return ret; > + > + led->led_current = (u8)curr; > + > + return len; > +} > + > +static ssize_t lp5521_selftest(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct i2c_client *client = to_i2c_client(dev); > + struct lp5521_chip *chip = i2c_get_clientdata(client); > + ssize_t ret = 0; > + > + mutex_lock(&chip->lock); > + ret = lp5521_run_selftest(chip, buf); > + mutex_unlock(&chip->lock); > + return ret; > +} > + > +/* led class device attributes */ > +static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, show_current, store_current); > +static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL); > + > +static struct attribute *lp5521_led_attributes[] = { > + &dev_attr_led_current.attr, > + &dev_attr_max_current.attr, > + NULL, > +}; > + > +static struct attribute_group lp5521_led_attribute_group = { > + .attrs = lp5521_led_attributes > +}; > + > +/* device attributes */ > +static DEVICE_ATTR(engine1_mode, S_IRUGO | S_IWUGO, > + show_engine1_mode, store_engine1_mode); > +static DEVICE_ATTR(engine2_mode, S_IRUGO | S_IWUGO, > + show_engine2_mode, store_engine2_mode); > +static DEVICE_ATTR(engine3_mode, S_IRUGO | S_IWUGO, > + show_engine3_mode, store_engine3_mode); > +static DEVICE_ATTR(engine1_load, S_IWUGO, NULL, store_engine1_load); > +static DEVICE_ATTR(engine2_load, S_IWUGO, NULL, store_engine2_load); > +static DEVICE_ATTR(engine3_load, S_IWUGO, NULL, store_engine3_load); > +static DEVICE_ATTR(selftest, S_IRUGO, lp5521_selftest, NULL); > + > +static struct attribute *lp5521_attributes[] = { > + &dev_attr_engine1_mode.attr, > + &dev_attr_engine2_mode.attr, > + &dev_attr_engine3_mode.attr, > + &dev_attr_selftest.attr, > + NULL > +}; > + > +static struct attribute *lp5521_engine1_attributes[] = { > + &dev_attr_engine1_load.attr, > + NULL > +}; > + > +static struct attribute *lp5521_engine2_attributes[] = { > + &dev_attr_engine2_load.attr, > + NULL > +}; > + > +static struct attribute *lp5521_engine3_attributes[] = { > + &dev_attr_engine3_load.attr, > + NULL > +}; > + > +static const struct attribute_group lp5521_group = { > + .attrs = lp5521_attributes, > +}; > + > +static const struct attribute_group lp5521_engine_group[] = { > + {.attrs = lp5521_engine1_attributes }, > + {.attrs = lp5521_engine2_attributes }, > + {.attrs = lp5521_engine3_attributes }, > +}; > + > +static int lp5521_register_sysfs(struct i2c_client *client) > +{ > + struct device *dev = &client->dev; > + return sysfs_create_group(&dev->kobj, &lp5521_group); > +} > + > +static void lp5521_unregister_sysfs(struct i2c_client *client) > +{ > + struct lp5521_chip *chip = i2c_get_clientdata(client); > + struct device *dev = &client->dev; > + int i; > + > + sysfs_remove_group(&dev->kobj, &lp5521_group); > + > + for (i = 0; i < LP5521_MAX_ENGINES; i++) { > + if (chip->engines[i].mode == LP5521_CMD_LOAD) > + sysfs_remove_group(&dev->kobj, > + chip->engines[i].attributes); > + } > + > + for (i = 0; i < chip->num_leds; i++) > + sysfs_remove_group(&chip->leds[i].cdev.dev->kobj, > + &lp5521_led_attribute_group); > +} > + > +static int __init lp5521_init_led(struct lp5521_led *led, > + struct i2c_client *client, > + int chan, struct lp5521_platform_data *pdata) > +{ > + struct device *dev = &client->dev; > + char name[32]; > + int res; > + > + if (chan >= LP5521_MAX_LEDS) > + return -EINVAL; > + > + if (pdata->led_config[chan].led_current == 0) > + return 0; > + > + led->led_current = pdata->led_config[chan].led_current; > + led->max_current = pdata->led_config[chan].max_current; > + led->chan_nr = pdata->led_config[chan].chan_nr; > + > + snprintf(name, sizeof(name), "%s:channel%d", client->name, chan); > + led->cdev.brightness_set = lp5521_set_brightness; > + led->cdev.name = name; > + res = led_classdev_register(dev, &led->cdev); > + if (res < 0) { > + dev_err(dev, "couldn't register led on channel %d\n", chan); > + return res; > + } > + > + res = sysfs_create_group(&led->cdev.dev->kobj, > + &lp5521_led_attribute_group); > + if (res < 0) { > + dev_err(dev, "couldn't register current attribute\n"); > + led_classdev_unregister(&led->cdev); > + return res; > + } > + return 0; > +} > + > +static int lp5521_probe(struct i2c_client *client, > + const struct i2c_device_id *id) > +{ > + struct lp5521_chip *chip; > + struct lp5521_platform_data *pdata; > + int ret, i, led; > + > + chip = kzalloc(sizeof(*chip), GFP_KERNEL); > + if (!chip) > + return -ENOMEM; > + > + i2c_set_clientdata(client, chip); > + chip->client = client; > + > + pdata = client->dev.platform_data; > + > + if (!pdata) { > + dev_err(&client->dev, "no platform data\n"); > + ret = -EINVAL; > + goto fail1; > + } > + > + mutex_init(&chip->lock); > + > + chip->pdata = pdata; > + > + if (pdata->setup_resources) { > + ret = pdata->setup_resources(); > + if (ret < 0) > + goto fail1; > + } > + > + if (pdata->enable) { > + pdata->enable(0); > + usleep_range(1000, 10000); > + pdata->enable(1); > + usleep_range(1000, 10000); /* Spec says min 500us */ > + } > + > + ret = lp5521_detect(client); > + > + if (ret) { > + dev_err(&client->dev, "Chip not found\n"); > + goto fail2; > + } > + > + dev_info(&client->dev, "%s programmable led chip found\n", id->name); > + > + ret = lp5521_configure(client, lp5521_engine_group); > + if (ret < 0) { > + dev_err(&client->dev, "error configuring chip\n"); > + goto fail2; > + } > + > + /* Initialize leds */ > + chip->num_channels = pdata->num_channels; > + chip->num_leds = 0; > + led = 0; > + for (i = 0; i < pdata->num_channels; i++) { > + /* Do not initialize channels that are not connected */ > + if (pdata->led_config[i].led_current == 0) > + continue; > + > + chip->num_leds++; > + ret = lp5521_init_led(&chip->leds[led], client, i, pdata); > + if (ret) { > + dev_err(&client->dev, "error initializing leds\n"); > + goto fail3; > + } > + > + chip->leds[led].id = led; > + /* Set initial LED current */ > + lp5521_set_led_current(chip, led, > + chip->leds[led].led_current); > + > + INIT_WORK(&(chip->leds[led].brightness_work), > + lp5521_led_brightness_work); > + > + led++; > + } > + > + ret = lp5521_register_sysfs(client); > + if (ret) { > + dev_err(&client->dev, "registering sysfs failed\n"); > + goto fail3; > + } > + return ret; > +fail3: > + for (i = 0; i < chip->num_leds; i++) { > + led_classdev_unregister(&chip->leds[i].cdev); > + cancel_work_sync(&chip->leds[i].brightness_work); > + } > +fail2: > + if (pdata->enable) > + pdata->enable(0); > + if (pdata->release_resources) > + pdata->release_resources(); > +fail1: > + kfree(chip); > + return ret; > +} > + > +static int lp5521_remove(struct i2c_client *client) > +{ > + struct lp5521_chip *chip = i2c_get_clientdata(client); > + int i; > + > + lp5521_unregister_sysfs(client); > + > + for (i = 0; i < chip->num_leds; i++) { > + led_classdev_unregister(&chip->leds[i].cdev); > + cancel_work_sync(&chip->leds[i].brightness_work); > + } > + > + if (chip->pdata->enable) > + chip->pdata->enable(0); > + if (chip->pdata->release_resources) > + chip->pdata->release_resources(); > + kfree(chip); > + return 0; > +} > + > +static const struct i2c_device_id lp5521_id[] = { > + { "lp5521", 0 }, /* Three channel chip */ > + { } > +}; > +MODULE_DEVICE_TABLE(i2c, lp5521_id); > + > +static struct i2c_driver lp5521_driver = { > + .driver = { > + .name = "lp5521", > + }, > + .probe = lp5521_probe, > + .remove = lp5521_remove, > + .id_table = lp5521_id, > +}; > + > +static int __init lp5521_init(void) > +{ > + int ret; > + > + ret = i2c_add_driver(&lp5521_driver); > + > + if (ret < 0) > + printk(KERN_ALERT "Adding lp5521 driver failed\n"); > + > + return ret; > +} > + > +static void __exit lp5521_exit(void) > +{ > + i2c_del_driver(&lp5521_driver); > +} > + > +module_init(lp5521_init); > +module_exit(lp5521_exit); > + > +MODULE_AUTHOR("Mathias Nyman, Yuri Zaporozhets, Samu Onkalo"); > +MODULE_DESCRIPTION("LP5521 LED engine"); > +MODULE_LICENSE("GPL v2"); > diff --git a/include/linux/leds-lp5521.h b/include/linux/leds-lp5521.h > new file mode 100644 > index 0000000..2d3aca2 > --- /dev/null > +++ b/include/linux/leds-lp5521.h > @@ -0,0 +1,46 @@ > +/* > + * LP5521 LED chip driver. > + * > + * Copyright (C) 2010 Nokia Corporation > + * > + * Contact: Samu Onkalo <samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> > + * > + * 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_LP5521_H > +#define __LINUX_LP5521_H > + > +struct lp5521_led_config { > + u8 chan_nr; > + u8 led_current; /* mA x10, 0 if led is not connected */ > + u8 max_current; > +}; documentation would be useful. kerneldoc fo preference. > +#define LP5521_CLOCK_AUTO 0 > +#define LP5521_CLOCK_INT 1 > +#define LP5521_CLOCK_EXT 2 > + > +struct lp5521_platform_data { > + struct lp5521_led_config *led_config; > + u8 num_channels; > + u8 clock_mode; > + int (*setup_resources)(void); > + void (*release_resources)(void); > + void (*enable)(bool state); > +}; > + > +#endif /* __LINUX_LP5521_H */ > + > -- > 1.6.0.4 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-i2c" in > the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org > More majordomo info at http://vger.kernel.org/majordomo-info.html -- -- Ben Q: What's a light-year? A: One-third less calories than a regular year. ^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 3/4] leds: Update LP552x support Kconfig and Makefile [not found] ` <1284631946-5350-1-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> 2010-09-16 10:12 ` [PATCH 1/4] leds: driver for National Semiconductor LP5521 chip Samu Onkalo @ 2010-09-16 10:12 ` Samu Onkalo 2010-09-27 9:21 ` [PATCH 0/4] Led driver support for LP5521 and LP5523 chips samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w 2 siblings, 0 replies; 13+ messages in thread From: Samu Onkalo @ 2010-09-16 10:12 UTC (permalink / raw) To: rpurdie-VuQAYsv1563Yd54FQh9/CA Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA Provide configuration and compilation support for LP5521 and LP5523 Signed-off-by: Samu Onkalo <samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> --- drivers/leds/Kconfig | 18 ++++++++++++++++++ drivers/leds/Makefile | 2 ++ 2 files changed, 20 insertions(+), 0 deletions(-) diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index e411262..c789087 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -176,6 +176,24 @@ config LEDS_LP3944 To compile this driver as a module, choose M here: the module will be called leds-lp3944. +config LEDS_LP5521 + tristate "LED Support for N.S. LP5521 LED driver chip" + depends on LEDS_CLASS && I2C + help + If you say yes here you get support for the National Semiconductor + LP5521 LED driver. It is 3 channel chip with programmable engines. + Driver provides direct control via LED class and interface for + programming the engines. + +config LEDS_LP5523 + tristate "LED Support for N.S. LP5523 LED driver chip" + depends on LEDS_CLASS && I2C + help + If you say yes here you get support for the National Semiconductor + LP5523 LED driver. It is 9 channel chip with programmable engines. + Driver provides direct control via LED class and interface for + programming the engines. + config LEDS_CLEVO_MAIL tristate "Mail LED on Clevo notebook" depends on X86 && SERIO_I8042 && DMI diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 7d6b958..2ba98f6 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -23,6 +23,8 @@ obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o +obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o +obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o obj-$(CONFIG_LEDS_FSG) += leds-fsg.o -- 1.6.0.4 ^ permalink raw reply related [flat|nested] 13+ messages in thread
* RE: [PATCH 0/4] Led driver support for LP5521 and LP5523 chips [not found] ` <1284631946-5350-1-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org> 2010-09-16 10:12 ` [PATCH 1/4] leds: driver for National Semiconductor LP5521 chip Samu Onkalo 2010-09-16 10:12 ` [PATCH 3/4] leds: Update LP552x support Kconfig and Makefile Samu Onkalo @ 2010-09-27 9:21 ` samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w [not found] ` <62697B07E9803846BC582181BD6FB6B835EF64A9AA-xJW1crHCIS+8kqYwC468Frtp2NbXvJi8gfoxzgwHRXE@public.gmane.org> 2 siblings, 1 reply; 13+ messages in thread From: samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w @ 2010-09-27 9:21 UTC (permalink / raw) To: rpurdie-VuQAYsv1563Yd54FQh9/CA, akpm-3NddpPZAyC0 Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA Hi >-----Original Message----- >From: linux-i2c-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org [mailto:linux-i2c- >owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org] On Behalf Of Onkalo Samu.P (Nokia-MS/Tampere) >Sent: 16 September, 2010 13:12 >To: rpurdie-VuQAYsv1563Yd54FQh9/CA@public.gmane.org I haven't got any mails or comments from Richard about this driver. Also led tree here: http://git.o-hand.com/cgit.cgi/linux-rpurdie-leds/ is not updated in 4 months. I'm just wondering should I send this to somebody else than Richard Purdie? I'll update the driver based on other comments and send v2 patch set. -Samu >Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA@public.gmane.org; linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org >Subject: [PATCH 0/4] Led driver support for LP5521 and LP5523 chips > >Patch set provides support for LP5521 and LP5523 LED driver chips >from National Semicondutor. Both drivers supports programmable engines >and naturally LED class features. > >Documentation is provided as a part of the patch set. >I created "leds" sub directory to Documentation. >Perhaps rest of the leds* documentation should be moved >there. > >Datasheets are freely available at National Semiconductor www pages. > >Tested to work in top of 2.6.36-RC4 kernel. > >Samu Onkalo (4): > leds: driver for National Semiconductor LP5521 chip > leds: Driver for National Semiconductors LP5523 chip > leds: Update LP552x support Kconfig and Makefile > Documentation: led drivers lp5521 and lp5523 > > Documentation/leds/leds-lp5521.txt | 85 +++ > Documentation/leds/leds-lp5523.txt | 81 +++ > drivers/leds/Kconfig | 18 + > drivers/leds/Makefile | 2 + > drivers/leds/leds-lp5521.c | 820 +++++++++++++++++++++++++++ > drivers/leds/leds-lp5523.c | 1063 >++++++++++++++++++++++++++++++++++++ > include/linux/leds-lp5521.h | 46 ++ > include/linux/leds-lp5523.h | 46 ++ > 8 files changed, 2161 insertions(+), 0 deletions(-) > create mode 100644 Documentation/leds/leds-lp5521.txt > create mode 100644 Documentation/leds/leds-lp5523.txt > create mode 100644 drivers/leds/leds-lp5521.c > create mode 100644 drivers/leds/leds-lp5523.c > create mode 100644 include/linux/leds-lp5521.h > create mode 100644 include/linux/leds-lp5523.h > >-- >To unsubscribe from this list: send the line "unsubscribe linux-i2c" in >the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org >More majordomo info at http://vger.kernel.org/majordomo-info.html ^ permalink raw reply [flat|nested] 13+ messages in thread
[parent not found: <62697B07E9803846BC582181BD6FB6B835EF64A9AA-xJW1crHCIS+8kqYwC468Frtp2NbXvJi8gfoxzgwHRXE@public.gmane.org>]
* Re: [PATCH 0/4] Led driver support for LP5521 and LP5523 chips [not found] ` <62697B07E9803846BC582181BD6FB6B835EF64A9AA-xJW1crHCIS+8kqYwC468Frtp2NbXvJi8gfoxzgwHRXE@public.gmane.org> @ 2010-09-28 9:00 ` Linus Walleij [not found] ` <AANLkTi=N-p_ojTt6ia6bLxoA5AKC6rdZky9kvz_BqFqa-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org> 0 siblings, 1 reply; 13+ messages in thread From: Linus Walleij @ 2010-09-28 9:00 UTC (permalink / raw) To: samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w, akpm-3NddpPZAyC0 Cc: rpurdie-VuQAYsv1563Yd54FQh9/CA, linux-i2c-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA, Martin Persson 2010/9/27 <samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>: > I haven't got any mails or comments from Richard about this driver. > Also led tree here: > http://git.o-hand.com/cgit.cgi/linux-rpurdie-leds/ > > is not updated in 4 months. > > I'm just wondering should I send this to somebody else than Richard Purdie? > > I'll update the driver based on other comments and send v2 patch set. We (ST-Ericsson) need this driver in as well. If Richard is busy maybe Andrew can pick it up for the time being? Looking forward to the v2 version! Yours, Linus Walleij ^ permalink raw reply [flat|nested] 13+ messages in thread
[parent not found: <AANLkTi=N-p_ojTt6ia6bLxoA5AKC6rdZky9kvz_BqFqa-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>]
* Re: [PATCH 0/4] Led driver support for LP5521 and LP5523 chips [not found] ` <AANLkTi=N-p_ojTt6ia6bLxoA5AKC6rdZky9kvz_BqFqa-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org> @ 2010-09-28 14:14 ` Alan Cox 2010-09-28 19:59 ` Andrew Morton 1 sibling, 0 replies; 13+ messages in thread From: Alan Cox @ 2010-09-28 14:14 UTC (permalink / raw) To: Linus Walleij Cc: samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w, akpm-3NddpPZAyC0, rpurdie-VuQAYsv1563Yd54FQh9/CA, linux-i2c-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA, Martin Persson On Tue, 28 Sep 2010 11:00:20 +0200 Linus Walleij <linus.ml.walleij-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > 2010/9/27 <samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>: > > > I haven't got any mails or comments from Richard about this driver. > > Also led tree here: > > http://git.o-hand.com/cgit.cgi/linux-rpurdie-leds/ > > > > is not updated in 4 months. > > > > I'm just wondering should I send this to somebody else than Richard Purdie? > > > > I'll update the driver based on other comments and send v2 patch set. > > We (ST-Ericsson) need this driver in as well. > > If Richard is busy maybe Andrew can pick it up for the time being? Same here (Intel) Alan ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 0/4] Led driver support for LP5521 and LP5523 chips [not found] ` <AANLkTi=N-p_ojTt6ia6bLxoA5AKC6rdZky9kvz_BqFqa-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org> 2010-09-28 14:14 ` Alan Cox @ 2010-09-28 19:59 ` Andrew Morton 1 sibling, 0 replies; 13+ messages in thread From: Andrew Morton @ 2010-09-28 19:59 UTC (permalink / raw) To: Linus Walleij Cc: samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w, rpurdie-VuQAYsv1563Yd54FQh9/CA, linux-i2c-u79uwXL29TY76Z2rM5mHXA, linux-kernel-u79uwXL29TY76Z2rM5mHXA, Martin Persson On Tue, 28 Sep 2010 11:00:20 +0200 Linus Walleij <linus.ml.walleij-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > 2010/9/27 <samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>: > > > I haven't got any mails or comments from Richard about this driver. > > Also led tree here: > > http://git.o-hand.com/cgit.cgi/linux-rpurdie-leds/ > > > > is not updated in 4 months. > > > > I'm just wondering should I send this to somebody else than Richard Purdie? > > > > I'll update the driver based on other comments and send v2 patch set. > > We (ST-Ericsson) need this driver in as well. > > If Richard is busy maybe Andrew can pick it up for the time being? I looked. There are a few valid-looking review comments which remain unaddressed and un-replied-to. > Looking forward to the v2 version! yup, please cc me on v2. ^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2010-09-28 19:59 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-09-16 10:12 [PATCH 0/4] Led driver support for LP5521 and LP5523 chips Samu Onkalo
2010-09-16 10:12 ` [PATCH 2/4] leds: Driver for National Semiconductors LP5523 chip Samu Onkalo
2010-09-27 10:54 ` Matti J. Aaltonen
[not found] ` <1285584868.8182.194.camel-U1ola594hmgZeDAa2SinrdBPR1lH4CV8@public.gmane.org>
2010-09-27 11:03 ` Matti J. Aaltonen
2010-09-16 10:12 ` [PATCH 4/4] Documentation: led drivers lp5521 and lp5523 Samu Onkalo
[not found] ` <1284631946-5350-5-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>
2010-09-22 18:14 ` matt mooney
[not found] ` <1284631946-5350-1-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>
2010-09-16 10:12 ` [PATCH 1/4] leds: driver for National Semiconductor LP5521 chip Samu Onkalo
[not found] ` <1284631946-5350-2-git-send-email-samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>
2010-09-22 0:14 ` Ben Dooks
2010-09-16 10:12 ` [PATCH 3/4] leds: Update LP552x support Kconfig and Makefile Samu Onkalo
2010-09-27 9:21 ` [PATCH 0/4] Led driver support for LP5521 and LP5523 chips samu.p.onkalo-xNZwKgViW5gAvxtiuMwx3w
[not found] ` <62697B07E9803846BC582181BD6FB6B835EF64A9AA-xJW1crHCIS+8kqYwC468Frtp2NbXvJi8gfoxzgwHRXE@public.gmane.org>
2010-09-28 9:00 ` Linus Walleij
[not found] ` <AANLkTi=N-p_ojTt6ia6bLxoA5AKC6rdZky9kvz_BqFqa-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2010-09-28 14:14 ` Alan Cox
2010-09-28 19:59 ` Andrew Morton
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox