Driver for BlinkM i2c LED module

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

 



Hi all!

*drum roll*

This is the first version of the blinkM i2c led driver.

blinkM is an RGB led module which hooks up to an i2c bus.
See http://thingm.com/products/blinkm .

The protocol uses sequences of i2c commands to communicate with the tiny 
embedded controller.

This driver implements the needed bits to make the blinkM work as
LED device (accepting the triggers in sysfs) and also has a sysfs group for 
the more "advanced settings" exposed by the controller.
Of course not all advanced options are implemented yet ;).

Comments ?

I'm also looking for the best place to fit this in.
Staging ? drivers/led ?

Have Phun!

Best,
Jan-Simon
/*
 *  test_blinkm.c Jan-Simon Moeller (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;			/* c_r  -  color red */
	u8 green;		/* c_g  -  color green */
	u8 blue;		/* c_b  -  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;			/* c_h  -  HSB  hue */
	u8 saturation;		/* c_s  -  HSB  saturation */
	u8 brightness;		/* c_br -  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 */
};

#define BLM_DIR_READ       0
#define BLM_DIR_WRITE      1
#define BLM_DIR_WRITE_READ 2
#define BLM_DIR_READ_WRITE 3

/* 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*/
/* cmdchar = command (ascii)
   cmdbyte = command in hex
   nr_args = number of arguments to send
   nr_ret  = number of return values
   dir = direction (0 = read, 1 = write)
 */
static const struct {
	int cmd;
	char cmdchar;
	u8 cmdbyte;
	u8 nr_args;
	u8 nr_ret;
	u8 dir:2;
} blinkm_cmds[17] = {
	/* cmdchar, cmdbyte, nr_args, nr_ret,  dir */
	{
	0, 'n', 0x6e, 3, 0, 1}, {
	1, 'c', 0x63, 3, 0, 1}, {
	2, 'h', 0x68, 3, 0, 1}, {
	3, 'C', 0x43, 3, 0, 1}, {
	4, 'H', 0x48, 3, 0, 1}, {
	5, 'p', 0x70, 3, 0, 1}, {
	6, 'o', 0x6f, 0, 0, 1}, {
	7, 'f', 0x66, 1, 0, 1}, {
	8, 't', 0x74, 1, 0, 1}, {
	9, 'g', 0x67, 0, 3, 0}, {
	10, 'W', 0x57, 7, 0, 1}, {
	11, 'R', 0x52, 2, 5, 2}, {
	12, 'L', 0x4c, 3, 0, 1}, {
	13, 'A', 0x41, 4, 0, 1}, {
	14, 'a', 0x61, 0, 1, 0}, {
	15, 'Z', 0x5a, 0, 1, 0}, {
16, 'B', 0x42, 5, 0, 1},};

static ssize_t show_red(struct device *dev, struct device_attribute *attr,
			char *buf)
{
	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 -1;
	return scnprintf(buf, PAGE_SIZE, "%02X\n", data->red);
}

static ssize_t store_red(struct device *dev, struct device_attribute *attr,
			 const char *buf, size_t count)
{
	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;
	}

	data->red = value;

	/* 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 count;
}

static DEVICE_ATTR(red, S_IRUGO | S_IWUGO, show_red, store_red);

static ssize_t show_green(struct device *dev, struct device_attribute *attr,
			  char *buf)
{
	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 -1;
	return scnprintf(buf, PAGE_SIZE, "%02X\n", data->green);
}

static ssize_t store_green(struct device *dev, struct device_attribute *attr,
			   const char *buf, size_t count)
{
	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;
	}
	data->green = value;

	/* if mode ... (todo: fading ?) */
	ret = blinkm_transfer_hw(client, BLM_GO_RGB);
	if (ret < 0) {
		dev_err(dev, "BlinkM: can't set RGB\n");
		return ret;
	}

	return count;
}

static DEVICE_ATTR(green, S_IRUGO | S_IWUGO, show_green, store_green);

static ssize_t show_blue(struct device *dev, struct device_attribute *attr,
			 char *buf)
{
	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 -1;
	return scnprintf(buf, PAGE_SIZE, "%02X\n", data->blue);
}

static ssize_t store_blue(struct device *dev, struct device_attribute *attr,
			  const char *buf, size_t count)
{
	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;
	}
	data->blue = value;

	/* if mode ... (todo:fading ?) */
	ret = blinkm_transfer_hw(client, BLM_GO_RGB);
	if (ret < 0) {
		dev_err(dev, "BlinkM: can't set RGB\n");
		return ret;
	}

	return count;
}

static DEVICE_ATTR(blue, S_IRUGO | S_IWUGO, 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_IWUGO, 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 of "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_GO_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_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:
		data->args[0] = data->hue;
		data->args[1] = data->saturation;
		data->args[2] = data->brightness;
		blinkm_write(client, cmd, data->args);
		break;
	case BLM_FADE_RAND_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_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_SET_FADE_SPEED:
		blinkm_write(client, cmd, NULL);
		break;
	case BLM_SET_TIME_ADJ:
		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_WRITE_SCRIPT_LINE:
		/* blinkm_write(client, cmd, NULL); */
		break;
	case BLM_READ_SCRIPT_LINE:
		/*blinkm_write(client, cmd, NULL);
		blinkm_read(client, cmd, NULL); */
		break;
	case BLM_SET_SCRIPT_LR:
		/*blinkm_write(client, cmd, NULL);*/
		break;
	case BLM_SET_ADDR:
		/*blinkm_write(client, cmd, NULL);*/
		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_GET_FW_VER:
		blinkm_write(client, cmd, data->args);
		blinkm_read(client, cmd, data->args);
		break;
	case BLM_SET_STARTUP_PARAM:
		blinkm_write(client, cmd, data->args);
		break;
	default:
		return -1;
	}			/* 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 void blinkm_led_red_set(struct led_classdev *led_cdev,
			       enum led_brightness value)
{
	/* 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_r = kzalloc(sizeof(struct blinkm_work),
						GFP_ATOMIC);

	switch (value) {
	case 0:
		data->red = 0;
		break;
	case 127:
		data->red = 0x88;
		break;
	case 255:
		data->red = 0xFF;
		break;
	default:
		data->red = 0;
	}
/*      data->red=(u8)value;        we know it fits ... 0..255 */
	atomic_inc(&led->active);

	bl_work_r->blinkm_led = led;
	INIT_WORK(&bl_work_r->work, led_work);
	schedule_work(&bl_work_r->work);
}

static void blinkm_led_green_set(struct led_classdev *led_cdev,
				 enum led_brightness value)
{
	/* 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_g = kzalloc(sizeof(struct blinkm_work),
						GFP_ATOMIC);

	switch (value) {
	case 0:
		data->green = 0;
		break;
	case 127:
		data->green = 0x88;
		break;
	case 255:
		data->green = 0xFF;
		break;
	default:
		data->green = 0;
	}
/*      data->green=(u8)value;        we know it fits ... 0..255 */
	atomic_inc(&led->active);

	bl_work_g->blinkm_led = led;
	INIT_WORK(&bl_work_g->work, led_work);
	schedule_work(&bl_work_g->work);
}

static void blinkm_led_blue_set(struct led_classdev *led_cdev,
				enum led_brightness value)
{
	/* 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_b = kmalloc(sizeof(struct blinkm_work),
						GFP_ATOMIC);

	switch (value) {
	case 0:
		data->blue = 0;
		break;
	case 127:
		data->blue = 0x88;
		break;
	case 255:
		data->blue = 0xFF;
		break;
	default:
		data->blue = 0;
	}
/*      data->blue=(u8)value;       // we know it fits ... 0..255 */
	atomic_inc(&led->active);

	bl_work_b->blinkm_led = led;
	INIT_WORK(&bl_work_b->work, led_work);
	schedule_work(&bl_work_b->work);
}

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 -1;
	msleep(2000);

	data->red = 0x25;
	data->green = 0x10;
	data->blue = 0x31;
	ret = blinkm_transfer_hw(client, BLM_FADE_RGB);
	if (ret < 0)
		return -1;
	msleep(2000);

	data->hue = 0x50;
	data->saturation = 0x10;
	data->brightness = 0x20;
	ret = blinkm_transfer_hw(client, BLM_FADE_HSB);
	if (ret < 0)
		return -1;
	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 *ledr;
	struct blinkm_led *ledg;
	struct blinkm_led *ledb;
	int err;

	data = kzalloc(sizeof(struct blinkm_data), GFP_KERNEL);
	if (!data) {
		err = -ENOMEM;
		goto exit;
	}

	data->i2c_addr = 0x09;
	data->red = 0x01;
	data->green = 0x01;
	data->blue = 0x01;
	data->hue = 0x01;
	data->saturation = 0x01;
	data->brightness = 0x01;
	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 (0x09)*/
	data->fw_ver = 0xfe;
/* firmware version - use fake until we read real value (currently broken!)*/
	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;
	}
	/* red, id = 0 */
	ledr = &data->blinkm_leds[0];
	ledr->i2c_client = client;
	ledr->id = 0;
	ledr->led_cdev.name = "blinkm_red";
	ledr->led_cdev.max_brightness = 255;
	ledr->led_cdev.brightness_set = blinkm_led_red_set;
	ledr->led_cdev.flags = LED_CORE_SUSPENDRESUME;
	atomic_set(&ledr->active, 0);
	err = led_classdev_register(&client->dev, &ledr->led_cdev);
	if (err < 0) {
		dev_err(&client->dev,
			"couldn't register LED %s\n", ledr->led_cdev.name);
		goto failred;
	}
	/* green, id = 1 */
	ledg = &data->blinkm_leds[1];
	ledg->i2c_client = client;
	ledg->id = 1;
	ledg->led_cdev.name = "blinkm_green";
	ledg->led_cdev.max_brightness = 255;
	ledg->led_cdev.brightness_set = blinkm_led_green_set;
	ledg->led_cdev.flags = LED_CORE_SUSPENDRESUME;
	err = led_classdev_register(&client->dev, &ledg->led_cdev);
	atomic_set(&ledg->active, 0);
	if (err < 0) {
		dev_err(&client->dev,
			"couldn't register LED %s\n", ledg->led_cdev.name);
		goto failgreen;
	}
	/* blue, id = 2 */
	ledb = &data->blinkm_leds[2];
	ledb->i2c_client = client;
	ledb->id = 2;
	ledb->led_cdev.name = "blinkm_blue";
	ledb->led_cdev.max_brightness = 255;
	ledb->led_cdev.brightness_set = blinkm_led_blue_set;
	ledb->led_cdev.flags = LED_CORE_SUSPENDRESUME;
	err = led_classdev_register(&client->dev, &ledb->led_cdev);
	atomic_set(&ledb->active, 0);
	if (err < 0) {
		dev_err(&client->dev,
			"couldn't register LED %s\n", ledb->led_cdev.name);
		goto failblue;
	}

	/* Initialize the blinkm */
	blinkm_init_hw(client);

	return 0;

failblue:
	led_classdev_unregister(&ledg->led_cdev);

failgreen:
	led_classdev_unregister(&ledr->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;

	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 = 0x05;
	data->green = 0x05;
	data->blue = 0x05;
	ret = blinkm_transfer_hw(client, BLM_FADE_RGB);
	if (ret < 0)
		printk(KERN_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)
		printk(KERN_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)
		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");

	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");
MODULE_LICENSE("GPL");

module_init(blinkm_init);
module_exit(blinkm_exit);
_______________________________________________
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