On Fri, Jun 01, 2012 at 03:45:59PM +0200, Jan-Simon Möller wrote: > 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 ? Just some nitpicking. I don't have a device for testing. > > I'm also looking for the best place to fit this in. > Staging ? drivers/led ? > > Have Phun! I had fun reviewing the code. :-) > > Best, > Jan-Simon > 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 */ Is c_r an old name? > 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 Where are these values used? What's the difference between write-read and read-write? > > /* 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) I think this is where you would use the BLM_DIR_* macros. > */ > static const struct { > int cmd; I don't think you need the cmd field, as blinkm_cmds[N].cmd is always N as of now. > char cmdchar; > u8 cmdbyte; Cmdchar and cmdbyte seem to be the same (numerically) in the table. Is that intended? > 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},}; > I would leave the array size out, but I guess that's a matter of preference. And I would place the curly brackets like this: static const struct { /* ... */ } blinkm_cmds[] = { {0, 'n', 0x6e, 3, 0, 1}, {1, 'c', 0x63, 3, 0, 1}, {2, 'h', 0x68, 3, 0, 1}, /* ... */ }; > 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); > Looks like store_red, store_green, and store_blue could be merged to de-duplicate some code. Same with show_*. > 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 It's "watch out for". :-) > * 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; I would write the equivalent cases using fall-through to save space: case BLM_GO_RGB: case BLM_FADE_RGB: case BLM_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_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_SET_STARTUP_PARAM: > blinkm_write(client, cmd, data->args); > break; > default: > return -1; You need to unlock the mutex. > } /* 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,...) [...] > static void blinkm_led_blue_set(struct led_classdev *led_cdev,...) [...] Code duplication again. (Or triplication :-D) > 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; Why is it 1 instead of 0? (Just asking because it looks non-obvious) > 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)*/ What does the 0x09 in the parentheses mean? > 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; Why is it 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"); I'd call it "BlinkM LED driver" or something, "BlinkM" alone isn't really descriptive. > MODULE_LICENSE("GPL"); > > module_init(blinkm_init); > module_exit(blinkm_exit); Thanks, Jonathan Neuschäfer _______________________________________________ Kernelnewbies mailing list Kernelnewbies@xxxxxxxxxxxxxxxxx http://lists.kernelnewbies.org/mailman/listinfo/kernelnewbies