diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-06-08 22:31:16 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-06-08 22:31:16 +0400 |
commit | 3f17ea6dea8ba5668873afa54628a91aaa3fb1c0 (patch) | |
tree | afbeb2accd4c2199ddd705ae943995b143a0af02 /drivers/hid/hid-thingm.c | |
parent | 1860e379875dfe7271c649058aeddffe5afd9d0d (diff) | |
parent | 1a5700bc2d10cd379a795fd2bb377a190af5acd4 (diff) | |
download | linux-3f17ea6dea8ba5668873afa54628a91aaa3fb1c0.tar.xz |
Merge branch 'next' (accumulated 3.16 merge window patches) into master
Now that 3.15 is released, this merges the 'next' branch into 'master',
bringing us to the normal situation where my 'master' branch is the
merge window.
* accumulated work in next: (6809 commits)
ufs: sb mutex merge + mutex_destroy
powerpc: update comments for generic idle conversion
cris: update comments for generic idle conversion
idle: remove cpu_idle() forward declarations
nbd: zero from and len fields in NBD_CMD_DISCONNECT.
mm: convert some level-less printks to pr_*
MAINTAINERS: adi-buildroot-devel is moderated
MAINTAINERS: add linux-api for review of API/ABI changes
mm/kmemleak-test.c: use pr_fmt for logging
fs/dlm/debug_fs.c: replace seq_printf by seq_puts
fs/dlm/lockspace.c: convert simple_str to kstr
fs/dlm/config.c: convert simple_str to kstr
mm: mark remap_file_pages() syscall as deprecated
mm: memcontrol: remove unnecessary memcg argument from soft limit functions
mm: memcontrol: clean up memcg zoneinfo lookup
mm/memblock.c: call kmemleak directly from memblock_(alloc|free)
mm/mempool.c: update the kmemleak stack trace for mempool allocations
lib/radix-tree.c: update the kmemleak stack trace for radix tree allocations
mm: introduce kmemleak_update_trace()
mm/kmemleak.c: use %u to print ->checksum
...
Diffstat (limited to 'drivers/hid/hid-thingm.c')
-rw-r--r-- | drivers/hid/hid-thingm.c | 361 |
1 files changed, 201 insertions, 160 deletions
diff --git a/drivers/hid/hid-thingm.c b/drivers/hid/hid-thingm.c index a97c78845f7b..134be89b15ea 100644 --- a/drivers/hid/hid-thingm.c +++ b/drivers/hid/hid-thingm.c @@ -1,7 +1,7 @@ /* * ThingM blink(1) USB RGB LED driver * - * Copyright 2013 Savoir-faire Linux Inc. + * Copyright 2013-2014 Savoir-faire Linux Inc. * Vivien Didelot <vivien.didelot@savoirfairelinux.com> * * This program is free software; you can redistribute it and/or @@ -10,244 +10,285 @@ */ #include <linux/hid.h> +#include <linux/hidraw.h> #include <linux/leds.h> #include <linux/module.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> #include "hid-ids.h" -#define BLINK1_CMD_SIZE 9 +#define REPORT_ID 1 +#define REPORT_SIZE 9 -#define blink1_rgb_to_r(rgb) ((rgb & 0xFF0000) >> 16) -#define blink1_rgb_to_g(rgb) ((rgb & 0x00FF00) >> 8) -#define blink1_rgb_to_b(rgb) ((rgb & 0x0000FF) >> 0) +/* Firmware major number of supported devices */ +#define THINGM_MAJOR_MK1 '1' +#define THINGM_MAJOR_MK2 '2' -/** - * struct blink1_data - blink(1) device specific data - * @hdev: HID device. - * @led_cdev: LED class instance. - * @rgb: 8-bit per channel RGB notation. - * @fade: fade time in hundredths of a second. - * @brightness: brightness coefficient. - * @play: play/pause in-memory patterns. - */ -struct blink1_data { +struct thingm_fwinfo { + char major; + unsigned numrgb; + unsigned first; +}; + +static const struct thingm_fwinfo thingm_fwinfo[] = { + { + .major = THINGM_MAJOR_MK1, + .numrgb = 1, + .first = 0, + }, { + .major = THINGM_MAJOR_MK2, + .numrgb = 2, + .first = 1, + } +}; + +/* A red, green or blue channel, part of an RGB chip */ +struct thingm_led { + struct thingm_rgb *rgb; + struct led_classdev ldev; + char name[32]; +}; + +/* Basically a WS2812 5050 RGB LED chip */ +struct thingm_rgb { + struct thingm_device *tdev; + struct thingm_led red; + struct thingm_led green; + struct thingm_led blue; + struct work_struct work; + u8 num; +}; + +struct thingm_device { struct hid_device *hdev; - struct led_classdev led_cdev; - u32 rgb; - u16 fade; - u8 brightness; - bool play; + struct { + char major; + char minor; + } version; + const struct thingm_fwinfo *fwinfo; + struct mutex lock; + struct thingm_rgb *rgb; }; -static int blink1_send_command(struct blink1_data *data, - u8 buf[BLINK1_CMD_SIZE]) +static int thingm_send(struct thingm_device *tdev, u8 buf[REPORT_SIZE]) { int ret; - hid_dbg(data->hdev, "command: %d%c%.2x%.2x%.2x%.2x%.2x%.2x%.2x\n", + hid_dbg(tdev->hdev, "-> %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8]); - ret = hid_hw_raw_request(data->hdev, buf[0], buf, BLINK1_CMD_SIZE, - HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); return ret < 0 ? ret : 0; } -static int blink1_update_color(struct blink1_data *data) +static int thingm_recv(struct thingm_device *tdev, u8 buf[REPORT_SIZE]) { - u8 buf[BLINK1_CMD_SIZE] = { 1, 'n', 0, 0, 0, 0, 0, 0, 0 }; - - if (data->brightness) { - unsigned int coef = DIV_ROUND_CLOSEST(255, data->brightness); + int ret; - buf[2] = DIV_ROUND_CLOSEST(blink1_rgb_to_r(data->rgb), coef); - buf[3] = DIV_ROUND_CLOSEST(blink1_rgb_to_g(data->rgb), coef); - buf[4] = DIV_ROUND_CLOSEST(blink1_rgb_to_b(data->rgb), coef); - } + ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret < 0) + return ret; - if (data->fade) { - buf[1] = 'c'; - buf[5] = (data->fade & 0xFF00) >> 8; - buf[6] = (data->fade & 0x00FF); - } + hid_dbg(tdev->hdev, "<- %d %c %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx\n", + buf[0], buf[1], buf[2], buf[3], buf[4], + buf[5], buf[6], buf[7], buf[8]); - return blink1_send_command(data, buf); + return 0; } -static void blink1_led_set(struct led_classdev *led_cdev, - enum led_brightness brightness) +static int thingm_version(struct thingm_device *tdev) { - struct blink1_data *data = dev_get_drvdata(led_cdev->dev->parent); + u8 buf[REPORT_SIZE] = { REPORT_ID, 'v', 0, 0, 0, 0, 0, 0, 0 }; + int err; - data->brightness = brightness; - if (blink1_update_color(data)) - hid_err(data->hdev, "failed to update color\n"); -} + err = thingm_send(tdev, buf); + if (err) + return err; -static enum led_brightness blink1_led_get(struct led_classdev *led_cdev) -{ - struct blink1_data *data = dev_get_drvdata(led_cdev->dev->parent); + err = thingm_recv(tdev, buf); + if (err) + return err; - return data->brightness; + tdev->version.major = buf[3]; + tdev->version.minor = buf[4]; + + return 0; } -static ssize_t blink1_show_rgb(struct device *dev, - struct device_attribute *attr, char *buf) +static int thingm_write_color(struct thingm_rgb *rgb) { - struct blink1_data *data = dev_get_drvdata(dev->parent); + u8 buf[REPORT_SIZE] = { REPORT_ID, 'c', 0, 0, 0, 0, 0, rgb->num, 0 }; - return sprintf(buf, "%.6X\n", data->rgb); + buf[2] = rgb->red.ldev.brightness; + buf[3] = rgb->green.ldev.brightness; + buf[4] = rgb->blue.ldev.brightness; + + return thingm_send(rgb->tdev, buf); } -static ssize_t blink1_store_rgb(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) +static void thingm_work(struct work_struct *work) { - struct blink1_data *data = dev_get_drvdata(dev->parent); - long unsigned int rgb; - int ret; + struct thingm_rgb *rgb = container_of(work, struct thingm_rgb, work); - ret = kstrtoul(buf, 16, &rgb); - if (ret) - return ret; - - /* RGB triplet notation is 24-bit hexadecimal */ - if (rgb > 0xFFFFFF) - return -EINVAL; + mutex_lock(&rgb->tdev->lock); - data->rgb = rgb; - ret = blink1_update_color(data); + if (thingm_write_color(rgb)) + hid_err(rgb->tdev->hdev, "failed to write color\n"); - return ret ? ret : count; + mutex_unlock(&rgb->tdev->lock); } -static DEVICE_ATTR(rgb, S_IRUGO | S_IWUSR, blink1_show_rgb, blink1_store_rgb); - -static ssize_t blink1_show_fade(struct device *dev, - struct device_attribute *attr, char *buf) +static void thingm_led_set(struct led_classdev *ldev, + enum led_brightness brightness) { - struct blink1_data *data = dev_get_drvdata(dev->parent); + struct thingm_led *led = container_of(ldev, struct thingm_led, ldev); - return sprintf(buf, "%d\n", data->fade * 10); + /* the ledclass has already stored the brightness value */ + schedule_work(&led->rgb->work); } -static ssize_t blink1_store_fade(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) +static int thingm_init_rgb(struct thingm_rgb *rgb) { - struct blink1_data *data = dev_get_drvdata(dev->parent); - long unsigned int fade; - int ret; + const int minor = ((struct hidraw *) rgb->tdev->hdev->hidraw)->minor; + int err; + + /* Register the red diode */ + snprintf(rgb->red.name, sizeof(rgb->red.name), + "thingm%d:red:led%d", minor, rgb->num); + rgb->red.ldev.name = rgb->red.name; + rgb->red.ldev.max_brightness = 255; + rgb->red.ldev.brightness_set = thingm_led_set; + rgb->red.rgb = rgb; + + err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->red.ldev); + if (err) + return err; + + /* Register the green diode */ + snprintf(rgb->green.name, sizeof(rgb->green.name), + "thingm%d:green:led%d", minor, rgb->num); + rgb->green.ldev.name = rgb->green.name; + rgb->green.ldev.max_brightness = 255; + rgb->green.ldev.brightness_set = thingm_led_set; + rgb->green.rgb = rgb; + + err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->green.ldev); + if (err) + goto unregister_red; + + /* Register the blue diode */ + snprintf(rgb->blue.name, sizeof(rgb->blue.name), + "thingm%d:blue:led%d", minor, rgb->num); + rgb->blue.ldev.name = rgb->blue.name; + rgb->blue.ldev.max_brightness = 255; + rgb->blue.ldev.brightness_set = thingm_led_set; + rgb->blue.rgb = rgb; + + err = led_classdev_register(&rgb->tdev->hdev->dev, &rgb->blue.ldev); + if (err) + goto unregister_green; + + INIT_WORK(&rgb->work, thingm_work); - ret = kstrtoul(buf, 10, &fade); - if (ret) - return ret; + return 0; - /* blink(1) accepts 16-bit fade time, number of 10ms ticks */ - fade = DIV_ROUND_CLOSEST(fade, 10); - if (fade > 65535) - return -EINVAL; +unregister_green: + led_classdev_unregister(&rgb->green.ldev); - data->fade = fade; +unregister_red: + led_classdev_unregister(&rgb->red.ldev); - return count; + return err; } -static DEVICE_ATTR(fade, S_IRUGO | S_IWUSR, - blink1_show_fade, blink1_store_fade); - -static ssize_t blink1_show_play(struct device *dev, - struct device_attribute *attr, char *buf) +static void thingm_remove_rgb(struct thingm_rgb *rgb) { - struct blink1_data *data = dev_get_drvdata(dev->parent); - - return sprintf(buf, "%d\n", data->play); + flush_work(&rgb->work); + led_classdev_unregister(&rgb->red.ldev); + led_classdev_unregister(&rgb->green.ldev); + led_classdev_unregister(&rgb->blue.ldev); } -static ssize_t blink1_store_play(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) +static int thingm_probe(struct hid_device *hdev, const struct hid_device_id *id) { - struct blink1_data *data = dev_get_drvdata(dev->parent); - u8 cmd[BLINK1_CMD_SIZE] = { 1, 'p', 0, 0, 0, 0, 0, 0, 0 }; - long unsigned int play; - int ret; + struct thingm_device *tdev; + int i, err; - ret = kstrtoul(buf, 10, &play); - if (ret) - return ret; + tdev = devm_kzalloc(&hdev->dev, sizeof(struct thingm_device), + GFP_KERNEL); + if (!tdev) + return -ENOMEM; - data->play = !!play; - cmd[2] = data->play; - ret = blink1_send_command(data, cmd); + tdev->hdev = hdev; + hid_set_drvdata(hdev, tdev); - return ret ? ret : count; -} - -static DEVICE_ATTR(play, S_IRUGO | S_IWUSR, - blink1_show_play, blink1_store_play); + err = hid_parse(hdev); + if (err) + goto error; -static const struct attribute_group blink1_sysfs_group = { - .attrs = (struct attribute *[]) { - &dev_attr_rgb.attr, - &dev_attr_fade.attr, - &dev_attr_play.attr, - NULL - }, -}; + err = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (err) + goto error; -static int thingm_probe(struct hid_device *hdev, const struct hid_device_id *id) -{ - struct blink1_data *data; - struct led_classdev *led; - char led_name[13]; - int ret; + mutex_init(&tdev->lock); - data = devm_kzalloc(&hdev->dev, sizeof(struct blink1_data), GFP_KERNEL); - if (!data) - return -ENOMEM; + err = thingm_version(tdev); + if (err) + goto stop; - hid_set_drvdata(hdev, data); - data->hdev = hdev; - data->rgb = 0xFFFFFF; /* set a default white color */ + hid_dbg(hdev, "firmware version: %c.%c\n", + tdev->version.major, tdev->version.minor); - ret = hid_parse(hdev); - if (ret) - goto error; + for (i = 0; i < ARRAY_SIZE(thingm_fwinfo) && !tdev->fwinfo; ++i) + if (thingm_fwinfo[i].major == tdev->version.major) + tdev->fwinfo = &thingm_fwinfo[i]; - ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); - if (ret) - goto error; + if (!tdev->fwinfo) { + hid_err(hdev, "unsupported firmware %c\n", tdev->version.major); + goto stop; + } - /* blink(1) serial numbers range is 0x1A001000 to 0x1A002FFF */ - led = &data->led_cdev; - snprintf(led_name, sizeof(led_name), "blink1::%s", hdev->uniq + 4); - led->name = led_name; - led->brightness_set = blink1_led_set; - led->brightness_get = blink1_led_get; - ret = led_classdev_register(&hdev->dev, led); - if (ret) + tdev->rgb = devm_kzalloc(&hdev->dev, + sizeof(struct thingm_rgb) * tdev->fwinfo->numrgb, + GFP_KERNEL); + if (!tdev->rgb) { + err = -ENOMEM; goto stop; + } - ret = sysfs_create_group(&led->dev->kobj, &blink1_sysfs_group); - if (ret) - goto remove_led; + for (i = 0; i < tdev->fwinfo->numrgb; ++i) { + struct thingm_rgb *rgb = tdev->rgb + i; + + rgb->tdev = tdev; + rgb->num = tdev->fwinfo->first + i; + err = thingm_init_rgb(rgb); + if (err) { + while (--i >= 0) + thingm_remove_rgb(tdev->rgb + i); + goto stop; + } + } return 0; - -remove_led: - led_classdev_unregister(led); stop: hid_hw_stop(hdev); error: - return ret; + return err; } static void thingm_remove(struct hid_device *hdev) { - struct blink1_data *data = hid_get_drvdata(hdev); - struct led_classdev *led = &data->led_cdev; + struct thingm_device *tdev = hid_get_drvdata(hdev); + int i; + + for (i = 0; i < tdev->fwinfo->numrgb; ++i) + thingm_remove_rgb(tdev->rgb + i); - sysfs_remove_group(&led->dev->kobj, &blink1_sysfs_group); - led_classdev_unregister(led); hid_hw_stop(hdev); } |