From 6c7ad07e9e05a659496e26243643280610c13d3a Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Fri, 17 Jun 2016 21:20:46 +0200 Subject: [PATCH 01/11] HID: migrate USB LED driver from usb misc to hid This patch migrates the USB LED driver to the HID subsystem. Supported are Dream Cheeky Webmail Notifier / Friends Alert and Riso Kagaku Webmail Notifier. Benefits: - Avoid using USB low-level calls and use the HID subsystem instead (as this device provides a USB HID interface) - Use standard LED subsystem instead of proprietary sysfs entries, this allows e.g. to use the device with features like triggers Successfully tested with a Dream Cheeky Webmail Notifier and a Riso Kagaku Webmail Notifier compatible device. Signed-off-by: Heiner Kallweit Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 12 ++ drivers/hid/Makefile | 1 + drivers/hid/hid-core.c | 6 +- drivers/hid/hid-ids.h | 2 + drivers/hid/hid-led.c | 288 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 306 insertions(+), 3 deletions(-) create mode 100644 drivers/hid/hid-led.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 5646ca4b95de..04bd2039b23b 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -388,6 +388,18 @@ config HID_LCPOWER ---help--- Support for LC-Power RC1000MCE RF remote control. +config HID_LED + tristate "Simple USB RGB LED support" + depends on HID + depends on LEDS_CLASS + ---help--- + Support for simple USB RGB LED devices. Currently supported are the + Riso Kagaku Webmail Notifier and the Dream Cheeky Webmail Notifier + and Friends Alert. + + To compile this driver as a module, choose M here: the + module will be called hid-led. + config HID_LENOVO tristate "Lenovo / Thinkpad devices" depends on HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index a2fb562de748..86d71f60a967 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -96,6 +96,7 @@ obj-$(CONFIG_HID_TIVO) += hid-tivo.o obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o obj-$(CONFIG_HID_TWINHAN) += hid-twinhan.o obj-$(CONFIG_HID_UCLOGIC) += hid-uclogic.o +obj-$(CONFIG_HID_LED) += hid-led.o obj-$(CONFIG_HID_XINMO) += hid-xinmo.o obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o obj-$(CONFIG_HID_ZYDACRON) += hid-zydacron.o diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 8ea3a26360e9..eb674ce75fab 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1879,6 +1879,8 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE) }, { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006) }, { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, USB_DEVICE_ID_DREAM_CHEEKY_WN) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, USB_DEVICE_ID_DREAM_CHEEKY_FA) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0009) }, { HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0030) }, @@ -2008,6 +2010,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) }, { HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU, USB_DEVICE_ID_RI_KA_WEBMAIL) }, #if IS_ENABLED(CONFIG_HID_ROCCAT) { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ARVO) }, { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ISKU) }, @@ -2348,8 +2351,6 @@ static const struct hid_device_id hid_ignore_list[] = { { HID_USB_DEVICE(USB_VENDOR_ID_DEALEXTREAME, USB_DEVICE_ID_DEALEXTREAME_RADIO_SI4701) }, { HID_USB_DEVICE(USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EARTHMATE) }, { HID_USB_DEVICE(USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EM_LT20) }, - { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, 0x0004) }, - { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, 0x000a) }, { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, 0x0400) }, { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, 0x0401) }, { HID_USB_DEVICE(USB_VENDOR_ID_ESSENTIAL_REALITY, USB_DEVICE_ID_ESSENTIAL_REALITY_P5) }, @@ -2486,7 +2487,6 @@ static const struct hid_device_id hid_ignore_list[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_DPAD) }, #endif { HID_USB_DEVICE(USB_VENDOR_ID_YEALINK, USB_DEVICE_ID_YEALINK_P1K_P4K_B2K) }, - { HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU, USB_DEVICE_ID_RI_KA_WEBMAIL) }, { } }; diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 3eec09a134cb..e104abae0dad 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -334,6 +334,8 @@ #define USB_DEVICE_ID_ELECOM_BM084 0x0061 #define USB_VENDOR_ID_DREAM_CHEEKY 0x1d34 +#define USB_DEVICE_ID_DREAM_CHEEKY_WN 0x0004 +#define USB_DEVICE_ID_DREAM_CHEEKY_FA 0x000a #define USB_VENDOR_ID_ELITEGROUP 0x03fc #define USB_DEVICE_ID_ELITEGROUP_05D8 0x05d8 diff --git a/drivers/hid/hid-led.c b/drivers/hid/hid-led.c new file mode 100644 index 000000000000..43fb089a8cd5 --- /dev/null +++ b/drivers/hid/hid-led.c @@ -0,0 +1,288 @@ +/* + * Simple USB RGB LED driver + * + * Copyright 2016 Heiner Kallweit + * Based on drivers/hid/hid-thingm.c and + * drivers/usb/misc/usbled.c + * + * 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 + +#include "hid-ids.h" + +enum hidled_report_type { + RAW_REQUEST, + OUTPUT_REPORT +}; + +enum hidled_type { + RISO_KAGAKU, + DREAM_CHEEKY, +}; + +static unsigned const char riso_kagaku_tbl[] = { +/* R+2G+4B -> riso kagaku color index */ + [0] = 0, /* black */ + [1] = 2, /* red */ + [2] = 1, /* green */ + [3] = 5, /* yellow */ + [4] = 3, /* blue */ + [5] = 6, /* magenta */ + [6] = 4, /* cyan */ + [7] = 7 /* white */ +}; + +#define RISO_KAGAKU_IX(r, g, b) riso_kagaku_tbl[((r)?1:0)+((g)?2:0)+((b)?4:0)] + +struct hidled_device; + +struct hidled_config { + enum hidled_type type; + const char *name; + const char *short_name; + enum led_brightness max_brightness; + size_t report_size; + enum hidled_report_type report_type; + u8 report_id; + int (*init)(struct hidled_device *ldev); + int (*write)(struct led_classdev *cdev, enum led_brightness br); +}; + +struct hidled_led { + struct led_classdev cdev; + struct hidled_device *ldev; + char name[32]; +}; + +struct hidled_device { + const struct hidled_config *config; + struct hidled_led red; + struct hidled_led green; + struct hidled_led blue; + struct hid_device *hdev; + struct mutex lock; +}; + +#define MAX_REPORT_SIZE 16 + +#define to_hidled_led(arg) container_of(arg, struct hidled_led, cdev) + +static bool riso_kagaku_switch_green_blue; +module_param(riso_kagaku_switch_green_blue, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(riso_kagaku_switch_green_blue, + "switch green and blue RGB component for Riso Kagaku devices"); + +static int hidled_send(struct hidled_device *ldev, __u8 *buf) +{ + int ret; + + buf[0] = ldev->config->report_id; + + mutex_lock(&ldev->lock); + + if (ldev->config->report_type == RAW_REQUEST) + ret = hid_hw_raw_request(ldev->hdev, buf[0], buf, + ldev->config->report_size, + HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + else if (ldev->config->report_type == OUTPUT_REPORT) + ret = hid_hw_output_report(ldev->hdev, buf, + ldev->config->report_size); + else + ret = -EINVAL; + + mutex_unlock(&ldev->lock); + + if (ret < 0) + return ret; + + return ret == ldev->config->report_size ? 0 : -EMSGSIZE; +} + +static u8 riso_kagaku_index(struct hidled_device *ldev) +{ + enum led_brightness r, g, b; + + r = ldev->red.cdev.brightness; + g = ldev->green.cdev.brightness; + b = ldev->blue.cdev.brightness; + + if (riso_kagaku_switch_green_blue) + return RISO_KAGAKU_IX(r, b, g); + else + return RISO_KAGAKU_IX(r, g, b); +} + +static int riso_kagaku_write(struct led_classdev *cdev, enum led_brightness br) +{ + struct hidled_led *led = to_hidled_led(cdev); + struct hidled_device *ldev = led->ldev; + __u8 buf[MAX_REPORT_SIZE] = {}; + + buf[1] = riso_kagaku_index(ldev); + + return hidled_send(ldev, buf); +} + +static int dream_cheeky_write(struct led_classdev *cdev, enum led_brightness br) +{ + struct hidled_led *led = to_hidled_led(cdev); + struct hidled_device *ldev = led->ldev; + __u8 buf[MAX_REPORT_SIZE] = {}; + + buf[1] = ldev->red.cdev.brightness; + buf[2] = ldev->green.cdev.brightness; + buf[3] = ldev->blue.cdev.brightness; + buf[7] = 0x1a; + buf[8] = 0x05; + + return hidled_send(ldev, buf); +} + +static int dream_cheeky_init(struct hidled_device *ldev) +{ + __u8 buf[MAX_REPORT_SIZE] = {}; + + /* Dream Cheeky magic */ + buf[1] = 0x1f; + buf[2] = 0x02; + buf[4] = 0x5f; + buf[7] = 0x1a; + buf[8] = 0x03; + + return hidled_send(ldev, buf); +} + +static const struct hidled_config hidled_configs[] = { + { + .type = RISO_KAGAKU, + .name = "Riso Kagaku Webmail Notifier", + .short_name = "riso_kagaku", + .max_brightness = 1, + .report_size = 6, + .report_type = OUTPUT_REPORT, + .report_id = 0, + .write = riso_kagaku_write, + }, + { + .type = DREAM_CHEEKY, + .name = "Dream Cheeky Webmail Notifier", + .short_name = "dream_cheeky", + .max_brightness = 31, + .report_size = 9, + .report_type = RAW_REQUEST, + .report_id = 0, + .init = dream_cheeky_init, + .write = dream_cheeky_write, + }, +}; + +static int hidled_init_led(struct hidled_led *led, const char *color_name, + struct hidled_device *ldev, unsigned int minor) +{ + snprintf(led->name, sizeof(led->name), "%s%u:%s", + ldev->config->short_name, minor, color_name); + led->cdev.name = led->name; + led->cdev.max_brightness = ldev->config->max_brightness; + led->cdev.brightness_set_blocking = ldev->config->write; + led->cdev.flags = LED_HW_PLUGGABLE; + led->ldev = ldev; + + return devm_led_classdev_register(&ldev->hdev->dev, &led->cdev); +} + +static int hidled_init_rgb(struct hidled_device *ldev, unsigned int minor) +{ + int ret; + + /* Register the red diode */ + ret = hidled_init_led(&ldev->red, "red", ldev, minor); + if (ret) + return ret; + + /* Register the green diode */ + ret = hidled_init_led(&ldev->green, "green", ldev, minor); + if (ret) + return ret; + + /* Register the blue diode */ + return hidled_init_led(&ldev->blue, "blue", ldev, minor); +} + +static int hidled_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct hidled_device *ldev; + unsigned int minor; + int ret, i; + + ldev = devm_kzalloc(&hdev->dev, sizeof(*ldev), GFP_KERNEL); + if (!ldev) + return -ENOMEM; + + ret = hid_parse(hdev); + if (ret) + return ret; + + ldev->hdev = hdev; + mutex_init(&ldev->lock); + + for (i = 0; !ldev->config && i < ARRAY_SIZE(hidled_configs); i++) + if (hidled_configs[i].type == id->driver_data) + ldev->config = &hidled_configs[i]; + + if (!ldev->config) + return -EINVAL; + + if (ldev->config->init) { + ret = ldev->config->init(ldev); + if (ret) + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) + return ret; + + minor = ((struct hidraw *) hdev->hidraw)->minor; + + ret = hidled_init_rgb(ldev, minor); + if (ret) { + hid_hw_stop(hdev); + return ret; + } + + hid_info(hdev, "%s initialized\n", ldev->config->name); + + return 0; +} + +static const struct hid_device_id hidled_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU, + USB_DEVICE_ID_RI_KA_WEBMAIL), .driver_data = RISO_KAGAKU }, + { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, + USB_DEVICE_ID_DREAM_CHEEKY_WN), .driver_data = DREAM_CHEEKY }, + { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, + USB_DEVICE_ID_DREAM_CHEEKY_FA), .driver_data = DREAM_CHEEKY }, + { } +}; +MODULE_DEVICE_TABLE(hid, hidled_table); + +static struct hid_driver hidled_driver = { + .name = "hid-led", + .probe = hidled_probe, + .id_table = hidled_table, +}; + +module_hid_driver(hidled_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Heiner Kallweit "); +MODULE_DESCRIPTION("Simple USB RGB LED driver"); From a335aaf3125c02a47bc108e9f4c6cb66ca84ce46 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Fri, 17 Jun 2016 08:11:59 +0200 Subject: [PATCH 02/11] usb: misc: remove outdated USB LED driver The USB LED driver exposes a undocumented sysfs interface and doesn't use the standard kernel LED subsystem. It supports three devices: Delcom Visual Signal Indicator The driver supports generation 1 of the device only which was manufactured until 2008. Remove support for this device completely. Riso Kagaku RGB LED + Dream Cheeky Webmail Notifier These devices are HID compliant and are supported by a new USB LED driver under drivers/hid utilizing the kernel LED subsystem. So let's remove the old USB LED driver. Signed-off-by: Heiner Kallweit Acked-by: Greg Kroah-Hartman Acked-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/usb/misc/Kconfig | 9 -- drivers/usb/misc/Makefile | 1 - drivers/usb/misc/usbled.c | 273 -------------------------------------- 3 files changed, 283 deletions(-) delete mode 100644 drivers/usb/misc/usbled.c diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index f7a7fc21be8a..e9c5458dd848 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -79,15 +79,6 @@ config USB_LCD To compile this driver as a module, choose M here: the module will be called usblcd. -config USB_LED - tristate "USB LED driver support" - help - Say Y here if you want to connect an USBLED device to your - computer's USB port. - - To compile this driver as a module, choose M here: the - module will be called usbled. - config USB_CYPRESS_CY7C63 tristate "Cypress CY7C63xxx USB driver support" help diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 45fd4ac39d3e..4bc755b15418 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -15,7 +15,6 @@ obj-$(CONFIG_USB_IOWARRIOR) += iowarrior.o obj-$(CONFIG_USB_ISIGHTFW) += isight_firmware.o obj-$(CONFIG_USB_LCD) += usblcd.o obj-$(CONFIG_USB_LD) += ldusb.o -obj-$(CONFIG_USB_LED) += usbled.o obj-$(CONFIG_USB_LEGOTOWER) += legousbtower.o obj-$(CONFIG_USB_RIO500) += rio500.o obj-$(CONFIG_USB_TEST) += usbtest.o diff --git a/drivers/usb/misc/usbled.c b/drivers/usb/misc/usbled.c deleted file mode 100644 index bdef0d6eb91d..000000000000 --- a/drivers/usb/misc/usbled.c +++ /dev/null @@ -1,273 +0,0 @@ -/* - * USB LED driver - * - * Copyright (C) 2004 Greg Kroah-Hartman (greg@kroah.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 -#include -#include -#include -#include - - -#define DRIVER_AUTHOR "Greg Kroah-Hartman, greg@kroah.com" -#define DRIVER_DESC "USB LED Driver" - -enum led_type { - DELCOM_VISUAL_SIGNAL_INDICATOR, - DREAM_CHEEKY_WEBMAIL_NOTIFIER, - RISO_KAGAKU_LED -}; - -/* the Webmail LED made by RISO KAGAKU CORP. decodes a color index - internally, we want to keep the red+green+blue sysfs api, so we decode - from 1-bit RGB to the riso kagaku color index according to this table... */ - -static unsigned const char riso_kagaku_tbl[] = { -/* R+2G+4B -> riso kagaku color index */ - [0] = 0, /* black */ - [1] = 2, /* red */ - [2] = 1, /* green */ - [3] = 5, /* yellow */ - [4] = 3, /* blue */ - [5] = 6, /* magenta */ - [6] = 4, /* cyan */ - [7] = 7 /* white */ -}; - -#define RISO_KAGAKU_IX(r,g,b) riso_kagaku_tbl[((r)?1:0)+((g)?2:0)+((b)?4:0)] - -/* table of devices that work with this driver */ -static const struct usb_device_id id_table[] = { - { USB_DEVICE(0x0fc5, 0x1223), - .driver_info = DELCOM_VISUAL_SIGNAL_INDICATOR }, - { USB_DEVICE(0x1d34, 0x0004), - .driver_info = DREAM_CHEEKY_WEBMAIL_NOTIFIER }, - { USB_DEVICE(0x1d34, 0x000a), - .driver_info = DREAM_CHEEKY_WEBMAIL_NOTIFIER }, - { USB_DEVICE(0x1294, 0x1320), - .driver_info = RISO_KAGAKU_LED }, - { }, -}; -MODULE_DEVICE_TABLE(usb, id_table); - -struct usb_led { - struct usb_device *udev; - unsigned char blue; - unsigned char red; - unsigned char green; - enum led_type type; -}; - -static void change_color(struct usb_led *led) -{ - int retval = 0; - unsigned char *buffer; - int actlength; - - buffer = kmalloc(8, GFP_KERNEL); - if (!buffer) { - dev_err(&led->udev->dev, "out of memory\n"); - return; - } - - switch (led->type) { - case DELCOM_VISUAL_SIGNAL_INDICATOR: { - unsigned char color = 0x07; - - if (led->blue) - color &= ~0x04; - if (led->red) - color &= ~0x02; - if (led->green) - color &= ~0x01; - dev_dbg(&led->udev->dev, - "blue = %d, red = %d, green = %d, color = %.2x\n", - led->blue, led->red, led->green, color); - - retval = usb_control_msg(led->udev, - usb_sndctrlpipe(led->udev, 0), - 0x12, - 0xc8, - (0x02 * 0x100) + 0x0a, - (0x00 * 0x100) + color, - buffer, - 8, - 2000); - break; - } - - case DREAM_CHEEKY_WEBMAIL_NOTIFIER: - dev_dbg(&led->udev->dev, - "red = %d, green = %d, blue = %d\n", - led->red, led->green, led->blue); - - buffer[0] = led->red; - buffer[1] = led->green; - buffer[2] = led->blue; - buffer[3] = buffer[4] = buffer[5] = 0; - buffer[6] = 0x1a; - buffer[7] = 0x05; - - retval = usb_control_msg(led->udev, - usb_sndctrlpipe(led->udev, 0), - 0x09, - 0x21, - 0x200, - 0, - buffer, - 8, - 2000); - break; - - case RISO_KAGAKU_LED: - buffer[0] = RISO_KAGAKU_IX(led->red, led->green, led->blue); - buffer[1] = 0; - buffer[2] = 0; - buffer[3] = 0; - buffer[4] = 0; - - retval = usb_interrupt_msg(led->udev, - usb_sndctrlpipe(led->udev, 2), - buffer, 5, &actlength, 1000 /*ms timeout*/); - break; - - default: - dev_err(&led->udev->dev, "unknown device type %d\n", led->type); - } - - if (retval) - dev_dbg(&led->udev->dev, "retval = %d\n", retval); - kfree(buffer); -} - -#define show_set(value) \ -static ssize_t show_##value(struct device *dev, struct device_attribute *attr,\ - char *buf) \ -{ \ - struct usb_interface *intf = to_usb_interface(dev); \ - struct usb_led *led = usb_get_intfdata(intf); \ - \ - return sprintf(buf, "%d\n", led->value); \ -} \ -static ssize_t set_##value(struct device *dev, struct device_attribute *attr,\ - const char *buf, size_t count) \ -{ \ - struct usb_interface *intf = to_usb_interface(dev); \ - struct usb_led *led = usb_get_intfdata(intf); \ - int temp = simple_strtoul(buf, NULL, 10); \ - \ - led->value = temp; \ - change_color(led); \ - return count; \ -} \ -static DEVICE_ATTR(value, S_IRUGO | S_IWUSR, show_##value, set_##value); -show_set(blue); -show_set(red); -show_set(green); - -static int led_probe(struct usb_interface *interface, - const struct usb_device_id *id) -{ - struct usb_device *udev = interface_to_usbdev(interface); - struct usb_led *dev = NULL; - int retval = -ENOMEM; - - dev = kzalloc(sizeof(struct usb_led), GFP_KERNEL); - if (dev == NULL) { - dev_err(&interface->dev, "out of memory\n"); - goto error_mem; - } - - dev->udev = usb_get_dev(udev); - dev->type = id->driver_info; - - usb_set_intfdata(interface, dev); - - retval = device_create_file(&interface->dev, &dev_attr_blue); - if (retval) - goto error; - retval = device_create_file(&interface->dev, &dev_attr_red); - if (retval) - goto error; - retval = device_create_file(&interface->dev, &dev_attr_green); - if (retval) - goto error; - - if (dev->type == DREAM_CHEEKY_WEBMAIL_NOTIFIER) { - unsigned char *enable; - - enable = kmemdup("\x1f\x02\0\x5f\0\0\x1a\x03", 8, GFP_KERNEL); - if (!enable) { - dev_err(&interface->dev, "out of memory\n"); - retval = -ENOMEM; - goto error; - } - - retval = usb_control_msg(udev, - usb_sndctrlpipe(udev, 0), - 0x09, - 0x21, - 0x200, - 0, - enable, - 8, - 2000); - - kfree(enable); - if (retval != 8) - goto error; - } - - dev_info(&interface->dev, "USB LED device now attached\n"); - return 0; - -error: - device_remove_file(&interface->dev, &dev_attr_blue); - device_remove_file(&interface->dev, &dev_attr_red); - device_remove_file(&interface->dev, &dev_attr_green); - usb_set_intfdata(interface, NULL); - usb_put_dev(dev->udev); - kfree(dev); -error_mem: - return retval; -} - -static void led_disconnect(struct usb_interface *interface) -{ - struct usb_led *dev; - - dev = usb_get_intfdata(interface); - - device_remove_file(&interface->dev, &dev_attr_blue); - device_remove_file(&interface->dev, &dev_attr_red); - device_remove_file(&interface->dev, &dev_attr_green); - - /* first remove the files, then set the pointer to NULL */ - usb_set_intfdata(interface, NULL); - - usb_put_dev(dev->udev); - - kfree(dev); - - dev_info(&interface->dev, "USB LED now disconnected\n"); -} - -static struct usb_driver led_driver = { - .name = "usbled", - .probe = led_probe, - .disconnect = led_disconnect, - .id_table = id_table, -}; - -module_usb_driver(led_driver); - -MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); From 26423b84a3e6beb437fe1078b5970f8b60dee037 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Fri, 17 Jun 2016 22:29:47 +0200 Subject: [PATCH 03/11] HID: led: fix config The driver port was done carefully not to depend on USB at all, in favor of being generic HID driver instead. Therefore there is no need to explicitly talk about USB only in the config. Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 04bd2039b23b..9eeb0138ede3 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -389,11 +389,11 @@ config HID_LCPOWER Support for LC-Power RC1000MCE RF remote control. config HID_LED - tristate "Simple USB RGB LED support" + tristate "Simple RGB LED support" depends on HID depends on LEDS_CLASS ---help--- - Support for simple USB RGB LED devices. Currently supported are the + Support for simple RGB LED devices. Currently supported are the Riso Kagaku Webmail Notifier and the Dream Cheeky Webmail Notifier and Friends Alert. From 5bc839367bce7a609986a446bd7288409f51f1bc Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Tue, 21 Jun 2016 21:48:26 +0200 Subject: [PATCH 04/11] HID: hid-led: add support for devices with multiple independent LEDs Add generic support for devices with multiple independent LED's. Signed-off-by: Heiner Kallweit Signed-off-by: Jiri Kosina --- drivers/hid/hid-led.c | 91 +++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/drivers/hid/hid-led.c b/drivers/hid/hid-led.c index 43fb089a8cd5..d5d8d941c4b0 100644 --- a/drivers/hid/hid-led.c +++ b/drivers/hid/hid-led.c @@ -43,12 +43,14 @@ static unsigned const char riso_kagaku_tbl[] = { #define RISO_KAGAKU_IX(r, g, b) riso_kagaku_tbl[((r)?1:0)+((g)?2:0)+((b)?4:0)] struct hidled_device; +struct hidled_rgb; struct hidled_config { enum hidled_type type; const char *name; const char *short_name; enum led_brightness max_brightness; + int num_leds; size_t report_size; enum hidled_report_type report_type; u8 report_id; @@ -58,16 +60,22 @@ struct hidled_config { struct hidled_led { struct led_classdev cdev; - struct hidled_device *ldev; + struct hidled_rgb *rgb; char name[32]; }; +struct hidled_rgb { + struct hidled_device *ldev; + struct hidled_led red; + struct hidled_led green; + struct hidled_led blue; + u8 num; +}; + struct hidled_device { const struct hidled_config *config; - struct hidled_led red; - struct hidled_led green; - struct hidled_led blue; struct hid_device *hdev; + struct hidled_rgb *rgb; struct mutex lock; }; @@ -107,13 +115,13 @@ static int hidled_send(struct hidled_device *ldev, __u8 *buf) return ret == ldev->config->report_size ? 0 : -EMSGSIZE; } -static u8 riso_kagaku_index(struct hidled_device *ldev) +static u8 riso_kagaku_index(struct hidled_rgb *rgb) { enum led_brightness r, g, b; - r = ldev->red.cdev.brightness; - g = ldev->green.cdev.brightness; - b = ldev->blue.cdev.brightness; + r = rgb->red.cdev.brightness; + g = rgb->green.cdev.brightness; + b = rgb->blue.cdev.brightness; if (riso_kagaku_switch_green_blue) return RISO_KAGAKU_IX(r, b, g); @@ -124,27 +132,27 @@ static u8 riso_kagaku_index(struct hidled_device *ldev) static int riso_kagaku_write(struct led_classdev *cdev, enum led_brightness br) { struct hidled_led *led = to_hidled_led(cdev); - struct hidled_device *ldev = led->ldev; + struct hidled_rgb *rgb = led->rgb; __u8 buf[MAX_REPORT_SIZE] = {}; - buf[1] = riso_kagaku_index(ldev); + buf[1] = riso_kagaku_index(rgb); - return hidled_send(ldev, buf); + return hidled_send(rgb->ldev, buf); } static int dream_cheeky_write(struct led_classdev *cdev, enum led_brightness br) { struct hidled_led *led = to_hidled_led(cdev); - struct hidled_device *ldev = led->ldev; + struct hidled_rgb *rgb = led->rgb; __u8 buf[MAX_REPORT_SIZE] = {}; - buf[1] = ldev->red.cdev.brightness; - buf[2] = ldev->green.cdev.brightness; - buf[3] = ldev->blue.cdev.brightness; + buf[1] = rgb->red.cdev.brightness; + buf[2] = rgb->green.cdev.brightness; + buf[3] = rgb->blue.cdev.brightness; buf[7] = 0x1a; buf[8] = 0x05; - return hidled_send(ldev, buf); + return hidled_send(rgb->ldev, buf); } static int dream_cheeky_init(struct hidled_device *ldev) @@ -167,6 +175,7 @@ static const struct hidled_config hidled_configs[] = { .name = "Riso Kagaku Webmail Notifier", .short_name = "riso_kagaku", .max_brightness = 1, + .num_leds = 1, .report_size = 6, .report_type = OUTPUT_REPORT, .report_id = 0, @@ -177,6 +186,7 @@ static const struct hidled_config hidled_configs[] = { .name = "Dream Cheeky Webmail Notifier", .short_name = "dream_cheeky", .max_brightness = 31, + .num_leds = 1, .report_size = 9, .report_type = RAW_REQUEST, .report_id = 0, @@ -186,35 +196,41 @@ static const struct hidled_config hidled_configs[] = { }; static int hidled_init_led(struct hidled_led *led, const char *color_name, - struct hidled_device *ldev, unsigned int minor) + struct hidled_rgb *rgb, unsigned int minor) { - snprintf(led->name, sizeof(led->name), "%s%u:%s", - ldev->config->short_name, minor, color_name); - led->cdev.name = led->name; - led->cdev.max_brightness = ldev->config->max_brightness; - led->cdev.brightness_set_blocking = ldev->config->write; - led->cdev.flags = LED_HW_PLUGGABLE; - led->ldev = ldev; + const struct hidled_config *config = rgb->ldev->config; - return devm_led_classdev_register(&ldev->hdev->dev, &led->cdev); + if (config->num_leds > 1) + snprintf(led->name, sizeof(led->name), "%s%u:%s:led%u", + config->short_name, minor, color_name, rgb->num); + else + snprintf(led->name, sizeof(led->name), "%s%u:%s", + config->short_name, minor, color_name); + led->cdev.name = led->name; + led->cdev.max_brightness = config->max_brightness; + led->cdev.brightness_set_blocking = config->write; + led->cdev.flags = LED_HW_PLUGGABLE; + led->rgb = rgb; + + return devm_led_classdev_register(&rgb->ldev->hdev->dev, &led->cdev); } -static int hidled_init_rgb(struct hidled_device *ldev, unsigned int minor) +static int hidled_init_rgb(struct hidled_rgb *rgb, unsigned int minor) { int ret; /* Register the red diode */ - ret = hidled_init_led(&ldev->red, "red", ldev, minor); + ret = hidled_init_led(&rgb->red, "red", rgb, minor); if (ret) return ret; /* Register the green diode */ - ret = hidled_init_led(&ldev->green, "green", ldev, minor); + ret = hidled_init_led(&rgb->green, "green", rgb, minor); if (ret) return ret; /* Register the blue diode */ - return hidled_init_led(&ldev->blue, "blue", ldev, minor); + return hidled_init_led(&rgb->blue, "blue", rgb, minor); } static int hidled_probe(struct hid_device *hdev, const struct hid_device_id *id) @@ -247,16 +263,25 @@ static int hidled_probe(struct hid_device *hdev, const struct hid_device_id *id) return ret; } + ldev->rgb = devm_kcalloc(&hdev->dev, ldev->config->num_leds, + sizeof(struct hidled_rgb), GFP_KERNEL); + if (!ldev->rgb) + return -ENOMEM; + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); if (ret) return ret; minor = ((struct hidraw *) hdev->hidraw)->minor; - ret = hidled_init_rgb(ldev, minor); - if (ret) { - hid_hw_stop(hdev); - return ret; + for (i = 0; i < ldev->config->num_leds; i++) { + ldev->rgb[i].ldev = ldev; + ldev->rgb[i].num = i; + ret = hidled_init_rgb(&ldev->rgb[i], minor); + if (ret) { + hid_hw_stop(hdev); + return ret; + } } hid_info(hdev, "%s initialized\n", ldev->config->name); From 4374573008df08a3a7a84cd827ed8f1ceee9901b Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Tue, 21 Jun 2016 21:48:46 +0200 Subject: [PATCH 05/11] HID: hid-led: add support for reading from LED devices Add support for reading data from LED devices. Signed-off-by: Heiner Kallweit Signed-off-by: Jiri Kosina --- drivers/hid/hid-led.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/hid/hid-led.c b/drivers/hid/hid-led.c index d5d8d941c4b0..4075f095c2a2 100644 --- a/drivers/hid/hid-led.c +++ b/drivers/hid/hid-led.c @@ -115,6 +115,35 @@ static int hidled_send(struct hidled_device *ldev, __u8 *buf) return ret == ldev->config->report_size ? 0 : -EMSGSIZE; } +/* reading data is supported for report type RAW_REQUEST only */ +static int hidled_recv(struct hidled_device *ldev, __u8 *buf) +{ + int ret; + + if (ldev->config->report_type != RAW_REQUEST) + return -EINVAL; + + buf[0] = ldev->config->report_id; + + mutex_lock(&ldev->lock); + + ret = hid_hw_raw_request(ldev->hdev, buf[0], buf, + ldev->config->report_size, + HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + if (ret < 0) + goto err; + + ret = hid_hw_raw_request(ldev->hdev, buf[0], buf, + ldev->config->report_size, + HID_FEATURE_REPORT, + HID_REQ_GET_REPORT); +err: + mutex_unlock(&ldev->lock); + + return ret < 0 ? ret : 0; +} + static u8 riso_kagaku_index(struct hidled_rgb *rgb) { enum led_brightness r, g, b; From 007414e8fbc8894f33a548874946b6edd2a2918e Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Tue, 21 Jun 2016 21:49:29 +0200 Subject: [PATCH 06/11] HID: hid-led: add support for ThingM blink(1) Add support for ThingM blink(1) and make the dedicated ThingM driver obsolete. Successfully tested with a blink(1) mk2. Signed-off-by: Heiner Kallweit Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 7 +++-- drivers/hid/hid-led.c | 66 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 9eeb0138ede3..0fecf8f0bb9d 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -393,9 +393,10 @@ config HID_LED depends on HID depends on LEDS_CLASS ---help--- - Support for simple RGB LED devices. Currently supported are the - Riso Kagaku Webmail Notifier and the Dream Cheeky Webmail Notifier - and Friends Alert. + Support for simple RGB LED devices. Currently supported are: + - Riso Kagaku Webmail Notifier + - Dream Cheeky Webmail Notifier and Friends Alert + - ThingM blink(1) To compile this driver as a module, choose M here: the module will be called hid-led. diff --git a/drivers/hid/hid-led.c b/drivers/hid/hid-led.c index 4075f095c2a2..e11b6455429b 100644 --- a/drivers/hid/hid-led.c +++ b/drivers/hid/hid-led.c @@ -26,6 +26,7 @@ enum hidled_report_type { enum hidled_type { RISO_KAGAKU, DREAM_CHEEKY, + THINGM, }; static unsigned const char riso_kagaku_tbl[] = { @@ -198,6 +199,57 @@ static int dream_cheeky_init(struct hidled_device *ldev) return hidled_send(ldev, buf); } +static int _thingm_write(struct led_classdev *cdev, enum led_brightness br, + u8 offset) +{ + struct hidled_led *led = to_hidled_led(cdev); + __u8 buf[MAX_REPORT_SIZE] = { [1] = 'c' }; + + buf[2] = led->rgb->red.cdev.brightness; + buf[3] = led->rgb->green.cdev.brightness; + buf[4] = led->rgb->blue.cdev.brightness; + buf[7] = led->rgb->num + offset; + + return hidled_send(led->rgb->ldev, buf); +} + +static int thingm_write_v1(struct led_classdev *cdev, enum led_brightness br) +{ + return _thingm_write(cdev, br, 0); +} + +static int thingm_write(struct led_classdev *cdev, enum led_brightness br) +{ + return _thingm_write(cdev, br, 1); +} + +static const struct hidled_config hidled_config_thingm_v1 = { + .name = "ThingM blink(1) v1", + .short_name = "thingm", + .max_brightness = 255, + .num_leds = 1, + .report_size = 9, + .report_type = RAW_REQUEST, + .report_id = 1, + .write = thingm_write_v1, +}; + +static int thingm_init(struct hidled_device *ldev) +{ + __u8 buf[MAX_REPORT_SIZE] = { [1] = 'v' }; + int ret; + + ret = hidled_recv(ldev, buf); + if (ret) + return ret; + + /* Check for firmware major version 1 */ + if (buf[3] == '1') + ldev->config = &hidled_config_thingm_v1; + + return 0; +} + static const struct hidled_config hidled_configs[] = { { .type = RISO_KAGAKU, @@ -222,6 +274,18 @@ static const struct hidled_config hidled_configs[] = { .init = dream_cheeky_init, .write = dream_cheeky_write, }, + { + .type = THINGM, + .name = "ThingM blink(1)", + .short_name = "thingm", + .max_brightness = 255, + .num_leds = 2, + .report_size = 9, + .report_type = RAW_REQUEST, + .report_id = 1, + .init = thingm_init, + .write = thingm_write, + }, }; static int hidled_init_led(struct hidled_led *led, const char *color_name, @@ -325,6 +389,8 @@ static const struct hid_device_id hidled_table[] = { USB_DEVICE_ID_DREAM_CHEEKY_WN), .driver_data = DREAM_CHEEKY }, { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, USB_DEVICE_ID_DREAM_CHEEKY_FA), .driver_data = DREAM_CHEEKY }, + { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, + USB_DEVICE_ID_BLINK1), .driver_data = THINGM }, { } }; MODULE_DEVICE_TABLE(hid, hidled_table); From 38b09c030b91c368a839b4783abab51a9745a633 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 22 Jun 2016 22:04:36 +0200 Subject: [PATCH 07/11] HID: remove ThingM blink(1) driver Now that support for ThingM blink(1) was merged into the hid-led driver the dedicated driver for this device can be removed. Signed-off-by: Heiner Kallweit Acked-by: Vivien Didelot Signed-off-by: Jiri Kosina --- MAINTAINERS | 5 - drivers/hid/Kconfig | 8 +- drivers/hid/Makefile | 1 - drivers/hid/hid-thingm.c | 263 --------------------------------------- 4 files changed, 4 insertions(+), 273 deletions(-) delete mode 100644 drivers/hid/hid-thingm.c diff --git a/MAINTAINERS b/MAINTAINERS index 1dd9335de071..ca05aacdf94e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11042,11 +11042,6 @@ F: Documentation/thermal/cpu-cooling-api.txt F: drivers/thermal/cpu_cooling.c F: include/linux/cpu_cooling.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 0fecf8f0bb9d..8b1eb47930bc 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -832,11 +832,11 @@ config HID_THINGM tristate "ThingM blink(1) USB RGB LED" depends on HID depends on LEDS_CLASS + select HID_LED ---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. + Support for the ThingM blink(1) USB RGB LED. This driver has been + merged into the generic hid led driver. Config symbol HID_THINGM + just selects HID_LED and will be removed soon. config HID_THRUSTMASTER tristate "ThrustMaster devices support" diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 86d71f60a967..ab5a5b005e5d 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -90,7 +90,6 @@ obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.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-thingm.c b/drivers/hid/hid-thingm.c deleted file mode 100644 index 9ad9c6ec5bba..000000000000 --- a/drivers/hid/hid-thingm.c +++ /dev/null @@ -1,263 +0,0 @@ -/* - * ThingM blink(1) USB RGB LED driver - * - * Copyright 2013-2014 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 - -#include "hid-ids.h" - -#define REPORT_ID 1 -#define REPORT_SIZE 9 - -/* Firmware major number of supported devices */ -#define THINGM_MAJOR_MK1 '1' -#define THINGM_MAJOR_MK2 '2' - -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; - u8 num; -}; - -struct thingm_device { - struct hid_device *hdev; - struct { - char major; - char minor; - } version; - const struct thingm_fwinfo *fwinfo; - struct mutex lock; - struct thingm_rgb *rgb; -}; - -static int thingm_send(struct thingm_device *tdev, u8 buf[REPORT_SIZE]) -{ - int ret; - - 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]); - - mutex_lock(&tdev->lock); - - ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE, - HID_FEATURE_REPORT, HID_REQ_SET_REPORT); - - mutex_unlock(&tdev->lock); - - return ret < 0 ? ret : 0; -} - -static int thingm_recv(struct thingm_device *tdev, u8 buf[REPORT_SIZE]) -{ - int ret; - - /* - * A read consists of two operations: sending the read command - * and the actual read from the device. Use the mutex to protect - * the full sequence of both operations. - */ - mutex_lock(&tdev->lock); - - ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE, - HID_FEATURE_REPORT, HID_REQ_SET_REPORT); - if (ret < 0) - goto err; - - ret = hid_hw_raw_request(tdev->hdev, buf[0], buf, REPORT_SIZE, - HID_FEATURE_REPORT, HID_REQ_GET_REPORT); - if (ret < 0) - goto err; - - ret = 0; - - 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]); -err: - mutex_unlock(&tdev->lock); - return ret; -} - -static int thingm_version(struct thingm_device *tdev) -{ - u8 buf[REPORT_SIZE] = { REPORT_ID, 'v', 0, 0, 0, 0, 0, 0, 0 }; - int err; - - err = thingm_recv(tdev, buf); - if (err) - return err; - - tdev->version.major = buf[3]; - tdev->version.minor = buf[4]; - - return 0; -} - -static int thingm_write_color(struct thingm_rgb *rgb) -{ - u8 buf[REPORT_SIZE] = { REPORT_ID, 'c', 0, 0, 0, 0, 0, rgb->num, 0 }; - - 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 int thingm_led_set(struct led_classdev *ldev, - enum led_brightness brightness) -{ - struct thingm_led *led = container_of(ldev, struct thingm_led, ldev); - - return thingm_write_color(led->rgb); -} - -static int thingm_init_led(struct thingm_led *led, const char *color_name, - struct thingm_rgb *rgb, int minor) -{ - snprintf(led->name, sizeof(led->name), "thingm%d:%s:led%d", - minor, color_name, rgb->num); - led->ldev.name = led->name; - led->ldev.max_brightness = 255; - led->ldev.brightness_set_blocking = thingm_led_set; - led->ldev.flags = LED_HW_PLUGGABLE; - led->rgb = rgb; - return devm_led_classdev_register(&rgb->tdev->hdev->dev, &led->ldev); -} - -static int thingm_init_rgb(struct thingm_rgb *rgb) -{ - const int minor = ((struct hidraw *) rgb->tdev->hdev->hidraw)->minor; - int err; - - /* Register the red diode */ - err = thingm_init_led(&rgb->red, "red", rgb, minor); - if (err) - return err; - - /* Register the green diode */ - err = thingm_init_led(&rgb->green, "green", rgb, minor); - if (err) - return err; - - /* Register the blue diode */ - return thingm_init_led(&rgb->blue, "blue", rgb, minor); -} - -static int thingm_probe(struct hid_device *hdev, const struct hid_device_id *id) -{ - struct thingm_device *tdev; - int i, err; - - tdev = devm_kzalloc(&hdev->dev, sizeof(struct thingm_device), - GFP_KERNEL); - if (!tdev) - return -ENOMEM; - - tdev->hdev = hdev; - hid_set_drvdata(hdev, tdev); - - err = hid_parse(hdev); - if (err) - return err; - - mutex_init(&tdev->lock); - - err = thingm_version(tdev); - if (err) - return err; - - hid_dbg(hdev, "firmware version: %c.%c\n", - tdev->version.major, tdev->version.minor); - - for (i = 0; i < ARRAY_SIZE(thingm_fwinfo) && !tdev->fwinfo; ++i) - if (thingm_fwinfo[i].major == tdev->version.major) - tdev->fwinfo = &thingm_fwinfo[i]; - - if (!tdev->fwinfo) { - hid_err(hdev, "unsupported firmware %c\n", tdev->version.major); - return -ENODEV; - } - - tdev->rgb = devm_kzalloc(&hdev->dev, - sizeof(struct thingm_rgb) * tdev->fwinfo->numrgb, - GFP_KERNEL); - if (!tdev->rgb) - return -ENOMEM; - - err = hid_hw_start(hdev, HID_CONNECT_HIDRAW); - if (err) - return err; - - 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) { - hid_hw_stop(hdev); - return err; - } - } - - return 0; -} - -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, - .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"); From 34d9810b316b6de11a801a30339c070d1798a7b0 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sun, 3 Jul 2016 17:33:04 +0200 Subject: [PATCH 08/11] HID: hid-led: remove report id from struct hidled_config Delcom uses the report id to submit command information. To be able to use the hidled framework also for Delcom devices we have to remove the report id from struct hidled_config. Signed-off-by: Heiner Kallweit Signed-off-by: Jiri Kosina --- drivers/hid/hid-led.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/drivers/hid/hid-led.c b/drivers/hid/hid-led.c index e11b6455429b..fce2a03cb647 100644 --- a/drivers/hid/hid-led.c +++ b/drivers/hid/hid-led.c @@ -54,7 +54,6 @@ struct hidled_config { int num_leds; size_t report_size; enum hidled_report_type report_type; - u8 report_id; int (*init)(struct hidled_device *ldev); int (*write)(struct led_classdev *cdev, enum led_brightness br); }; @@ -93,8 +92,6 @@ static int hidled_send(struct hidled_device *ldev, __u8 *buf) { int ret; - buf[0] = ldev->config->report_id; - mutex_lock(&ldev->lock); if (ldev->config->report_type == RAW_REQUEST) @@ -124,8 +121,6 @@ static int hidled_recv(struct hidled_device *ldev, __u8 *buf) if (ldev->config->report_type != RAW_REQUEST) return -EINVAL; - buf[0] = ldev->config->report_id; - mutex_lock(&ldev->lock); ret = hid_hw_raw_request(ldev->hdev, buf[0], buf, @@ -203,7 +198,7 @@ static int _thingm_write(struct led_classdev *cdev, enum led_brightness br, u8 offset) { struct hidled_led *led = to_hidled_led(cdev); - __u8 buf[MAX_REPORT_SIZE] = { [1] = 'c' }; + __u8 buf[MAX_REPORT_SIZE] = { 1, 'c' }; buf[2] = led->rgb->red.cdev.brightness; buf[3] = led->rgb->green.cdev.brightness; @@ -230,13 +225,12 @@ static const struct hidled_config hidled_config_thingm_v1 = { .num_leds = 1, .report_size = 9, .report_type = RAW_REQUEST, - .report_id = 1, .write = thingm_write_v1, }; static int thingm_init(struct hidled_device *ldev) { - __u8 buf[MAX_REPORT_SIZE] = { [1] = 'v' }; + __u8 buf[MAX_REPORT_SIZE] = { 1, 'v' }; int ret; ret = hidled_recv(ldev, buf); @@ -259,7 +253,6 @@ static const struct hidled_config hidled_configs[] = { .num_leds = 1, .report_size = 6, .report_type = OUTPUT_REPORT, - .report_id = 0, .write = riso_kagaku_write, }, { @@ -270,7 +263,6 @@ static const struct hidled_config hidled_configs[] = { .num_leds = 1, .report_size = 9, .report_type = RAW_REQUEST, - .report_id = 0, .init = dream_cheeky_init, .write = dream_cheeky_write, }, @@ -282,7 +274,6 @@ static const struct hidled_config hidled_configs[] = { .num_leds = 2, .report_size = 9, .report_type = RAW_REQUEST, - .report_id = 1, .init = thingm_init, .write = thingm_write, }, From de9086509e716e9797f401dd260d5b4bdd2244c7 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Mon, 4 Jul 2016 21:45:54 +0200 Subject: [PATCH 09/11] HID: hid-led: add support for Delcom Visual Signal Indicator G2 Add support for the HID-compliant Delcom Visual Signal Indicator generation 2 devices. Successfully tested with part no 904000 from the family of these devices. Signed-off-by: Heiner Kallweit Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 1 + drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 3 ++ drivers/hid/hid-led.c | 98 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 8b1eb47930bc..e33ccefe3e54 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -397,6 +397,7 @@ config HID_LED - Riso Kagaku Webmail Notifier - Dream Cheeky Webmail Notifier and Friends Alert - ThingM blink(1) + - Delcom Visual Signal Indicator Generation 2 To compile this driver as a module, choose M here: the module will be called hid-led. diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index eb674ce75fab..f808ae71f9a1 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1877,6 +1877,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_4) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_DELCOM, USB_DEVICE_ID_DELCOM_VISUAL_IND) }, { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006) }, { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011) }, { HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, USB_DEVICE_ID_DREAM_CHEEKY_WN) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index e104abae0dad..ffefbd09fb4c 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -296,6 +296,9 @@ #define USB_VENDOR_ID_DEALEXTREAME 0x10c5 #define USB_DEVICE_ID_DEALEXTREAME_RADIO_SI4701 0x819a +#define USB_VENDOR_ID_DELCOM 0x0fc5 +#define USB_DEVICE_ID_DELCOM_VISUAL_IND 0xb080 + #define USB_VENDOR_ID_DELORME 0x1163 #define USB_DEVICE_ID_DELORME_EARTHMATE 0x0100 #define USB_DEVICE_ID_DELORME_EM_LT20 0x0200 diff --git a/drivers/hid/hid-led.c b/drivers/hid/hid-led.c index fce2a03cb647..d793ecefb8b6 100644 --- a/drivers/hid/hid-led.c +++ b/drivers/hid/hid-led.c @@ -27,6 +27,7 @@ enum hidled_type { RISO_KAGAKU, DREAM_CHEEKY, THINGM, + DELCOM, }; static unsigned const char riso_kagaku_tbl[] = { @@ -43,6 +44,28 @@ static unsigned const char riso_kagaku_tbl[] = { #define RISO_KAGAKU_IX(r, g, b) riso_kagaku_tbl[((r)?1:0)+((g)?2:0)+((b)?4:0)] +union delcom_packet { + __u8 data[8]; + struct { + __u8 major_cmd; + __u8 minor_cmd; + __u8 data_lsb; + __u8 data_msb; + } tx; + struct { + __u8 cmd; + } rx; + struct { + __le16 family_code; + __le16 security_code; + __u8 fw_version; + } fw; +}; + +#define DELCOM_GREEN_LED 0 +#define DELCOM_RED_LED 1 +#define DELCOM_BLUE_LED 2 + struct hidled_device; struct hidled_rgb; @@ -244,6 +267,68 @@ static int thingm_init(struct hidled_device *ldev) return 0; } +static inline int delcom_get_lednum(const struct hidled_led *led) +{ + if (led == &led->rgb->red) + return DELCOM_RED_LED; + else if (led == &led->rgb->green) + return DELCOM_GREEN_LED; + else + return DELCOM_BLUE_LED; +} + +static int delcom_enable_led(struct hidled_led *led) +{ + union delcom_packet dp = { .tx.major_cmd = 101, .tx.minor_cmd = 12 }; + + dp.tx.data_lsb = 1 << delcom_get_lednum(led); + dp.tx.data_msb = 0; + + return hidled_send(led->rgb->ldev, dp.data); +} + +static int delcom_set_pwm(struct hidled_led *led) +{ + union delcom_packet dp = { .tx.major_cmd = 101, .tx.minor_cmd = 34 }; + + dp.tx.data_lsb = delcom_get_lednum(led); + dp.tx.data_msb = led->cdev.brightness; + + return hidled_send(led->rgb->ldev, dp.data); +} + +static int delcom_write(struct led_classdev *cdev, enum led_brightness br) +{ + struct hidled_led *led = to_hidled_led(cdev); + int ret; + + /* + * enable LED + * We can't do this in the init function already because the device + * is internally reset later. + */ + ret = delcom_enable_led(led); + if (ret) + return ret; + + return delcom_set_pwm(led); +} + +static int delcom_init(struct hidled_device *ldev) +{ + union delcom_packet dp = { .rx.cmd = 104 }; + int ret; + + ret = hidled_recv(ldev, dp.data); + if (ret) + return ret; + /* + * Several Delcom devices share the same USB VID/PID + * Check for family id 2 for Visual Signal Indicator + */ + return dp.fw.family_code == 2 ? 0 : -ENODEV; +} + static const struct hidled_config hidled_configs[] = { { .type = RISO_KAGAKU, @@ -277,6 +362,17 @@ static const struct hidled_config hidled_configs[] = { .init = thingm_init, .write = thingm_write, }, + { + .type = DELCOM, + .name = "Delcom Visual Signal Indicator G2", + .short_name = "delcom", + .max_brightness = 100, + .num_leds = 1, + .report_size = 8, + .report_type = RAW_REQUEST, + .init = delcom_init, + .write = delcom_write, + }, }; static int hidled_init_led(struct hidled_led *led, const char *color_name, @@ -382,6 +478,8 @@ static const struct hid_device_id hidled_table[] = { USB_DEVICE_ID_DREAM_CHEEKY_FA), .driver_data = DREAM_CHEEKY }, { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1), .driver_data = THINGM }, + { HID_USB_DEVICE(USB_VENDOR_ID_DELCOM, + USB_DEVICE_ID_DELCOM_VISUAL_IND), .driver_data = DELCOM }, { } }; MODULE_DEVICE_TABLE(hid, hidled_table); From 9d1e048cc8e1f9317b0bff611021aaf52e65f9d4 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Mon, 4 Jul 2016 21:47:52 +0200 Subject: [PATCH 10/11] HID: hid-led: add support for Greynut Luxafor Add support for Greynut Luxafor. This device has two groups of three independent LED's each. Successfully tested with such a device. Signed-off-by: Heiner Kallweit Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 1 + drivers/hid/hid-core.c | 1 + drivers/hid/hid-ids.h | 1 + drivers/hid/hid-led.c | 26 ++++++++++++++++++++++++++ 4 files changed, 29 insertions(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index e33ccefe3e54..8b4b38fb9e7e 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -398,6 +398,7 @@ config HID_LED - Dream Cheeky Webmail Notifier and Friends Alert - ThingM blink(1) - Delcom Visual Signal Indicator Generation 2 + - Greynut Luxafor To compile this driver as a module, choose M here: the module will be called hid-led. diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index f808ae71f9a1..a034f14f6d6f 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1965,6 +1965,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_LUXAFOR) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_MOUSE_4500) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_KEYBOARD) }, { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index ffefbd09fb4c..dafd2a37da1f 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -685,6 +685,7 @@ #define USB_DEVICE_ID_PICOLCD_BOOTLOADER 0xf002 #define USB_DEVICE_ID_PICK16F1454 0x0042 #define USB_DEVICE_ID_PICK16F1454_V2 0xf2f7 +#define USB_DEVICE_ID_LUXAFOR 0xf372 #define USB_VENDOR_ID_MICROSOFT 0x045e #define USB_DEVICE_ID_SIDEWINDER_GV 0x003b diff --git a/drivers/hid/hid-led.c b/drivers/hid/hid-led.c index d793ecefb8b6..4c43ef8e71f8 100644 --- a/drivers/hid/hid-led.c +++ b/drivers/hid/hid-led.c @@ -28,6 +28,7 @@ enum hidled_type { DREAM_CHEEKY, THINGM, DELCOM, + LUXAFOR, }; static unsigned const char riso_kagaku_tbl[] = { @@ -329,6 +330,19 @@ static int delcom_init(struct hidled_device *ldev) return dp.fw.family_code == 2 ? 0 : -ENODEV; } +static int luxafor_write(struct led_classdev *cdev, enum led_brightness br) +{ + struct hidled_led *led = to_hidled_led(cdev); + __u8 buf[MAX_REPORT_SIZE] = { [1] = 1 }; + + buf[2] = led->rgb->num + 1; + buf[3] = led->rgb->red.cdev.brightness; + buf[4] = led->rgb->green.cdev.brightness; + buf[5] = led->rgb->blue.cdev.brightness; + + return hidled_send(led->rgb->ldev, buf); +} + static const struct hidled_config hidled_configs[] = { { .type = RISO_KAGAKU, @@ -373,6 +387,16 @@ static const struct hidled_config hidled_configs[] = { .init = delcom_init, .write = delcom_write, }, + { + .type = LUXAFOR, + .name = "Greynut Luxafor", + .short_name = "luxafor", + .max_brightness = 255, + .num_leds = 6, + .report_size = 9, + .report_type = OUTPUT_REPORT, + .write = luxafor_write, + }, }; static int hidled_init_led(struct hidled_led *led, const char *color_name, @@ -480,6 +504,8 @@ static const struct hid_device_id hidled_table[] = { USB_DEVICE_ID_BLINK1), .driver_data = THINGM }, { HID_USB_DEVICE(USB_VENDOR_ID_DELCOM, USB_DEVICE_ID_DELCOM_VISUAL_IND), .driver_data = DELCOM }, + { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, + USB_DEVICE_ID_LUXAFOR), .driver_data = LUXAFOR }, { } }; MODULE_DEVICE_TABLE(hid, hidled_table); From f4c109b660fa1d0453a6dde75bef725304687832 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Fri, 8 Jul 2016 06:58:37 +0200 Subject: [PATCH 11/11] HID: hid-led: fix Delcom support on big endian systems Properly handle this __le16 value on big endian systems. Signed-off-by: Heiner Kallweit Signed-off-by: Jiri Kosina --- drivers/hid/hid-led.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-led.c b/drivers/hid/hid-led.c index 4c43ef8e71f8..d8d55f37b4f5 100644 --- a/drivers/hid/hid-led.c +++ b/drivers/hid/hid-led.c @@ -327,7 +327,7 @@ static int delcom_init(struct hidled_device *ldev) * Several Delcom devices share the same USB VID/PID * Check for family id 2 for Visual Signal Indicator */ - return dp.fw.family_code == 2 ? 0 : -ENODEV; + return le16_to_cpu(dp.fw.family_code) == 2 ? 0 : -ENODEV; } static int luxafor_write(struct led_classdev *cdev, enum led_brightness br)