From 75dbb9530f73da521b871ba3c4bd32f301a62635 Mon Sep 17 00:00:00 2001 From: Simon Wood Date: Thu, 31 Jan 2013 08:07:07 -0700 Subject: USB: HID: SRW-S1 Gaming Wheel Driver Add support the SRW-S1 by patching HID descriptor to read axis as Generic Desktop X, Y and Z (rather than Usage page being 'Simulation'). Signed-off-by: Simon Wood Tested-by: John Murphy Signed-off-by: Jiri Kosina --- drivers/hid/Makefile | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/hid/Makefile') diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index b62215716b2f..bbebe0a49798 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -101,6 +101,7 @@ obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o obj-$(CONFIG_HID_SONY) += hid-sony.o obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o +obj-$(CONFIG_HID_STEELSERIES_SRWS1) += hid-steelseries-srws1.o obj-$(CONFIG_HID_SUNPLUS) += hid-sunplus.o obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o -- cgit v1.2.3 From 090800c2a3f746572ef142e2d5404922a599e841 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 31 Jan 2013 16:50:24 +0100 Subject: HID: steelseries: rename driver to be compliant with other drivers We usually group drivers on a per-vendor basis, implementing device-specific deviations from the standard in vendor-specific driver. Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 2 +- drivers/hid/Makefile | 2 +- drivers/hid/hid-steelseries-srws1.c | 392 ------------------------------------ drivers/hid/hid-steelseries.c | 392 ++++++++++++++++++++++++++++++++++++ 4 files changed, 394 insertions(+), 394 deletions(-) delete mode 100644 drivers/hid/hid-steelseries-srws1.c create mode 100644 drivers/hid/hid-steelseries.c (limited to 'drivers/hid/Makefile') diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 37df92ffba50..6d5d2667750c 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -596,7 +596,7 @@ config HID_SPEEDLINK ---help--- Support for Speedlink Vicious and Divine Cezanne mouse. -config HID_STEELSERIES_SRWS1 +config HID_STEELSERIES tristate "Steelseries SRW-S1 steering wheel support" depends on USB_HID ---help--- diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index bbebe0a49798..247f2b81677a 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -101,7 +101,7 @@ obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o obj-$(CONFIG_HID_SONY) += hid-sony.o obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o -obj-$(CONFIG_HID_STEELSERIES_SRWS1) += hid-steelseries-srws1.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_THRUSTMASTER) += hid-tmff.o diff --git a/drivers/hid/hid-steelseries-srws1.c b/drivers/hid/hid-steelseries-srws1.c deleted file mode 100644 index 365bc9ef1e79..000000000000 --- a/drivers/hid/hid-steelseries-srws1.c +++ /dev/null @@ -1,392 +0,0 @@ -/* - * HID driver for Steelseries SRW-S1 - * - * Copyright (c) 2013 Simon Wood - */ - -/* - * 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; either version 2 of the License, or (at your option) - * any later version. - */ - -#include -#include -#include -#include - -#include "usbhid/usbhid.h" -#include "hid-ids.h" - -#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) -#define SRWS1_NUMBER_LEDS 15 -struct steelseries_srws1_data { - __u16 led_state; - struct led_classdev *led[SRWS1_NUMBER_LEDS]; -}; -#endif - -/* Fixed report descriptor for Steelseries SRW-S1 wheel controller - * - * The original descriptor hides the sensitivity and assists dials - * a custom vendor usage page. This inserts a patch to make them - * appear in the 'Generic Desktop' usage. - */ - -static __u8 steelseries_srws1_rdesc_fixed[] = { -0x05, 0x01, /* Usage Page (Desktop) */ -0x09, 0x08, /* Usage (MultiAxis), Changed */ -0xA1, 0x01, /* Collection (Application), */ -0xA1, 0x02, /* Collection (Logical), */ -0x95, 0x01, /* Report Count (1), */ -0x05, 0x01, /* Changed Usage Page (Desktop), */ -0x09, 0x30, /* Changed Usage (X), */ -0x16, 0xF8, 0xF8, /* Logical Minimum (-1800), */ -0x26, 0x08, 0x07, /* Logical Maximum (1800), */ -0x65, 0x14, /* Unit (Degrees), */ -0x55, 0x0F, /* Unit Exponent (15), */ -0x75, 0x10, /* Report Size (16), */ -0x81, 0x02, /* Input (Variable), */ -0x09, 0x31, /* Changed Usage (Y), */ -0x15, 0x00, /* Logical Minimum (0), */ -0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ -0x75, 0x0C, /* Report Size (12), */ -0x81, 0x02, /* Input (Variable), */ -0x09, 0x32, /* Changed Usage (Z), */ -0x15, 0x00, /* Logical Minimum (0), */ -0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ -0x75, 0x0C, /* Report Size (12), */ -0x81, 0x02, /* Input (Variable), */ -0x05, 0x01, /* Usage Page (Desktop), */ -0x09, 0x39, /* Usage (Hat Switch), */ -0x25, 0x07, /* Logical Maximum (7), */ -0x35, 0x00, /* Physical Minimum (0), */ -0x46, 0x3B, 0x01, /* Physical Maximum (315), */ -0x65, 0x14, /* Unit (Degrees), */ -0x75, 0x04, /* Report Size (4), */ -0x95, 0x01, /* Report Count (1), */ -0x81, 0x02, /* Input (Variable), */ -0x25, 0x01, /* Logical Maximum (1), */ -0x45, 0x01, /* Physical Maximum (1), */ -0x65, 0x00, /* Unit, */ -0x75, 0x01, /* Report Size (1), */ -0x95, 0x03, /* Report Count (3), */ -0x81, 0x01, /* Input (Constant), */ -0x05, 0x09, /* Usage Page (Button), */ -0x19, 0x01, /* Usage Minimum (01h), */ -0x29, 0x11, /* Usage Maximum (11h), */ -0x95, 0x11, /* Report Count (17), */ -0x81, 0x02, /* Input (Variable), */ - /* ---- Dial patch starts here ---- */ -0x05, 0x01, /* Usage Page (Desktop), */ -0x09, 0x33, /* Usage (RX), */ -0x75, 0x04, /* Report Size (4), */ -0x95, 0x02, /* Report Count (2), */ -0x15, 0x00, /* Logical Minimum (0), */ -0x25, 0x0b, /* Logical Maximum (b), */ -0x81, 0x02, /* Input (Variable), */ -0x09, 0x35, /* Usage (RZ), */ -0x75, 0x04, /* Report Size (4), */ -0x95, 0x01, /* Report Count (1), */ -0x25, 0x03, /* Logical Maximum (3), */ -0x81, 0x02, /* Input (Variable), */ - /* ---- Dial patch ends here ---- */ -0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ -0x09, 0x01, /* Usage (01h), */ -0x75, 0x04, /* Changed Report Size (4), */ -0x95, 0x0D, /* Changed Report Count (13), */ -0x81, 0x02, /* Input (Variable), */ -0xC0, /* End Collection, */ -0xA1, 0x02, /* Collection (Logical), */ -0x09, 0x02, /* Usage (02h), */ -0x75, 0x08, /* Report Size (8), */ -0x95, 0x10, /* Report Count (16), */ -0x91, 0x02, /* Output (Variable), */ -0xC0, /* End Collection, */ -0xC0 /* End Collection */ -}; - -#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) -static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds) -{ - struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list; - struct hid_report *report = list_entry(report_list->next, struct hid_report, list); - __s32 *value = report->field[0]->value; - - value[0] = 0x40; - value[1] = leds & 0xFF; - value[2] = leds >> 8; - value[3] = 0x00; - value[4] = 0x00; - value[5] = 0x00; - value[6] = 0x00; - value[7] = 0x00; - value[8] = 0x00; - value[9] = 0x00; - value[10] = 0x00; - value[11] = 0x00; - value[12] = 0x00; - value[13] = 0x00; - value[14] = 0x00; - value[15] = 0x00; - - usbhid_submit_report(hdev, report, USB_DIR_OUT); - - /* 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) -{ - 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); - int i, state = 0; - - if (!drv_data) { - hid_err(hid, "Device data not found."); - return; - } - - for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { - if (led_cdev != drv_data->led[i]) - continue; - - state = (drv_data->led_state >> i) & 1; - if (value == LED_OFF && state) { - drv_data->led_state &= ~(1 << i); - steelseries_srws1_set_leds(hid, drv_data->led_state); - } else if (value != LED_OFF && !state) { - drv_data->led_state |= 1 << i; - steelseries_srws1_set_leds(hid, drv_data->led_state); - } - break; - } -} - -static enum led_brightness steelseries_srws1_led_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; - int i, value = 0; - - drv_data = hid_get_drvdata(hid); - - if (!drv_data) { - hid_err(hid, "Device data not found."); - return LED_OFF; - } - - for (i = 0; i < SRWS1_NUMBER_LEDS; i++) - if (led_cdev == drv_data->led[i]) { - value = (drv_data->led_state >> i) & 1; - break; - } - - return value ? LED_FULL : LED_OFF; -} - -static int steelseries_srws1_probe(struct hid_device *hdev, - const struct hid_device_id *id) -{ - int ret, i; - struct led_classdev *led; - size_t name_sz; - char *name; - - struct steelseries_srws1_data *drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL); - - if (drv_data == NULL) { - hid_err(hdev, "can't alloc SRW-S1 memory\n"); - return -ENOMEM; - } - - hid_set_drvdata(hdev, drv_data); - - ret = hid_parse(hdev); - if (ret) { - hid_err(hdev, "parse failed\n"); - goto err_free; - } - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); - if (ret) { - hid_err(hdev, "hw start failed\n"); - goto err_free; - } - - /* register led subsystem */ - drv_data->led_state = 0; - 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) + 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) { - hid_err(hdev, "can't allocate memory for LED %d\n", i); - goto err_led; - } - - name = (void *)(&led[1]); - snprintf(name, name_sz, "SRWS1::%s::RPM%d", hdev->uniq, i+1); - led->name = name; - led->brightness = 0; - led->max_brightness = 1; - led->brightness_get = steelseries_srws1_led_get_brightness; - led->brightness_set = steelseries_srws1_led_set_brightness; - - drv_data->led[i] = led; - ret = led_classdev_register(&hdev->dev, led); - - if (ret) { - 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 + 1; i++) { - led = drv_data->led[i]; - drv_data->led[i] = NULL; - if (!led) - continue; - led_classdev_unregister(led); - kfree(led); - } - goto out; /* but let the driver continue without LEDs */ - } - } -out: - return 0; -err_free: - kfree(drv_data); - return ret; -} - -static void steelseries_srws1_remove(struct hid_device *hdev) -{ - int i; - struct led_classdev *led; - - struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev); - - if (drv_data) { - /* Deregister LEDs (if any) */ - for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { - led = drv_data->led[i]; - drv_data->led[i] = NULL; - if (!led) - continue; - led_classdev_unregister(led); - kfree(led); - } - - } - - hid_hw_stop(hdev); - kfree(drv_data); - return; -} -#endif - -static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc, - unsigned int *rsize) -{ - if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8 - && rdesc[29] == 0xbb && rdesc[40] == 0xc5) { - hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n"); - rdesc = steelseries_srws1_rdesc_fixed; - *rsize = sizeof(steelseries_srws1_rdesc_fixed); - } - return rdesc; -} - -static const struct hid_device_id steelseries_srws1_devices[] = { - { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, - { } -}; -MODULE_DEVICE_TABLE(hid, steelseries_srws1_devices); - -static struct hid_driver steelseries_srws1_driver = { - .name = "steelseries_srws1", - .id_table = steelseries_srws1_devices, -#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) - .probe = steelseries_srws1_probe, - .remove = steelseries_srws1_remove, -#endif - .report_fixup = steelseries_srws1_report_fixup -}; - -static int __init steelseries_srws1_init(void) -{ - return hid_register_driver(&steelseries_srws1_driver); -} - -static void __exit steelseries_srws1_exit(void) -{ - hid_unregister_driver(&steelseries_srws1_driver); -} - -module_init(steelseries_srws1_init); -module_exit(steelseries_srws1_exit); -MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c new file mode 100644 index 000000000000..365bc9ef1e79 --- /dev/null +++ b/drivers/hid/hid-steelseries.c @@ -0,0 +1,392 @@ +/* + * HID driver for Steelseries SRW-S1 + * + * Copyright (c) 2013 Simon Wood + */ + +/* + * 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; either version 2 of the License, or (at your option) + * any later version. + */ + +#include +#include +#include +#include + +#include "usbhid/usbhid.h" +#include "hid-ids.h" + +#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) +#define SRWS1_NUMBER_LEDS 15 +struct steelseries_srws1_data { + __u16 led_state; + struct led_classdev *led[SRWS1_NUMBER_LEDS]; +}; +#endif + +/* Fixed report descriptor for Steelseries SRW-S1 wheel controller + * + * The original descriptor hides the sensitivity and assists dials + * a custom vendor usage page. This inserts a patch to make them + * appear in the 'Generic Desktop' usage. + */ + +static __u8 steelseries_srws1_rdesc_fixed[] = { +0x05, 0x01, /* Usage Page (Desktop) */ +0x09, 0x08, /* Usage (MultiAxis), Changed */ +0xA1, 0x01, /* Collection (Application), */ +0xA1, 0x02, /* Collection (Logical), */ +0x95, 0x01, /* Report Count (1), */ +0x05, 0x01, /* Changed Usage Page (Desktop), */ +0x09, 0x30, /* Changed Usage (X), */ +0x16, 0xF8, 0xF8, /* Logical Minimum (-1800), */ +0x26, 0x08, 0x07, /* Logical Maximum (1800), */ +0x65, 0x14, /* Unit (Degrees), */ +0x55, 0x0F, /* Unit Exponent (15), */ +0x75, 0x10, /* Report Size (16), */ +0x81, 0x02, /* Input (Variable), */ +0x09, 0x31, /* Changed Usage (Y), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ +0x75, 0x0C, /* Report Size (12), */ +0x81, 0x02, /* Input (Variable), */ +0x09, 0x32, /* Changed Usage (Z), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ +0x75, 0x0C, /* Report Size (12), */ +0x81, 0x02, /* Input (Variable), */ +0x05, 0x01, /* Usage Page (Desktop), */ +0x09, 0x39, /* Usage (Hat Switch), */ +0x25, 0x07, /* Logical Maximum (7), */ +0x35, 0x00, /* Physical Minimum (0), */ +0x46, 0x3B, 0x01, /* Physical Maximum (315), */ +0x65, 0x14, /* Unit (Degrees), */ +0x75, 0x04, /* Report Size (4), */ +0x95, 0x01, /* Report Count (1), */ +0x81, 0x02, /* Input (Variable), */ +0x25, 0x01, /* Logical Maximum (1), */ +0x45, 0x01, /* Physical Maximum (1), */ +0x65, 0x00, /* Unit, */ +0x75, 0x01, /* Report Size (1), */ +0x95, 0x03, /* Report Count (3), */ +0x81, 0x01, /* Input (Constant), */ +0x05, 0x09, /* Usage Page (Button), */ +0x19, 0x01, /* Usage Minimum (01h), */ +0x29, 0x11, /* Usage Maximum (11h), */ +0x95, 0x11, /* Report Count (17), */ +0x81, 0x02, /* Input (Variable), */ + /* ---- Dial patch starts here ---- */ +0x05, 0x01, /* Usage Page (Desktop), */ +0x09, 0x33, /* Usage (RX), */ +0x75, 0x04, /* Report Size (4), */ +0x95, 0x02, /* Report Count (2), */ +0x15, 0x00, /* Logical Minimum (0), */ +0x25, 0x0b, /* Logical Maximum (b), */ +0x81, 0x02, /* Input (Variable), */ +0x09, 0x35, /* Usage (RZ), */ +0x75, 0x04, /* Report Size (4), */ +0x95, 0x01, /* Report Count (1), */ +0x25, 0x03, /* Logical Maximum (3), */ +0x81, 0x02, /* Input (Variable), */ + /* ---- Dial patch ends here ---- */ +0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ +0x09, 0x01, /* Usage (01h), */ +0x75, 0x04, /* Changed Report Size (4), */ +0x95, 0x0D, /* Changed Report Count (13), */ +0x81, 0x02, /* Input (Variable), */ +0xC0, /* End Collection, */ +0xA1, 0x02, /* Collection (Logical), */ +0x09, 0x02, /* Usage (02h), */ +0x75, 0x08, /* Report Size (8), */ +0x95, 0x10, /* Report Count (16), */ +0x91, 0x02, /* Output (Variable), */ +0xC0, /* End Collection, */ +0xC0 /* End Collection */ +}; + +#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) +static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds) +{ + struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list; + struct hid_report *report = list_entry(report_list->next, struct hid_report, list); + __s32 *value = report->field[0]->value; + + value[0] = 0x40; + value[1] = leds & 0xFF; + value[2] = leds >> 8; + value[3] = 0x00; + value[4] = 0x00; + value[5] = 0x00; + value[6] = 0x00; + value[7] = 0x00; + value[8] = 0x00; + value[9] = 0x00; + value[10] = 0x00; + value[11] = 0x00; + value[12] = 0x00; + value[13] = 0x00; + value[14] = 0x00; + value[15] = 0x00; + + usbhid_submit_report(hdev, report, USB_DIR_OUT); + + /* 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) +{ + 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); + int i, state = 0; + + if (!drv_data) { + hid_err(hid, "Device data not found."); + return; + } + + for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { + if (led_cdev != drv_data->led[i]) + continue; + + state = (drv_data->led_state >> i) & 1; + if (value == LED_OFF && state) { + drv_data->led_state &= ~(1 << i); + steelseries_srws1_set_leds(hid, drv_data->led_state); + } else if (value != LED_OFF && !state) { + drv_data->led_state |= 1 << i; + steelseries_srws1_set_leds(hid, drv_data->led_state); + } + break; + } +} + +static enum led_brightness steelseries_srws1_led_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; + int i, value = 0; + + drv_data = hid_get_drvdata(hid); + + if (!drv_data) { + hid_err(hid, "Device data not found."); + return LED_OFF; + } + + for (i = 0; i < SRWS1_NUMBER_LEDS; i++) + if (led_cdev == drv_data->led[i]) { + value = (drv_data->led_state >> i) & 1; + break; + } + + return value ? LED_FULL : LED_OFF; +} + +static int steelseries_srws1_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret, i; + struct led_classdev *led; + size_t name_sz; + char *name; + + struct steelseries_srws1_data *drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL); + + if (drv_data == NULL) { + hid_err(hdev, "can't alloc SRW-S1 memory\n"); + return -ENOMEM; + } + + hid_set_drvdata(hdev, drv_data); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + goto err_free; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "hw start failed\n"); + goto err_free; + } + + /* register led subsystem */ + drv_data->led_state = 0; + 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) + 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) { + hid_err(hdev, "can't allocate memory for LED %d\n", i); + goto err_led; + } + + name = (void *)(&led[1]); + snprintf(name, name_sz, "SRWS1::%s::RPM%d", hdev->uniq, i+1); + led->name = name; + led->brightness = 0; + led->max_brightness = 1; + led->brightness_get = steelseries_srws1_led_get_brightness; + led->brightness_set = steelseries_srws1_led_set_brightness; + + drv_data->led[i] = led; + ret = led_classdev_register(&hdev->dev, led); + + if (ret) { + 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 + 1; i++) { + led = drv_data->led[i]; + drv_data->led[i] = NULL; + if (!led) + continue; + led_classdev_unregister(led); + kfree(led); + } + goto out; /* but let the driver continue without LEDs */ + } + } +out: + return 0; +err_free: + kfree(drv_data); + return ret; +} + +static void steelseries_srws1_remove(struct hid_device *hdev) +{ + int i; + struct led_classdev *led; + + struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev); + + if (drv_data) { + /* Deregister LEDs (if any) */ + for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { + led = drv_data->led[i]; + drv_data->led[i] = NULL; + if (!led) + continue; + led_classdev_unregister(led); + kfree(led); + } + + } + + hid_hw_stop(hdev); + kfree(drv_data); + return; +} +#endif + +static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8 + && rdesc[29] == 0xbb && rdesc[40] == 0xc5) { + hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n"); + rdesc = steelseries_srws1_rdesc_fixed; + *rsize = sizeof(steelseries_srws1_rdesc_fixed); + } + return rdesc; +} + +static const struct hid_device_id steelseries_srws1_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, + { } +}; +MODULE_DEVICE_TABLE(hid, steelseries_srws1_devices); + +static struct hid_driver steelseries_srws1_driver = { + .name = "steelseries_srws1", + .id_table = steelseries_srws1_devices, +#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) + .probe = steelseries_srws1_probe, + .remove = steelseries_srws1_remove, +#endif + .report_fixup = steelseries_srws1_report_fixup +}; + +static int __init steelseries_srws1_init(void) +{ + return hid_register_driver(&steelseries_srws1_driver); +} + +static void __exit steelseries_srws1_exit(void) +{ + hid_unregister_driver(&steelseries_srws1_driver); +} + +module_init(steelseries_srws1_init); +module_exit(steelseries_srws1_exit); +MODULE_LICENSE("GPL"); -- cgit v1.2.3