public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] Led driver support for LP5521 and LP5523 chips
@ 2010-09-16 10:12 Samu Onkalo
  2010-09-16 10:12 ` [PATCH 1/4] leds: driver for National Semiconductor LP5521 chip Samu Onkalo
                   ` (4 more replies)
  0 siblings, 5 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 1/4] leds: driver for National Semiconductor LP5521 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-22  0:14   ` Ben Dooks
  2010-09-16 10:12 ` [PATCH 2/4] leds: Driver for National Semiconductors LP5523 chip Samu Onkalo
                   ` (3 subsequent siblings)
  4 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

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@nokia.com>
---
 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@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-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@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_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

* [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 ` [PATCH 1/4] leds: driver for National Semiconductor LP5521 chip Samu Onkalo
@ 2010-09-16 10:12 ` Samu Onkalo
  2010-09-27 10:54   ` Matti J. Aaltonen
  2010-09-16 10:12 ` [PATCH 3/4] leds: Update LP552x support Kconfig and Makefile Samu Onkalo
                   ` (2 subsequent siblings)
  4 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

* [PATCH 3/4] leds: Update LP552x support Kconfig and Makefile
  2010-09-16 10:12 [PATCH 0/4] Led driver support for LP5521 and LP5523 chips Samu Onkalo
  2010-09-16 10:12 ` [PATCH 1/4] leds: driver for National Semiconductor LP5521 chip 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
  2010-09-16 10:12 ` [PATCH 4/4] Documentation: led drivers lp5521 and lp5523 Samu Onkalo
  2010-09-27  9:21 ` [PATCH 0/4] Led driver support for LP5521 and LP5523 chips samu.p.onkalo
  4 siblings, 0 replies; 13+ messages in thread
From: Samu Onkalo @ 2010-09-16 10:12 UTC (permalink / raw)
  To: rpurdie; +Cc: linux-i2c, linux-kernel

Provide configuration and compilation support for LP5521 and LP5523

Signed-off-by: Samu Onkalo <samu.p.onkalo@nokia.com>
---
 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

* [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
                   ` (2 preceding siblings ...)
  2010-09-16 10:12 ` [PATCH 3/4] leds: Update LP552x support Kconfig and Makefile Samu Onkalo
@ 2010-09-16 10:12 ` Samu Onkalo
  2010-09-22 18:14   ` matt mooney
  2010-09-27  9:21 ` [PATCH 0/4] Led driver support for LP5521 and LP5523 chips samu.p.onkalo
  4 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

* Re: [PATCH 1/4] leds: driver for National Semiconductor LP5521 chip
  2010-09-16 10:12 ` [PATCH 1/4] leds: driver for National Semiconductor LP5521 chip Samu Onkalo
@ 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, linux-i2c, linux-kernel

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@nokia.com>
> ---
>  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@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-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@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_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@vger.kernel.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

* Re: [PATCH 4/4] Documentation: led drivers lp5521 and lp5523
  2010-09-16 10:12 ` [PATCH 4/4] Documentation: led drivers lp5521 and lp5523 Samu Onkalo
@ 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, linux-i2c, linux-kernel

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

* RE: [PATCH 0/4] Led driver support for LP5521 and LP5523 chips
  2010-09-16 10:12 [PATCH 0/4] Led driver support for LP5521 and LP5523 chips Samu Onkalo
                   ` (3 preceding siblings ...)
  2010-09-16 10:12 ` [PATCH 4/4] Documentation: led drivers lp5521 and lp5523 Samu Onkalo
@ 2010-09-27  9:21 ` samu.p.onkalo
  2010-09-28  9:00   ` Linus Walleij
  4 siblings, 1 reply; 13+ messages in thread
From: samu.p.onkalo @ 2010-09-27  9:21 UTC (permalink / raw)
  To: rpurdie, akpm; +Cc: linux-i2c, linux-kernel

Hi

>-----Original Message-----
>From: linux-i2c-owner@vger.kernel.org [mailto:linux-i2c-
>owner@vger.kernel.org] On Behalf Of Onkalo Samu.P (Nokia-MS/Tampere)
>Sent: 16 September, 2010 13:12
>To: rpurdie@linux.intel.com

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@vger.kernel.org; linux-kernel@vger.kernel.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@vger.kernel.org
>More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[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
  2010-09-27 11:03     ` Matti J. Aaltonen
  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

* Re: [PATCH 2/4] leds: Driver for National Semiconductors LP5523 chip
  2010-09-27 10:54   ` Matti J. Aaltonen
@ 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@linux.intel.com, linux-i2c@vger.kernel.org,
	linux-kernel@vger.kernel.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

* Re: [PATCH 0/4] Led driver support for LP5521 and LP5523 chips
  2010-09-27  9:21 ` [PATCH 0/4] Led driver support for LP5521 and LP5523 chips samu.p.onkalo
@ 2010-09-28  9:00   ` Linus Walleij
  2010-09-28 14:14     ` Alan Cox
  2010-09-28 19:59     ` Andrew Morton
  0 siblings, 2 replies; 13+ messages in thread
From: Linus Walleij @ 2010-09-28  9:00 UTC (permalink / raw)
  To: samu.p.onkalo, akpm; +Cc: rpurdie, linux-i2c, linux-kernel, Martin Persson

2010/9/27  <samu.p.onkalo@nokia.com>:

> 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

* Re: [PATCH 0/4] Led driver support for LP5521 and LP5523 chips
  2010-09-28  9:00   ` Linus Walleij
@ 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, akpm, rpurdie, linux-i2c, linux-kernel,
	Martin Persson

On Tue, 28 Sep 2010 11:00:20 +0200
Linus Walleij <linus.ml.walleij@gmail.com> wrote:

> 2010/9/27  <samu.p.onkalo@nokia.com>:
> 
> > 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
  2010-09-28  9:00   ` Linus Walleij
  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, rpurdie, linux-i2c, linux-kernel, Martin Persson

On Tue, 28 Sep 2010 11:00:20 +0200
Linus Walleij <linus.ml.walleij@gmail.com> wrote:

> 2010/9/27  <samu.p.onkalo@nokia.com>:
> 
> > 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 1/4] leds: driver for National Semiconductor LP5521 chip Samu Onkalo
2010-09-22  0:14   ` Ben Dooks
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
2010-09-27 11:03     ` Matti J. Aaltonen
2010-09-16 10:12 ` [PATCH 3/4] leds: Update LP552x support Kconfig and Makefile Samu Onkalo
2010-09-16 10:12 ` [PATCH 4/4] Documentation: led drivers lp5521 and lp5523 Samu Onkalo
2010-09-22 18:14   ` matt mooney
2010-09-27  9:21 ` [PATCH 0/4] Led driver support for LP5521 and LP5523 chips samu.p.onkalo
2010-09-28  9:00   ` Linus Walleij
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