From 2e2daff3a51f2d10155b03f461f4e29eaf80dcbd Mon Sep 17 00:00:00 2001 From: Simon Wood Date: Thu, 31 Jan 2013 08:07:09 -0700 Subject: USB: HID: Steelseries SRW-S1 Add support for LEDs This patch to the SRW-S1 driver adds support for the LED RPM meter on the front of the device. The LEDs are controlled via /sys/class/leds interface, with an individual control for each of the 15 LEDs. Signed-off-by: Simon Wood Tested-by: John Murphy Signed-off-by: Jiri Kosina --- Documentation/ABI/testing/sysfs-driver-hid-srws1 | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-srws1 (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-driver-hid-srws1 b/Documentation/ABI/testing/sysfs-driver-hid-srws1 new file mode 100644 index 000000000000..c27b34dcaf83 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-srws1 @@ -0,0 +1,20 @@ +What: /sys/class/leds/SRWS1::::RPM1 +What: /sys/class/leds/SRWS1::::RPM2 +What: /sys/class/leds/SRWS1::::RPM3 +What: /sys/class/leds/SRWS1::::RPM4 +What: /sys/class/leds/SRWS1::::RPM5 +What: /sys/class/leds/SRWS1::::RPM6 +What: /sys/class/leds/SRWS1::::RPM7 +What: /sys/class/leds/SRWS1::::RPM8 +What: /sys/class/leds/SRWS1::::RPM9 +What: /sys/class/leds/SRWS1::::RPM10 +What: /sys/class/leds/SRWS1::::RPM11 +What: /sys/class/leds/SRWS1::::RPM12 +What: /sys/class/leds/SRWS1::::RPM13 +What: /sys/class/leds/SRWS1::::RPM14 +What: /sys/class/leds/SRWS1::::RPM15 +Date: Jan 2013 +KernelVersion: 3.9 +Contact: Simon Wood +Description: Provides a control for turning on/off the LEDs which form + an RPM meter on the front of the controller -- cgit v1.2.3-59-g8ed1b From e25d780581dc4d261c66e072a59c34782bd03e0a Mon Sep 17 00:00:00 2001 From: Simon Wood Date: Thu, 31 Jan 2013 08:07:10 -0700 Subject: USB: HID: Steelseries SRW-S1 Add support controlling all LEDs simultaneously This patch to the SRW-S1 driver adds the ability to control all LEDs simultaneously as testing showed that it was slow (noticably!!) when seting or clearing all the LEDs in turn. It adds a 'RPMALL' LED, whose behavoir is asserted to all the LEDs in the bar graph, individual LEDs can subsequently be turned on/off individually. Signed-off-by: Simon Wood Tested-by: John Murphy Signed-off-by: Jiri Kosina --- Documentation/ABI/testing/sysfs-driver-hid-srws1 | 1 + drivers/hid/hid-steelseries-srws1.c | 65 ++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 4 deletions(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-driver-hid-srws1 b/Documentation/ABI/testing/sysfs-driver-hid-srws1 index c27b34dcaf83..d0eba70c7d40 100644 --- a/Documentation/ABI/testing/sysfs-driver-hid-srws1 +++ b/Documentation/ABI/testing/sysfs-driver-hid-srws1 @@ -13,6 +13,7 @@ What: /sys/class/leds/SRWS1::::RPM12 What: /sys/class/leds/SRWS1::::RPM13 What: /sys/class/leds/SRWS1::::RPM14 What: /sys/class/leds/SRWS1::::RPM15 +What: /sys/class/leds/SRWS1::::RPMALL Date: Jan 2013 KernelVersion: 3.9 Contact: Simon Wood diff --git a/drivers/hid/hid-steelseries-srws1.c b/drivers/hid/hid-steelseries-srws1.c index a7386699ba7d..365bc9ef1e79 100644 --- a/drivers/hid/hid-steelseries-srws1.c +++ b/drivers/hid/hid-steelseries-srws1.c @@ -136,6 +136,42 @@ static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds) /* Note: LED change does not show on device until the device is read/polled */ } +static void steelseries_srws1_led_all_set_brightness(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct device *dev = led_cdev->dev->parent; + struct hid_device *hid = container_of(dev, struct hid_device, dev); + struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid); + + if (!drv_data) { + hid_err(hid, "Device data not found."); + return; + } + + if (value == LED_OFF) + drv_data->led_state = 0; + else + drv_data->led_state = (1 << (SRWS1_NUMBER_LEDS + 1)) - 1; + + steelseries_srws1_set_leds(hid, drv_data->led_state); +} + +static enum led_brightness steelseries_srws1_led_all_get_brightness(struct led_classdev *led_cdev) +{ + struct device *dev = led_cdev->dev->parent; + struct hid_device *hid = container_of(dev, struct hid_device, dev); + struct steelseries_srws1_data *drv_data; + + drv_data = hid_get_drvdata(hid); + + if (!drv_data) { + hid_err(hid, "Device data not found."); + return LED_OFF; + } + + return (drv_data->led_state >> SRWS1_NUMBER_LEDS) ? LED_FULL : LED_OFF; +} + static void steelseries_srws1_led_set_brightness(struct led_classdev *led_cdev, enum led_brightness value) { @@ -219,13 +255,34 @@ static int steelseries_srws1_probe(struct hid_device *hdev, /* register led subsystem */ drv_data->led_state = 0; - for (i = 0; i < SRWS1_NUMBER_LEDS; i++) + for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) drv_data->led[i] = NULL; steelseries_srws1_set_leds(hdev, 0); - name_sz = strlen(hdev->uniq) + 15; + name_sz = strlen(hdev->uniq) + 16; + + /* 'ALL', for setting all LEDs simultaneously */ + led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); + if (!led) { + hid_err(hdev, "can't allocate memory for LED ALL\n"); + goto err_led; + } + + name = (void *)(&led[1]); + snprintf(name, name_sz, "SRWS1::%s::RPMALL", hdev->uniq); + led->name = name; + led->brightness = 0; + led->max_brightness = 1; + led->brightness_get = steelseries_srws1_led_all_get_brightness; + led->brightness_set = steelseries_srws1_led_all_set_brightness; + + drv_data->led[SRWS1_NUMBER_LEDS] = led; + ret = led_classdev_register(&hdev->dev, led); + if (ret) + goto err_led; + /* Each individual LED */ for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); if (!led) { @@ -248,7 +305,7 @@ static int steelseries_srws1_probe(struct hid_device *hdev, hid_err(hdev, "failed to register LED %d. Aborting.\n", i); err_led: /* Deregister all LEDs (if any) */ - for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { + for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { led = drv_data->led[i]; drv_data->led[i] = NULL; if (!led) @@ -275,7 +332,7 @@ static void steelseries_srws1_remove(struct hid_device *hdev) if (drv_data) { /* Deregister LEDs (if any) */ - for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { + for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { led = drv_data->led[i]; drv_data->led[i] = NULL; if (!led) -- cgit v1.2.3-59-g8ed1b From 30ba2fbde1840db440915491cdde235b72a11384 Mon Sep 17 00:00:00 2001 From: Vivien Didelot Date: Tue, 22 Jan 2013 12:01:21 -0500 Subject: HID: add ThingM blink(1) USB RGB LED support The ThingM blink(1) is an open source hardware USB RGB LED. It contains an internal EEPROM, allowing to configure up to 12 light patterns. A light pattern is a RGB color plus a fade time. This driver registers a LED class instance with additional sysfs attributes to support basic functions such as setting RGB colors, fade and playing. Other functions are still accessible through the hidraw interface. At this time, the only documentation for the device is the firmware source code from ThingM, plus a few schematics. They are available at: https://github.com/todbot/blink1 This patch is version 3. It updates the name of the source file, the driver and the led sysfs entry, according to comments from Jiri Kosina and Simon Wood. Signed-off-by: Vivien Didelot Signed-off-by: Jiri Kosina --- Documentation/ABI/testing/sysfs-driver-hid-thingm | 23 ++ MAINTAINERS | 5 + drivers/hid/Kconfig | 10 + drivers/hid/Makefile | 1 + drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 3 + drivers/hid/hid-thingm.c | 272 ++++++++++++++++++++++ 7 files changed, 315 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-thingm create mode 100644 drivers/hid/hid-thingm.c (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-driver-hid-thingm b/Documentation/ABI/testing/sysfs-driver-hid-thingm new file mode 100644 index 000000000000..abcffeedd20a --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-thingm @@ -0,0 +1,23 @@ +What: /sys/class/leds/blink1::/rgb +Date: January 2013 +Contact: Vivien Didelot +Description: The ThingM blink1 is an USB RGB LED. The color notation is + 3-byte hexadecimal. Read this attribute to get the last set + color. Write the 24-bit hexadecimal color to change the current + LED color. The default color is full white (0xFFFFFF). + For instance, set the color to green with: echo 00FF00 > rgb + +What: /sys/class/leds/blink1::/fade +Date: January 2013 +Contact: Vivien Didelot +Description: This attribute allows to set a fade time in milliseconds for + the next color change. Read the attribute to know the current + fade time. The default value is set to 0 (no fade time). For + instance, set a fade time of 2 seconds with: echo 2000 > fade + +What: /sys/class/leds/blink1::/play +Date: January 2013 +Contact: Vivien Didelot +Description: This attribute is used to play/pause the light patterns. Write 1 + to start playing, 0 to stop. Reading this attribute returns the + current playing status. diff --git a/MAINTAINERS b/MAINTAINERS index b0b880da6e5c..10a4a36dd534 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7306,6 +7306,11 @@ S: Supported F: drivers/thermal/ F: include/linux/thermal.h +THINGM BLINK(1) USB RGB LED DRIVER +M: Vivien Didelot +S: Maintained +F: drivers/hid/hid-thingm.c + THINKPAD ACPI EXTRAS DRIVER M: Henrique de Moraes Holschuh L: ibm-acpi-devel@lists.sourceforge.net diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index bf88eca3e868..3d5294531ba5 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -655,6 +655,16 @@ config HID_TOPSEED Say Y if you have a TopSeed Cyberlink or BTC Emprex or Conceptronic CLLRCMCE remote control. +config HID_THINGM + tristate "ThingM blink(1) USB RGB LED" + depends on USB_HID + depends on LEDS_CLASS + ---help--- + Support for the ThingM blink(1) USB RGB LED. This driver registers a + Linux LED class instance, plus additional sysfs attributes to control + RGB colors, fade time and playing. The device is exposed through hidraw + to access other functions. + config HID_THRUSTMASTER tristate "ThrustMaster devices support" depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index b62215716b2f..93017043f6f8 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -103,6 +103,7 @@ obj-$(CONFIG_HID_SONY) += hid-sony.o obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o obj-$(CONFIG_HID_SUNPLUS) += hid-sunplus.o obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o +obj-$(CONFIG_HID_THINGM) += hid-thingm.o obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o obj-$(CONFIG_HID_TIVO) += hid-tivo.o obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index bf44af35439b..5f7115eb9412 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1699,6 +1699,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) }, { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) }, + { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) }, { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300) }, { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb304) }, { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb323) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 104105208596..eebbbf6f0a9a 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -747,6 +747,9 @@ #define USB_DEVICE_ID_SYNAPTICS_WTP 0x0010 #define USB_DEVICE_ID_SYNAPTICS_DPAD 0x0013 +#define USB_VENDOR_ID_THINGM 0x27b8 +#define USB_DEVICE_ID_BLINK1 0x01ed + #define USB_VENDOR_ID_THRUSTMASTER 0x044f #define USB_VENDOR_ID_TIVO 0x150a diff --git a/drivers/hid/hid-thingm.c b/drivers/hid/hid-thingm.c new file mode 100644 index 000000000000..2055a52e9a20 --- /dev/null +++ b/drivers/hid/hid-thingm.c @@ -0,0 +1,272 @@ +/* + * ThingM blink(1) USB RGB LED driver + * + * Copyright 2013 Savoir-faire Linux Inc. + * Vivien Didelot + * + * 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, version 2. + */ + +#include +#include +#include +#include + +#include "hid-ids.h" + +#define BLINK1_CMD_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) + +/** + * 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 hid_device *hdev; + struct led_classdev led_cdev; + u32 rgb; + u16 fade; + u8 brightness; + bool play; +}; + +static int blink1_send_command(struct blink1_data *data, + u8 buf[BLINK1_CMD_SIZE]) +{ + int ret; + + hid_dbg(data->hdev, "command: %d%c%.2x%.2x%.2x%.2x%.2x%.2x%.2x\n", + buf[0], buf[1], buf[2], buf[3], buf[4], + buf[5], buf[6], buf[7], buf[8]); + + ret = data->hdev->hid_output_raw_report(data->hdev, buf, + BLINK1_CMD_SIZE, HID_FEATURE_REPORT); + + return ret < 0 ? ret : 0; +} + +static int blink1_update_color(struct blink1_data *data) +{ + 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); + + 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); + } + + if (data->fade) { + buf[1] = 'c'; + buf[5] = (data->fade & 0xFF00) >> 8; + buf[6] = (data->fade & 0x00FF); + } + + return blink1_send_command(data, buf); +} + +static void blink1_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct blink1_data *data = dev_get_drvdata(led_cdev->dev->parent); + + data->brightness = brightness; + if (blink1_update_color(data)) + hid_err(data->hdev, "failed to update color\n"); +} + +static enum led_brightness blink1_led_get(struct led_classdev *led_cdev) +{ + struct blink1_data *data = dev_get_drvdata(led_cdev->dev->parent); + + return data->brightness; +} + +static ssize_t blink1_show_rgb(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct blink1_data *data = dev_get_drvdata(dev->parent); + + return sprintf(buf, "%.6X\n", data->rgb); +} + +static ssize_t blink1_store_rgb(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct blink1_data *data = dev_get_drvdata(dev->parent); + long unsigned int rgb; + int ret; + + ret = kstrtoul(buf, 16, &rgb); + if (ret) + return ret; + + /* RGB triplet notation is 24-bit hexadecimal */ + if (rgb > 0xFFFFFF) + return -EINVAL; + + data->rgb = rgb; + ret = blink1_update_color(data); + + return ret ? ret : count; +} + +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) +{ + struct blink1_data *data = dev_get_drvdata(dev->parent); + + return sprintf(buf, "%d\n", data->fade * 10); +} + +static ssize_t blink1_store_fade(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct blink1_data *data = dev_get_drvdata(dev->parent); + long unsigned int fade; + int ret; + + ret = kstrtoul(buf, 10, &fade); + if (ret) + return ret; + + /* blink(1) accepts 16-bit fade time, number of 10ms ticks */ + fade = DIV_ROUND_CLOSEST(fade, 10); + if (fade > 65535) + return -EINVAL; + + data->fade = fade; + + return count; +} + +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) +{ + struct blink1_data *data = dev_get_drvdata(dev->parent); + + return sprintf(buf, "%d\n", data->play); +} + +static ssize_t blink1_store_play(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + 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; + + ret = kstrtoul(buf, 10, &play); + if (ret) + return ret; + + data->play = !!play; + cmd[2] = data->play; + ret = blink1_send_command(data, cmd); + + return ret ? ret : count; +} + +static DEVICE_ATTR(play, S_IRUGO | S_IWUSR, + blink1_show_play, blink1_store_play); + +static const struct attribute_group blink1_sysfs_group = { + .attrs = (struct attribute *[]) { + &dev_attr_rgb.attr, + &dev_attr_fade.attr, + &dev_attr_play.attr, + NULL + }, +}; + +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; + + data = devm_kzalloc(&hdev->dev, sizeof(struct blink1_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + hid_set_drvdata(hdev, data); + data->hdev = hdev; + data->rgb = 0xFFFFFF; /* set a default white color */ + + ret = hid_parse(hdev); + if (ret) + goto error; + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) + goto error; + + /* 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) + goto stop; + + ret = sysfs_create_group(&led->dev->kobj, &blink1_sysfs_group); + if (ret) + goto remove_led; + + return 0; + +remove_led: + led_classdev_unregister(led); +stop: + hid_hw_stop(hdev); +error: + return ret; +} + +static void thingm_remove(struct hid_device *hdev) +{ + struct blink1_data *data = hid_get_drvdata(hdev); + struct led_classdev *led = &data->led_cdev; + + sysfs_remove_group(&led->dev->kobj, &blink1_sysfs_group); + led_classdev_unregister(led); + hid_hw_stop(hdev); +} + +static const struct hid_device_id thingm_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) }, + { } +}; +MODULE_DEVICE_TABLE(hid, thingm_table); + +static struct hid_driver thingm_driver = { + .name = "thingm", + .probe = thingm_probe, + .remove = thingm_remove, + .id_table = thingm_table, +}; + +module_hid_driver(thingm_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Vivien Didelot "); +MODULE_DESCRIPTION("ThingM blink(1) USB RGB LED driver"); -- cgit v1.2.3-59-g8ed1b