Re: rev2 - Driver for BlinkM i2c LED module

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



New version attched.

Am Sonntag, Juni 03, 2012, 07:32:08 PM schrieb Jonathan Neuschäfer:
> On Sat, Jun 02, 2012 at 11:29:46AM +0200, Jan-Simon Möller wrote:
> > static const struct {
> > 
> > 	char cmdchar;
> > 	u8 cmdbyte;
> > 	u8 nr_args;
> > 	u8 nr_ret;
> > 	u8 dir:2;
> > 
> > } blinkm_cmds[17] = {
> > 
> > 	/* cmdnr, cmdchar, cmdbyte, nr_args, nr_ret,  dir */
> 
> cmdnr isn't there anymore.

Yep, tnx.

> > 	/* We start hardware transfers which are not to be
> > 	
> > 	 * mixed with other commands. Aquire a lock now. */
> > 	
> > 	if (mutex_lock_interruptible(&data->update_lock) < 0)
> > 	
> > 		return -EAGAIN;
> > 	
> > 	/* switch cmd - usually write before reads */
> > 	switch (cmd) {
> 
> [...]
> 
> > 	default:
> > 		printk(KERN_INFO "BlinkM: unknown command %d\n", cmd);
> > 		return -1;
> 
> You need to unlock the mutex in the error case, too.
> 
> Also the -1 looks suspicious to me. When interpreted as -errno it would
> be -EPERM on most (or even all) architectures.  (according to
> include/asm-generic/errno-base.h)

Thanks for spotting this. Fixed.

[...]

> > 	/* red fade to off */
> > 	data->red = 0xff;
> > 	ret = blinkm_transfer_hw(client, BLM_GO_RGB);
> > 	if (ret < 0)
> > 	
> > 		printk(KERN_INFO
> > 		
> > 		       "Failure in blinkm_remove ignored. Continuing.\n");
> > 	
> > 	/* off */
> > 	data->red = 0x00;
> > 	data->green = 0x00;
> > 	data->blue = 0x00;
> > 	ret = blinkm_transfer_hw(client, BLM_FADE_RGB);
> > 	if (ret < 0)
> > 	
> > 		printk(KERN_INFO
> > 		
> > 		       "Failure in blinkm_remove ignored. Continuing.\n");
> 
> Did you leave this fading in for testing?
That is in by intention.


Best,
Jan-Simon
From 6bebeb09e907dc93fd386eef20339897eae09bfb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan-Simon=20M=C3=B6ller?= <jansimon.moeller@xxxxxx>
Date: Sun, 3 Jun 2012 22:10:52 +0200
Subject: [PATCH] Add driver for BlinkM device to drivers/leds/.
 Add entry to MAINTAINERS file.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

A BlinkM is a RGB LED controlled through I2C.

This driver implements an interface to the LED framework
and another sysfs group to access the internal options
of the BlinkM.

rev3: Fixed issues found by found by Jonathan Neuschäfer.

Signed-off-by: Jan-Simon Möller <jansimon.moeller@xxxxxx>
---
 MAINTAINERS                |    5 +
 drivers/leds/Kconfig       |    8 +
 drivers/leds/Makefile      |    1 +
 drivers/leds/leds-blinkm.c |  781 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 795 insertions(+), 0 deletions(-)
 create mode 100644 drivers/leds/leds-blinkm.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 3c94d73..7dc87a0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1518,6 +1518,11 @@ W:	http://blackfin.uclinux.org/
 S:	Supported
 F:	drivers/i2c/busses/i2c-bfin-twi.c
 
+BLINKM RGB LED DRIVER
+M:	Jan-Simon Moeller <jansimon.moeller@xxxxxx>
+S:	Maintained
+F:	drivers/leds/leds-blinkm.c
+
 BLOCK LAYER
 M:	Jens Axboe <axboe@xxxxxxxxx>
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux-block.git
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 21cb9b8..8e72c2f 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -431,6 +431,14 @@ config LEDS_OT200
 	  This option enables support for the LEDs on the Bachmann OT200.
 	  Say Y to enable LEDs on the Bachmann OT200.
 
+config LEDS_BLINKM
+	tristate "LED support for the BlinkM i2c RGB LED"
+	depends on LEDS_CLASS
+	depends on I2C
+	help
+	  This option enables support for the BlinkM RGB LED connected
+	  through I2C.
+
 config LEDS_TRIGGERS
 	bool "LED Trigger support"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index d601d68..99118fb 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_LEDS_ASIC3)		+= leds-asic3.o
 obj-$(CONFIG_LEDS_RENESAS_TPU)		+= leds-renesas-tpu.o
 obj-$(CONFIG_LEDS_MAX8997)		+= leds-max8997.o
 obj-$(CONFIG_LEDS_LM3556)		+= leds-lm3556.o
+obj-$(CONFIG_LEDS_BLINKM)		+= leds-blinkm.o
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
 
diff --git a/drivers/leds/leds-blinkm.c b/drivers/leds/leds-blinkm.c
new file mode 100644
index 0000000..8158128
--- /dev/null
+++ b/drivers/leds/leds-blinkm.c
@@ -0,0 +1,781 @@
+/*
+ *  leds-blinkm.c
+ *  (c) Jan-Simon Möller (dl9pf@xxxxxx)
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/printk.h>
+#include <linux/pm_runtime.h>
+#include <linux/leds.h>
+#include <linux/delay.h>
+
+/* Addresses to scan - BlinkM is on 0x09 by default*/
+static const unsigned short normal_i2c[] = { 0x09, I2C_CLIENT_END };
+
+static int blinkm_transfer_hw(struct i2c_client *client, int cmd);
+static int blinkm_test_run(struct i2c_client *client);
+
+struct blinkm_led {
+	struct i2c_client *i2c_client;
+	struct led_classdev led_cdev;
+	int id;
+	atomic_t active;
+};
+
+struct blinkm_work {
+	struct blinkm_led *blinkm_led;
+	struct work_struct work;
+};
+
+#define cdev_to_blmled(c)          container_of(c, struct blinkm_led, led_cdev)
+#define work_to_blmwork(c)         container_of(c, struct blinkm_work, work)
+
+struct blinkm_data {
+	struct i2c_client *i2c_client;
+	struct mutex update_lock;
+	/* used for led class interface */
+	struct blinkm_led blinkm_leds[3];
+	/* used for "blinkm" sysfs interface */
+	u8 red;			/* color red */
+	u8 green;		/* color green */
+	u8 blue;		/* color blue */
+	/* internal use */
+	u8 args[7];		/* set of args for transmission */
+	u8 i2c_addr;		/* i2c addr */
+	u8 fw_ver;		/* firmware version */
+	/* used, but not from userspace */
+	u8 hue;			/* HSB  hue */
+	u8 saturation;		/* HSB  saturation */
+	u8 brightness;		/* HSB  brightness */
+	/* currently unused / todo */
+	u8 fade_speed;		/* fade speed     1 - 255 */
+	s8 time_adjust;		/* time adjust -128 - 127 */
+	u8 fade:1;		/* fade on = 1, off = 0 */
+	u8 rand:1;		/* rand fade mode on = 1 */
+	u8 script_id;		/* script ID */
+	u8 script_repeats;	/* repeats of script */
+	u8 script_startline;	/* line to start */
+};
+
+/* Colors */
+#define RED   0
+#define GREEN 1
+#define BLUE  2
+
+/* mapping command names to cmd chars - see datasheet */
+#define BLM_GO_RGB            0
+#define BLM_FADE_RGB          1
+#define BLM_FADE_HSB          2
+#define BLM_FADE_RAND_RGB     3
+#define BLM_FADE_RAND_HSB     4
+#define BLM_PLAY_SCRIPT       5
+#define BLM_STOP_SCRIPT       6
+#define BLM_SET_FADE_SPEED    7
+#define BLM_SET_TIME_ADJ      8
+#define BLM_GET_CUR_RGB       9
+#define BLM_WRITE_SCRIPT_LINE 10
+#define BLM_READ_SCRIPT_LINE  11
+#define BLM_SET_SCRIPT_LR     12	/* Length & Repeats */
+#define BLM_SET_ADDR          13
+#define BLM_GET_ADDR          14
+#define BLM_GET_FW_VER        15
+#define BLM_SET_STARTUP_PARAM 16
+
+/* BlinkM Commands
+ *  as extracted out of the datasheet:
+ *
+ *  cmdchar = command (ascii)
+ *  cmdbyte = command in hex
+ *  nr_args = number of arguments (to send)
+ *  nr_ret  = number of return values (to read)
+ *  dir = direction (0 = read, 1 = write, 2 = both)
+ *
+ */
+static const struct {
+	char cmdchar;
+	u8 cmdbyte;
+	u8 nr_args;
+	u8 nr_ret;
+	u8 dir:2;
+} blinkm_cmds[17] = {
+  /* cmdchar, cmdbyte, nr_args, nr_ret,  dir */
+	{ 'n', 0x6e, 3, 0, 1},
+	{ 'c', 0x63, 3, 0, 1},
+	{ 'h', 0x68, 3, 0, 1},
+	{ 'C', 0x43, 3, 0, 1},
+	{ 'H', 0x48, 3, 0, 1},
+	{ 'p', 0x70, 3, 0, 1},
+	{ 'o', 0x6f, 0, 0, 1},
+	{ 'f', 0x66, 1, 0, 1},
+	{ 't', 0x74, 1, 0, 1},
+	{ 'g', 0x67, 0, 3, 0},
+	{ 'W', 0x57, 7, 0, 1},
+	{ 'R', 0x52, 2, 5, 2},
+	{ 'L', 0x4c, 3, 0, 1},
+	{ 'A', 0x41, 4, 0, 1},
+	{ 'a', 0x61, 0, 1, 0},
+	{ 'Z', 0x5a, 0, 1, 0},
+	{ 'B', 0x42, 5, 0, 1},
+};
+
+static ssize_t show_color_common(struct device *dev, char *buf, int color)
+{
+	struct i2c_client *client;
+	struct blinkm_data *data;
+	int ret;
+
+	client = to_i2c_client(dev);
+	data = i2c_get_clientdata(client);
+
+	ret = blinkm_transfer_hw(client, BLM_GET_CUR_RGB);
+	if (ret < 0)
+		return ret;
+	switch (color) {
+	case RED:
+		return scnprintf(buf, PAGE_SIZE, "%02X\n", data->red);
+		break;
+	case GREEN:
+		return scnprintf(buf, PAGE_SIZE, "%02X\n", data->green);
+		break;
+	case BLUE:
+		return scnprintf(buf, PAGE_SIZE, "%02X\n", data->blue);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return -EINVAL;
+}
+
+static int store_color_common(struct device *dev, const char *buf, int color)
+{
+	struct i2c_client *client;
+	struct blinkm_data *data;
+	int ret;
+	u8 value;
+
+	client = to_i2c_client(dev);
+	data = i2c_get_clientdata(client);
+
+	ret = kstrtou8(buf, 10, &value);
+	if (ret < 0) {
+		dev_err(dev, "BlinkM: value too large!\n");
+		return ret;
+	}
+
+	switch (color) {
+	case RED:
+		data->red = value;
+		break;
+	case GREEN:
+		data->green = value;
+		break;
+	case BLUE:
+		data->blue = value;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* if mode ... */
+	ret = blinkm_transfer_hw(client, BLM_GO_RGB);
+	if (ret < 0) {
+		dev_err(dev, "BlinkM: can't set RGB\n");
+		return ret;
+	}
+	return 0;
+}
+
+static ssize_t show_red(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	return show_color_common(dev, buf, RED);
+}
+
+static ssize_t store_red(struct device *dev, struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+	int ret;
+
+	ret = store_color_common(dev, buf, RED);
+	if (ret < 0)
+		return ret;
+	return count;
+}
+
+static DEVICE_ATTR(red, S_IRUGO | S_IWUG, show_red, store_red);
+
+static ssize_t show_green(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	return show_color_common(dev, buf, GREEN);
+}
+
+static ssize_t store_green(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+
+	int ret;
+
+	ret = store_color_common(dev, buf, GREEN);
+	if (ret < 0)
+		return ret;
+	return count;
+}
+
+static DEVICE_ATTR(green, S_IRUGO | S_IWUG, show_green, store_green);
+
+static ssize_t show_blue(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	return show_color_common(dev, buf, BLUE);
+}
+
+static ssize_t store_blue(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	int ret;
+
+	ret = store_color_common(dev, buf, BLUE);
+	if (ret < 0)
+		return ret;
+	return count;
+}
+
+static DEVICE_ATTR(blue, S_IRUGO | S_IWUG, show_blue, store_blue);
+
+static ssize_t show_test(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	return scnprintf(buf, PAGE_SIZE,
+			 "#Write into test to start test sequence!#\n");
+}
+
+static ssize_t store_test(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+
+	struct i2c_client *client;
+	int ret;
+	client = to_i2c_client(dev);
+
+	/*test */
+	ret = blinkm_test_run(client);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR(test, S_IRUGO | S_IWUG, show_test, store_test);
+
+/* TODO: HSB, fade, timeadj, script ... */
+
+static struct attribute *blinkm_attrs[] = {
+	&dev_attr_red.attr,
+	&dev_attr_green.attr,
+	&dev_attr_blue.attr,
+	&dev_attr_test.attr,
+	NULL,
+};
+
+static struct attribute_group blinkm_group = {
+	.name = "blinkm",
+	.attrs = blinkm_attrs,
+};
+
+static int blinkm_write(struct i2c_client *client, int cmd, u8 *arg)
+{
+	int result = 0;
+	int i = 0;
+	int arglen = blinkm_cmds[cmd].nr_args;
+	/* write out cmd to blinkm - always / default step */
+	result = i2c_smbus_write_byte(client, blinkm_cmds[cmd].cmdbyte);
+	if (result < 0)
+		return result;
+	/* no args to write out */
+	if (arglen == 0)
+		return 0;
+
+	for (i = 0; i < arglen; i++) {
+		/* repeat for arglen */
+		result = i2c_smbus_write_byte(client, arg[i]);
+		if (result < 0)
+			return result;
+	}
+	return 0;
+}
+
+static int blinkm_read(struct i2c_client *client, int cmd, u8 *arg)
+{
+	int result = 0;
+	int i = 0;
+	int retlen = blinkm_cmds[cmd].nr_ret;
+	for (i = 0; i < retlen; i++) {
+		/* repeat for retlen */
+		result = i2c_smbus_read_byte(client);
+		if (result < 0)
+			return result;
+		arg[i] = result;
+	}
+
+	return 0;
+}
+
+static int blinkm_transfer_hw(struct i2c_client *client, int cmd)
+{
+	/* the protocol is simple but non-standard:
+	 * e.g.  cmd 'g' (= 0x67) for "get device address"
+	 * - which defaults to 0x09 - would be the sequence:
+	 *   a) write 0x67 to the device (byte write)
+	 *   b) read the value (0x09) back right after (byte read)
+	 *
+	 * Watch out for "unfinished" sequences (i.e. not enough reads
+	 * or writes after a command. It will make the blinkM misbehave.
+	 * Sequence is key here.
+	 */
+
+	/* args / return are in private data struct */
+	struct blinkm_data *data = i2c_get_clientdata(client);
+
+	/* We start hardware transfers which are not to be
+	 * mixed with other commands. Aquire a lock now. */
+	if (mutex_lock_interruptible(&data->update_lock) < 0)
+		return -EAGAIN;
+
+	/* switch cmd - usually write before reads */
+	switch (cmd) {
+	case BLM_FADE_RAND_RGB:
+	case BLM_GO_RGB:
+	case BLM_FADE_RGB:
+		data->args[0] = data->red;
+		data->args[1] = data->green;
+		data->args[2] = data->blue;
+		blinkm_write(client, cmd, data->args);
+		break;
+	case BLM_FADE_HSB:
+	case BLM_FADE_RAND_HSB:
+		data->args[0] = data->hue;
+		data->args[1] = data->saturation;
+		data->args[2] = data->brightness;
+		blinkm_write(client, cmd, data->args);
+		break;
+	case BLM_PLAY_SCRIPT:
+		data->args[0] = data->script_id;
+		data->args[1] = data->script_repeats;
+		data->args[2] = data->script_startline;
+		blinkm_write(client, cmd, data->args);
+		break;
+	case BLM_STOP_SCRIPT:
+		blinkm_write(client, cmd, NULL);
+		break;
+	case BLM_GET_CUR_RGB:
+		data->args[0] = data->red;
+		data->args[1] = data->green;
+		data->args[2] = data->blue;
+		blinkm_write(client, cmd, NULL);
+		blinkm_read(client, cmd, data->args);
+		data->red = data->args[0];
+		data->green = data->args[1];
+		data->blue = data->args[2];
+		break;
+	case BLM_GET_ADDR:
+		data->args[0] = data->i2c_addr;
+		blinkm_write(client, cmd, NULL);
+		blinkm_read(client, cmd, data->args);
+		data->i2c_addr = data->args[0];
+		break;
+	case BLM_SET_TIME_ADJ:
+	case BLM_SET_FADE_SPEED:
+	case BLM_READ_SCRIPT_LINE:
+	case BLM_WRITE_SCRIPT_LINE:
+	case BLM_SET_SCRIPT_LR:
+	case BLM_SET_ADDR:
+	case BLM_GET_FW_VER:
+	case BLM_SET_STARTUP_PARAM:
+		pr_info("BlinkM: cmd %d not implemented yet.\n", cmd);
+		break;
+	default:
+		pr_info("BlinkM: unknown command %d\n", cmd);
+		mutex_unlock(&data->update_lock);
+		return -EINVAL;
+	}			/* end switch(cmd) */
+
+	/* transfers done, unlock */
+	mutex_unlock(&data->update_lock);
+	return 0;
+}
+
+static void led_work(struct work_struct *work)
+{
+	int ret;
+	struct blinkm_led *led;
+	struct blinkm_work *blm_work = work_to_blmwork(work);
+
+	led = blm_work->blinkm_led;
+	ret = blinkm_transfer_hw(led->i2c_client, BLM_GO_RGB);
+	atomic_dec(&led->active);
+	kfree(blm_work);
+}
+
+static int blinkm_led_common_set(struct led_classdev *led_cdev,
+				 enum led_brightness value, int color)
+{
+	/* led_brightness is 0, 127 or 255 - we just use it here as-is */
+	struct blinkm_led *led = cdev_to_blmled(led_cdev);
+	struct blinkm_data *data = i2c_get_clientdata(led->i2c_client);
+	struct blinkm_work *bl_work = kzalloc(sizeof(struct blinkm_work),
+					      GFP_ATOMIC);
+
+	switch (color) {
+	case RED:
+		data->red = (u8) value;
+		break;
+	case GREEN:
+		data->green = (u8) value;
+		break;
+	case BLUE:
+		data->blue = (u8) value;
+		break;
+	default:
+		pr_info("BlinkM: unknown color.\n");
+		return -EINVAL;
+	}
+
+	atomic_inc(&led->active);
+	bl_work->blinkm_led = led;
+	INIT_WORK(&bl_work->work, led_work);
+	schedule_work(&bl_work->work);
+	return 0;
+}
+
+static void blinkm_led_red_set(struct led_classdev *led_cdev,
+			       enum led_brightness value)
+{
+	blinkm_led_common_set(led_cdev, value, RED);
+}
+
+static void blinkm_led_green_set(struct led_classdev *led_cdev,
+				 enum led_brightness value)
+{
+	blinkm_led_common_set(led_cdev, value, GREEN);
+}
+
+static void blinkm_led_blue_set(struct led_classdev *led_cdev,
+				enum led_brightness value)
+{
+	blinkm_led_common_set(led_cdev, value, BLUE);
+}
+
+static void blinkm_init_hw(struct i2c_client *client)
+{
+	int ret;
+	ret = blinkm_transfer_hw(client, BLM_STOP_SCRIPT);
+	ret = blinkm_transfer_hw(client, BLM_GO_RGB);
+}
+
+static int blinkm_test_run(struct i2c_client *client)
+{
+	int ret;
+	struct blinkm_data *data = i2c_get_clientdata(client);
+
+	data->red = 0x01;
+	data->green = 0x05;
+	data->blue = 0x10;
+	ret = blinkm_transfer_hw(client, BLM_GO_RGB);
+	if (ret < 0)
+		return ret;
+	msleep(2000);
+
+	data->red = 0x25;
+	data->green = 0x10;
+	data->blue = 0x31;
+	ret = blinkm_transfer_hw(client, BLM_FADE_RGB);
+	if (ret < 0)
+		return ret;
+	msleep(2000);
+
+	data->hue = 0x50;
+	data->saturation = 0x10;
+	data->brightness = 0x20;
+	ret = blinkm_transfer_hw(client, BLM_FADE_HSB);
+	if (ret < 0)
+		return ret;
+	msleep(2000);
+
+	return 0;
+}
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info)
+{
+	struct i2c_adapter *adapter = client->adapter;
+	int ret = 0;
+	int count = 99;
+	u8 tmpargs[7] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
+				     | I2C_FUNC_SMBUS_WORD_DATA
+				     | I2C_FUNC_SMBUS_WRITE_BYTE))
+		return -ENODEV;
+
+	/* Now, we do the remaining detection. Simple for now. */
+	/* We might need more guards to protect other i2c slaves */
+
+	/* make sure the blinkM is balanced (read/writes) */
+	while (count > 0) {
+		ret = blinkm_write(client, BLM_GET_ADDR, NULL);
+		usleep_range(5000, 10000);
+		ret = blinkm_read(client, BLM_GET_ADDR, tmpargs);
+		usleep_range(5000, 10000);
+		if (tmpargs[0] == 0x09)
+			count = 0;
+		count--;
+	}
+
+	/* Step 1: Read BlinkM address back  -  cmd_char 'a' */
+	ret = blinkm_write(client, BLM_GET_ADDR, NULL);
+	if (ret < 0)
+		return -ENODEV;
+	usleep_range(20000, 30000);	/* allow a small delay */
+	ret = blinkm_read(client, BLM_GET_ADDR, tmpargs);
+	if (ret < 0)
+		return -ENODEV;
+
+	if (tmpargs[0] != 0x09) {
+		dev_err(&client->dev, "enodev DEV ADDR = 0x%02X\n", tmpargs[0]);
+		return -ENODEV;
+	}
+
+	strlcpy(info->type, "blinkm", I2C_NAME_SIZE);
+	return 0;
+}
+
+static int blinkm_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct blinkm_data *data;
+	struct blinkm_led *led[3];
+	int err, i;
+	char blinkm_led_name[28];
+
+	data = kzalloc(sizeof(struct blinkm_data), GFP_KERNEL);
+	if (!data) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	data->i2c_addr = 0x09;
+	data->red = 0x00;
+	data->green = 0x00;
+	data->blue = 0x00;
+	data->hue = 0x00;
+	data->saturation = 0x00;
+	data->brightness = 0x00;
+	data->fade = 0x01;
+	data->rand = 0x00;
+	data->fade_speed = 0x01;
+	data->time_adjust = 0x01;
+	data->i2c_addr = 0x08;
+	/* i2c addr  - use fake addr of 0x08 initially (real is 0x09) */
+	data->fw_ver = 0xfe;
+	/* firmware version - use fake until we read real value
+	 * (currently broken - BlinkM confused!) */
+	data->args[0] = 0x00;
+	data->script_id = 0x01;
+	data->script_repeats = 0x00;
+	data->script_startline = 0x00;
+	data->i2c_client = client;
+
+	i2c_set_clientdata(client, data);
+	mutex_init(&data->update_lock);
+
+	/* Register sysfs hooks */
+	err = sysfs_create_group(&client->dev.kobj, &blinkm_group);
+	if (err < 0) {
+		dev_err(&client->dev, "couldn't register sysfs group\n");
+		goto exit;
+	}
+
+	for (i = 0; i < 3; i++) {
+		/* RED = 0, GREEN = 1, BLUE = 2 */
+		led[i] = &data->blinkm_leds[i];
+		led[i]->i2c_client = client;
+		led[i]->id = i;
+		led[i]->led_cdev.max_brightness = 255;
+		led[i]->led_cdev.flags = LED_CORE_SUSPENDRESUME;
+		atomic_set(&led[i]->active, 0);
+		switch (i) {
+		case RED:
+			snprintf(blinkm_led_name, sizeof(blinkm_led_name),
+				 "blinkm-%d-red", client->adapter->nr);
+			led[i]->led_cdev.name = blinkm_led_name;
+			led[i]->led_cdev.brightness_set = blinkm_led_red_set;
+			err = led_classdev_register(&client->dev,
+						    &led[i]->led_cdev);
+			if (err < 0) {
+				dev_err(&client->dev,
+					"couldn't register LED %s\n",
+					led[i]->led_cdev.name);
+				goto failred;
+			}
+			break;
+		case GREEN:
+			snprintf(blinkm_led_name, sizeof(blinkm_led_name),
+				 "blinkm-%d-green", client->adapter->nr);
+			led[i]->led_cdev.name = blinkm_led_name;
+			led[i]->led_cdev.brightness_set = blinkm_led_green_set;
+			err = led_classdev_register(&client->dev,
+						    &led[i]->led_cdev);
+			if (err < 0) {
+				dev_err(&client->dev,
+					"couldn't register LED %s\n",
+					led[i]->led_cdev.name);
+				goto failgreen;
+			}
+			break;
+		case BLUE:
+			snprintf(blinkm_led_name, sizeof(blinkm_led_name),
+				 "blinkm-%d-blue", client->adapter->nr);
+			led[i]->led_cdev.name = blinkm_led_name;
+			led[i]->led_cdev.brightness_set = blinkm_led_blue_set;
+			err = led_classdev_register(&client->dev,
+						    &led[i]->led_cdev);
+			if (err < 0) {
+				dev_err(&client->dev,
+					"couldn't register LED %s\n",
+					led[i]->led_cdev.name);
+				goto failblue;
+			}
+			break;
+		}		/* end switch */
+	}			/* end for */
+
+	/* Initialize the blinkm */
+	blinkm_init_hw(client);
+
+	return 0;
+
+failblue:
+	led_classdev_unregister(&led[GREEN]->led_cdev);
+
+failgreen:
+	led_classdev_unregister(&led[RED]->led_cdev);
+
+failred:
+	sysfs_remove_group(&client->dev.kobj, &blinkm_group);
+exit:
+	kfree(data);
+	return err;
+}
+
+static int blinkm_remove(struct i2c_client *client)
+{
+	struct blinkm_data *data = i2c_get_clientdata(client);
+	int ret = 0;
+	int maxcount;
+	int i;
+
+	/* make sure no workqueue entries are pending */
+	for (i = 0; i < 3; i++) {
+		maxcount = 99;
+		led_classdev_unregister(&data->blinkm_leds[i].led_cdev);
+		while (atomic_read(&data->blinkm_leds[i].active) > 0) {
+			if (maxcount == 0)
+				break;
+			msleep(100);
+			maxcount--;
+		}
+	}
+
+	/* reset rgb */
+	data->red = 0x00;
+	data->green = 0x00;
+	data->blue = 0x00;
+	ret = blinkm_transfer_hw(client, BLM_FADE_RGB);
+	if (ret < 0)
+		pr_info("Failure in blinkm_remove ignored. Continuing.\n");
+
+	/* reset hsb */
+	data->hue = 0x00;
+	data->saturation = 0x00;
+	data->brightness = 0x00;
+	ret = blinkm_transfer_hw(client, BLM_FADE_HSB);
+	if (ret < 0)
+		pr_info("Failure in blinkm_remove ignored. Continuing.\n");
+
+	/* red fade to off */
+	data->red = 0xff;
+	ret = blinkm_transfer_hw(client, BLM_GO_RGB);
+	if (ret < 0)
+		pr_info("Failure in blinkm_remove ignored. Continuing.\n");
+
+	/* off */
+	data->red = 0x00;
+	data->green = 0x00;
+	data->blue = 0x00;
+	ret = blinkm_transfer_hw(client, BLM_FADE_RGB);
+	if (ret < 0)
+		pr_info("Failure in blinkm_remove ignored. Continuing.\n");
+
+	sysfs_remove_group(&client->dev.kobj, &blinkm_group);
+	kfree(data);
+	return 0;
+}
+
+static const struct i2c_device_id blinkm_id[] = {
+	{"blinkm", 0},
+	{}
+};
+
+MODULE_DEVICE_TABLE(i2c, blinkm_id);
+
+  /* This is the driver that will be inserted */
+static struct i2c_driver blinkm_driver = {
+	.class = I2C_CLASS_HWMON,
+	.driver = {
+		   .name = "blinkm",
+		   },
+	.probe = blinkm_probe,
+	.remove = blinkm_remove,
+	.id_table = blinkm_id,
+	.detect = blinkm_detect,
+	.address_list = normal_i2c,
+};
+
+static int __init blinkm_init(void)
+{
+	return i2c_add_driver(&blinkm_driver);
+}
+
+static void __exit blinkm_exit(void)
+{
+	i2c_del_driver(&blinkm_driver);
+}
+
+MODULE_AUTHOR("Jan-Simon Moeller <dl9pf@xxxxxx>");
+MODULE_DESCRIPTION("BlinkM RGB LED driver");
+MODULE_LICENSE("GPL");
+
+module_init(blinkm_init);
+module_exit(blinkm_exit);
-- 
1.7.3.4

_______________________________________________
Kernelnewbies mailing list
Kernelnewbies@xxxxxxxxxxxxxxxxx
http://lists.kernelnewbies.org/mailman/listinfo/kernelnewbies

[Index of Archives]     [Newbies FAQ]     [Linux Kernel Mentors]     [Linux Kernel Development]     [IETF Annouce]     [Git]     [Networking]     [Security]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux SCSI]     [Linux ACPI]
  Powered by Linux