summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/ABI/testing/sysfs-driver-hid-thingm23
-rw-r--r--MAINTAINERS5
-rw-r--r--drivers/hid/Kconfig10
-rw-r--r--drivers/hid/Makefile1
-rw-r--r--drivers/hid/hid-core.c1
-rw-r--r--drivers/hid/hid-ids.h3
-rw-r--r--drivers/hid/hid-thingm.c272
7 files changed, 315 insertions, 0 deletions
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::<serial>/rgb
+Date: January 2013
+Contact: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
+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::<serial>/fade
+Date: January 2013
+Contact: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
+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::<serial>/play
+Date: January 2013
+Contact: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
+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 <vivien.didelot@savoirfairelinux.com>
+S: Maintained
+F: drivers/hid/hid-thingm.c
+
THINKPAD ACPI EXTRAS DRIVER
M: Henrique de Moraes Holschuh <ibm-acpi@hmh.eng.br>
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 <vivien.didelot@savoirfairelinux.com>
+ *
+ * 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 <linux/hid.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#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 <vivien.didelot@savoirfairelinux.com>");
+MODULE_DESCRIPTION("ThingM blink(1) USB RGB LED driver");