diff options
| -rw-r--r-- | Documentation/ABI/testing/sysfs-driver-hid-srws1 | 21 | ||||
| -rw-r--r-- | drivers/hid/Kconfig | 6 | ||||
| -rw-r--r-- | drivers/hid/Makefile | 1 | ||||
| -rw-r--r-- | drivers/hid/hid-core.c | 1 | ||||
| -rw-r--r-- | drivers/hid/hid-ids.h | 4 | ||||
| -rw-r--r-- | drivers/hid/hid-sony.c | 46 | ||||
| -rw-r--r-- | drivers/hid/hid-steelseries.c | 393 |
7 files changed, 469 insertions, 3 deletions
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-srws1 b/Documentation/ABI/testing/sysfs-driver-hid-srws1 new file mode 100644 index 000000000000..d0eba70c7d40 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-srws1 | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | What: /sys/class/leds/SRWS1::<serial>::RPM1 | ||
| 2 | What: /sys/class/leds/SRWS1::<serial>::RPM2 | ||
| 3 | What: /sys/class/leds/SRWS1::<serial>::RPM3 | ||
| 4 | What: /sys/class/leds/SRWS1::<serial>::RPM4 | ||
| 5 | What: /sys/class/leds/SRWS1::<serial>::RPM5 | ||
| 6 | What: /sys/class/leds/SRWS1::<serial>::RPM6 | ||
| 7 | What: /sys/class/leds/SRWS1::<serial>::RPM7 | ||
| 8 | What: /sys/class/leds/SRWS1::<serial>::RPM8 | ||
| 9 | What: /sys/class/leds/SRWS1::<serial>::RPM9 | ||
| 10 | What: /sys/class/leds/SRWS1::<serial>::RPM10 | ||
| 11 | What: /sys/class/leds/SRWS1::<serial>::RPM11 | ||
| 12 | What: /sys/class/leds/SRWS1::<serial>::RPM12 | ||
| 13 | What: /sys/class/leds/SRWS1::<serial>::RPM13 | ||
| 14 | What: /sys/class/leds/SRWS1::<serial>::RPM14 | ||
| 15 | What: /sys/class/leds/SRWS1::<serial>::RPM15 | ||
| 16 | What: /sys/class/leds/SRWS1::<serial>::RPMALL | ||
| 17 | Date: Jan 2013 | ||
| 18 | KernelVersion: 3.9 | ||
| 19 | Contact: Simon Wood <simon@mungewell.org> | ||
| 20 | Description: Provides a control for turning on/off the LEDs which form | ||
| 21 | an RPM meter on the front of the controller | ||
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 3d5294531ba5..f25666d72217 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig | |||
| @@ -596,6 +596,12 @@ config HID_SPEEDLINK | |||
| 596 | ---help--- | 596 | ---help--- |
| 597 | Support for Speedlink Vicious and Divine Cezanne mouse. | 597 | Support for Speedlink Vicious and Divine Cezanne mouse. |
| 598 | 598 | ||
| 599 | config HID_STEELSERIES | ||
| 600 | tristate "Steelseries SRW-S1 steering wheel support" | ||
| 601 | depends on USB_HID | ||
| 602 | ---help--- | ||
| 603 | Support for Steelseries SRW-S1 steering wheel | ||
| 604 | |||
| 599 | config HID_SUNPLUS | 605 | config HID_SUNPLUS |
| 600 | tristate "Sunplus wireless desktop" | 606 | tristate "Sunplus wireless desktop" |
| 601 | depends on USB_HID | 607 | depends on USB_HID |
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 93017043f6f8..72d1b0bc0a97 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile | |||
| @@ -101,6 +101,7 @@ obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o | |||
| 101 | obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o | 101 | obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o |
| 102 | obj-$(CONFIG_HID_SONY) += hid-sony.o | 102 | obj-$(CONFIG_HID_SONY) += hid-sony.o |
| 103 | obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o | 103 | obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o |
| 104 | obj-$(CONFIG_HID_STEELSERIES) += hid-steelseries.o | ||
| 104 | obj-$(CONFIG_HID_SUNPLUS) += hid-sunplus.o | 105 | obj-$(CONFIG_HID_SUNPLUS) += hid-sunplus.o |
| 105 | obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o | 106 | obj-$(CONFIG_HID_GREENASIA) += hid-gaff.o |
| 106 | obj-$(CONFIG_HID_THINGM) += hid-thingm.o | 107 | obj-$(CONFIG_HID_THINGM) += hid-thingm.o |
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 6e904155ded5..ff75cabf7393 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c | |||
| @@ -1702,6 +1702,7 @@ static const struct hid_device_id hid_have_special_driver[] = { | |||
| 1702 | { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) }, | 1702 | { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) }, |
| 1703 | { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, | 1703 | { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, |
| 1704 | { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) }, | 1704 | { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) }, |
| 1705 | { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, | ||
| 1705 | { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) }, | 1706 | { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) }, |
| 1706 | { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) }, | 1707 | { HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) }, |
| 1707 | { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300) }, | 1708 | { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300) }, |
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index d15ed561136c..6e5c2ffa8d96 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h | |||
| @@ -715,6 +715,7 @@ | |||
| 715 | 715 | ||
| 716 | #define USB_VENDOR_ID_SONY 0x054c | 716 | #define USB_VENDOR_ID_SONY 0x054c |
| 717 | #define USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE 0x024b | 717 | #define USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE 0x024b |
| 718 | #define USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE 0x0374 | ||
| 718 | #define USB_DEVICE_ID_SONY_PS3_BDREMOTE 0x0306 | 719 | #define USB_DEVICE_ID_SONY_PS3_BDREMOTE 0x0306 |
| 719 | #define USB_DEVICE_ID_SONY_PS3_CONTROLLER 0x0268 | 720 | #define USB_DEVICE_ID_SONY_PS3_CONTROLLER 0x0268 |
| 720 | #define USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER 0x042f | 721 | #define USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER 0x042f |
| @@ -732,6 +733,9 @@ | |||
| 732 | #define USB_VENDOR_ID_STANTUM_SITRONIX 0x1403 | 733 | #define USB_VENDOR_ID_STANTUM_SITRONIX 0x1403 |
| 733 | #define USB_DEVICE_ID_MTP_SITRONIX 0x5001 | 734 | #define USB_DEVICE_ID_MTP_SITRONIX 0x5001 |
| 734 | 735 | ||
| 736 | #define USB_VENDOR_ID_STEELSERIES 0x1038 | ||
| 737 | #define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410 | ||
| 738 | |||
| 735 | #define USB_VENDOR_ID_SUN 0x0430 | 739 | #define USB_VENDOR_ID_SUN 0x0430 |
| 736 | #define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab | 740 | #define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab |
| 737 | 741 | ||
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 23fc762e8633..312098e4af4f 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c | |||
| @@ -33,6 +33,28 @@ static const u8 sixaxis_rdesc_fixup[] = { | |||
| 33 | 0x03, 0x46, 0xFF, 0x03, 0x09, 0x01, 0x81, 0x02 | 33 | 0x03, 0x46, 0xFF, 0x03, 0x09, 0x01, 0x81, 0x02 |
| 34 | }; | 34 | }; |
| 35 | 35 | ||
| 36 | static const u8 sixaxis_rdesc_fixup2[] = { | ||
| 37 | 0x05, 0x01, 0x09, 0x04, 0xa1, 0x01, 0xa1, 0x02, | ||
| 38 | 0x85, 0x01, 0x75, 0x08, 0x95, 0x01, 0x15, 0x00, | ||
| 39 | 0x26, 0xff, 0x00, 0x81, 0x03, 0x75, 0x01, 0x95, | ||
| 40 | 0x13, 0x15, 0x00, 0x25, 0x01, 0x35, 0x00, 0x45, | ||
| 41 | 0x01, 0x05, 0x09, 0x19, 0x01, 0x29, 0x13, 0x81, | ||
| 42 | 0x02, 0x75, 0x01, 0x95, 0x0d, 0x06, 0x00, 0xff, | ||
| 43 | 0x81, 0x03, 0x15, 0x00, 0x26, 0xff, 0x00, 0x05, | ||
| 44 | 0x01, 0x09, 0x01, 0xa1, 0x00, 0x75, 0x08, 0x95, | ||
| 45 | 0x04, 0x35, 0x00, 0x46, 0xff, 0x00, 0x09, 0x30, | ||
| 46 | 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02, | ||
| 47 | 0xc0, 0x05, 0x01, 0x95, 0x13, 0x09, 0x01, 0x81, | ||
| 48 | 0x02, 0x95, 0x0c, 0x81, 0x01, 0x75, 0x10, 0x95, | ||
| 49 | 0x04, 0x26, 0xff, 0x03, 0x46, 0xff, 0x03, 0x09, | ||
| 50 | 0x01, 0x81, 0x02, 0xc0, 0xa1, 0x02, 0x85, 0x02, | ||
| 51 | 0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xb1, 0x02, | ||
| 52 | 0xc0, 0xa1, 0x02, 0x85, 0xee, 0x75, 0x08, 0x95, | ||
| 53 | 0x30, 0x09, 0x01, 0xb1, 0x02, 0xc0, 0xa1, 0x02, | ||
| 54 | 0x85, 0xef, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01, | ||
| 55 | 0xb1, 0x02, 0xc0, 0xc0, | ||
| 56 | }; | ||
| 57 | |||
| 36 | struct sony_sc { | 58 | struct sony_sc { |
| 37 | unsigned long quirks; | 59 | unsigned long quirks; |
| 38 | }; | 60 | }; |
| @@ -43,9 +65,19 @@ static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc, | |||
| 43 | { | 65 | { |
| 44 | struct sony_sc *sc = hid_get_drvdata(hdev); | 66 | struct sony_sc *sc = hid_get_drvdata(hdev); |
| 45 | 67 | ||
| 46 | if ((sc->quirks & VAIO_RDESC_CONSTANT) && | 68 | /* |
| 47 | *rsize >= 56 && rdesc[54] == 0x81 && rdesc[55] == 0x07) { | 69 | * Some Sony RF receivers wrongly declare the mouse pointer as a |
| 48 | hid_info(hdev, "Fixing up Sony Vaio VGX report descriptor\n"); | 70 | * a constant non-data variable. |
| 71 | */ | ||
| 72 | if ((sc->quirks & VAIO_RDESC_CONSTANT) && *rsize >= 56 && | ||
| 73 | /* usage page: generic desktop controls */ | ||
| 74 | /* rdesc[0] == 0x05 && rdesc[1] == 0x01 && */ | ||
| 75 | /* usage: mouse */ | ||
| 76 | rdesc[2] == 0x09 && rdesc[3] == 0x02 && | ||
| 77 | /* input (usage page for x,y axes): constant, variable, relative */ | ||
| 78 | rdesc[54] == 0x81 && rdesc[55] == 0x07) { | ||
| 79 | hid_info(hdev, "Fixing up Sony RF Receiver report descriptor\n"); | ||
| 80 | /* input: data, variable, relative */ | ||
| 49 | rdesc[55] = 0x06; | 81 | rdesc[55] = 0x06; |
| 50 | } | 82 | } |
| 51 | 83 | ||
| @@ -56,6 +88,12 @@ static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc, | |||
| 56 | hid_info(hdev, "Fixing up Sony Sixaxis report descriptor\n"); | 88 | hid_info(hdev, "Fixing up Sony Sixaxis report descriptor\n"); |
| 57 | memcpy((void *)&rdesc[83], (void *)&sixaxis_rdesc_fixup, | 89 | memcpy((void *)&rdesc[83], (void *)&sixaxis_rdesc_fixup, |
| 58 | sizeof(sixaxis_rdesc_fixup)); | 90 | sizeof(sixaxis_rdesc_fixup)); |
| 91 | } else if (sc->quirks & SIXAXIS_CONTROLLER_USB && | ||
| 92 | *rsize > sizeof(sixaxis_rdesc_fixup2)) { | ||
| 93 | hid_info(hdev, "Sony Sixaxis clone detected. Using original report descriptor (size: %d clone; %d new)\n", | ||
| 94 | *rsize, (int)sizeof(sixaxis_rdesc_fixup2)); | ||
| 95 | *rsize = sizeof(sixaxis_rdesc_fixup2); | ||
| 96 | memcpy(rdesc, &sixaxis_rdesc_fixup2, *rsize); | ||
| 59 | } | 97 | } |
| 60 | return rdesc; | 98 | return rdesc; |
| 61 | } | 99 | } |
| @@ -217,6 +255,8 @@ static const struct hid_device_id sony_devices[] = { | |||
| 217 | .driver_data = SIXAXIS_CONTROLLER_BT }, | 255 | .driver_data = SIXAXIS_CONTROLLER_BT }, |
| 218 | { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE), | 256 | { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE), |
| 219 | .driver_data = VAIO_RDESC_CONSTANT }, | 257 | .driver_data = VAIO_RDESC_CONSTANT }, |
| 258 | { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE), | ||
| 259 | .driver_data = VAIO_RDESC_CONSTANT }, | ||
| 220 | { } | 260 | { } |
| 221 | }; | 261 | }; |
| 222 | MODULE_DEVICE_TABLE(hid, sony_devices); | 262 | MODULE_DEVICE_TABLE(hid, sony_devices); |
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c new file mode 100644 index 000000000000..2ed995cda44a --- /dev/null +++ b/drivers/hid/hid-steelseries.c | |||
| @@ -0,0 +1,393 @@ | |||
| 1 | /* | ||
| 2 | * HID driver for Steelseries SRW-S1 | ||
| 3 | * | ||
| 4 | * Copyright (c) 2013 Simon Wood | ||
| 5 | */ | ||
| 6 | |||
| 7 | /* | ||
| 8 | * This program is free software; you can redistribute it and/or modify it | ||
| 9 | * under the terms of the GNU General Public License as published by the Free | ||
| 10 | * Software Foundation; either version 2 of the License, or (at your option) | ||
| 11 | * any later version. | ||
| 12 | */ | ||
| 13 | |||
| 14 | #include <linux/device.h> | ||
| 15 | #include <linux/usb.h> | ||
| 16 | #include <linux/hid.h> | ||
| 17 | #include <linux/module.h> | ||
| 18 | |||
| 19 | #include "usbhid/usbhid.h" | ||
| 20 | #include "hid-ids.h" | ||
| 21 | |||
| 22 | #if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) | ||
| 23 | #define SRWS1_NUMBER_LEDS 15 | ||
| 24 | struct steelseries_srws1_data { | ||
| 25 | __u16 led_state; | ||
| 26 | /* the last element is used for setting all leds simultaneously */ | ||
| 27 | struct led_classdev *led[SRWS1_NUMBER_LEDS + 1]; | ||
| 28 | }; | ||
| 29 | #endif | ||
| 30 | |||
| 31 | /* Fixed report descriptor for Steelseries SRW-S1 wheel controller | ||
| 32 | * | ||
| 33 | * The original descriptor hides the sensitivity and assists dials | ||
| 34 | * a custom vendor usage page. This inserts a patch to make them | ||
| 35 | * appear in the 'Generic Desktop' usage. | ||
| 36 | */ | ||
| 37 | |||
| 38 | static __u8 steelseries_srws1_rdesc_fixed[] = { | ||
| 39 | 0x05, 0x01, /* Usage Page (Desktop) */ | ||
| 40 | 0x09, 0x08, /* Usage (MultiAxis), Changed */ | ||
| 41 | 0xA1, 0x01, /* Collection (Application), */ | ||
| 42 | 0xA1, 0x02, /* Collection (Logical), */ | ||
| 43 | 0x95, 0x01, /* Report Count (1), */ | ||
| 44 | 0x05, 0x01, /* Changed Usage Page (Desktop), */ | ||
| 45 | 0x09, 0x30, /* Changed Usage (X), */ | ||
| 46 | 0x16, 0xF8, 0xF8, /* Logical Minimum (-1800), */ | ||
| 47 | 0x26, 0x08, 0x07, /* Logical Maximum (1800), */ | ||
| 48 | 0x65, 0x14, /* Unit (Degrees), */ | ||
| 49 | 0x55, 0x0F, /* Unit Exponent (15), */ | ||
| 50 | 0x75, 0x10, /* Report Size (16), */ | ||
| 51 | 0x81, 0x02, /* Input (Variable), */ | ||
| 52 | 0x09, 0x31, /* Changed Usage (Y), */ | ||
| 53 | 0x15, 0x00, /* Logical Minimum (0), */ | ||
| 54 | 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ | ||
| 55 | 0x75, 0x0C, /* Report Size (12), */ | ||
| 56 | 0x81, 0x02, /* Input (Variable), */ | ||
| 57 | 0x09, 0x32, /* Changed Usage (Z), */ | ||
| 58 | 0x15, 0x00, /* Logical Minimum (0), */ | ||
| 59 | 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ | ||
| 60 | 0x75, 0x0C, /* Report Size (12), */ | ||
| 61 | 0x81, 0x02, /* Input (Variable), */ | ||
| 62 | 0x05, 0x01, /* Usage Page (Desktop), */ | ||
| 63 | 0x09, 0x39, /* Usage (Hat Switch), */ | ||
| 64 | 0x25, 0x07, /* Logical Maximum (7), */ | ||
| 65 | 0x35, 0x00, /* Physical Minimum (0), */ | ||
| 66 | 0x46, 0x3B, 0x01, /* Physical Maximum (315), */ | ||
| 67 | 0x65, 0x14, /* Unit (Degrees), */ | ||
| 68 | 0x75, 0x04, /* Report Size (4), */ | ||
| 69 | 0x95, 0x01, /* Report Count (1), */ | ||
| 70 | 0x81, 0x02, /* Input (Variable), */ | ||
| 71 | 0x25, 0x01, /* Logical Maximum (1), */ | ||
| 72 | 0x45, 0x01, /* Physical Maximum (1), */ | ||
| 73 | 0x65, 0x00, /* Unit, */ | ||
| 74 | 0x75, 0x01, /* Report Size (1), */ | ||
| 75 | 0x95, 0x03, /* Report Count (3), */ | ||
| 76 | 0x81, 0x01, /* Input (Constant), */ | ||
| 77 | 0x05, 0x09, /* Usage Page (Button), */ | ||
| 78 | 0x19, 0x01, /* Usage Minimum (01h), */ | ||
| 79 | 0x29, 0x11, /* Usage Maximum (11h), */ | ||
| 80 | 0x95, 0x11, /* Report Count (17), */ | ||
| 81 | 0x81, 0x02, /* Input (Variable), */ | ||
| 82 | /* ---- Dial patch starts here ---- */ | ||
| 83 | 0x05, 0x01, /* Usage Page (Desktop), */ | ||
| 84 | 0x09, 0x33, /* Usage (RX), */ | ||
| 85 | 0x75, 0x04, /* Report Size (4), */ | ||
| 86 | 0x95, 0x02, /* Report Count (2), */ | ||
| 87 | 0x15, 0x00, /* Logical Minimum (0), */ | ||
| 88 | 0x25, 0x0b, /* Logical Maximum (b), */ | ||
| 89 | 0x81, 0x02, /* Input (Variable), */ | ||
| 90 | 0x09, 0x35, /* Usage (RZ), */ | ||
| 91 | 0x75, 0x04, /* Report Size (4), */ | ||
| 92 | 0x95, 0x01, /* Report Count (1), */ | ||
| 93 | 0x25, 0x03, /* Logical Maximum (3), */ | ||
| 94 | 0x81, 0x02, /* Input (Variable), */ | ||
| 95 | /* ---- Dial patch ends here ---- */ | ||
| 96 | 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ | ||
| 97 | 0x09, 0x01, /* Usage (01h), */ | ||
| 98 | 0x75, 0x04, /* Changed Report Size (4), */ | ||
| 99 | 0x95, 0x0D, /* Changed Report Count (13), */ | ||
| 100 | 0x81, 0x02, /* Input (Variable), */ | ||
| 101 | 0xC0, /* End Collection, */ | ||
| 102 | 0xA1, 0x02, /* Collection (Logical), */ | ||
| 103 | 0x09, 0x02, /* Usage (02h), */ | ||
| 104 | 0x75, 0x08, /* Report Size (8), */ | ||
| 105 | 0x95, 0x10, /* Report Count (16), */ | ||
| 106 | 0x91, 0x02, /* Output (Variable), */ | ||
| 107 | 0xC0, /* End Collection, */ | ||
| 108 | 0xC0 /* End Collection */ | ||
| 109 | }; | ||
| 110 | |||
| 111 | #if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) | ||
| 112 | static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds) | ||
| 113 | { | ||
| 114 | struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list; | ||
| 115 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); | ||
| 116 | __s32 *value = report->field[0]->value; | ||
| 117 | |||
| 118 | value[0] = 0x40; | ||
| 119 | value[1] = leds & 0xFF; | ||
| 120 | value[2] = leds >> 8; | ||
| 121 | value[3] = 0x00; | ||
| 122 | value[4] = 0x00; | ||
| 123 | value[5] = 0x00; | ||
| 124 | value[6] = 0x00; | ||
| 125 | value[7] = 0x00; | ||
| 126 | value[8] = 0x00; | ||
| 127 | value[9] = 0x00; | ||
| 128 | value[10] = 0x00; | ||
| 129 | value[11] = 0x00; | ||
| 130 | value[12] = 0x00; | ||
| 131 | value[13] = 0x00; | ||
| 132 | value[14] = 0x00; | ||
| 133 | value[15] = 0x00; | ||
| 134 | |||
| 135 | usbhid_submit_report(hdev, report, USB_DIR_OUT); | ||
| 136 | |||
| 137 | /* Note: LED change does not show on device until the device is read/polled */ | ||
| 138 | } | ||
| 139 | |||
| 140 | static void steelseries_srws1_led_all_set_brightness(struct led_classdev *led_cdev, | ||
| 141 | enum led_brightness value) | ||
| 142 | { | ||
| 143 | struct device *dev = led_cdev->dev->parent; | ||
| 144 | struct hid_device *hid = container_of(dev, struct hid_device, dev); | ||
| 145 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid); | ||
| 146 | |||
| 147 | if (!drv_data) { | ||
| 148 | hid_err(hid, "Device data not found."); | ||
| 149 | return; | ||
| 150 | } | ||
| 151 | |||
| 152 | if (value == LED_OFF) | ||
| 153 | drv_data->led_state = 0; | ||
| 154 | else | ||
| 155 | drv_data->led_state = (1 << (SRWS1_NUMBER_LEDS + 1)) - 1; | ||
| 156 | |||
| 157 | steelseries_srws1_set_leds(hid, drv_data->led_state); | ||
| 158 | } | ||
| 159 | |||
| 160 | static enum led_brightness steelseries_srws1_led_all_get_brightness(struct led_classdev *led_cdev) | ||
| 161 | { | ||
| 162 | struct device *dev = led_cdev->dev->parent; | ||
| 163 | struct hid_device *hid = container_of(dev, struct hid_device, dev); | ||
| 164 | struct steelseries_srws1_data *drv_data; | ||
| 165 | |||
| 166 | drv_data = hid_get_drvdata(hid); | ||
| 167 | |||
| 168 | if (!drv_data) { | ||
| 169 | hid_err(hid, "Device data not found."); | ||
| 170 | return LED_OFF; | ||
| 171 | } | ||
| 172 | |||
| 173 | return (drv_data->led_state >> SRWS1_NUMBER_LEDS) ? LED_FULL : LED_OFF; | ||
| 174 | } | ||
| 175 | |||
| 176 | static void steelseries_srws1_led_set_brightness(struct led_classdev *led_cdev, | ||
| 177 | enum led_brightness value) | ||
| 178 | { | ||
| 179 | struct device *dev = led_cdev->dev->parent; | ||
| 180 | struct hid_device *hid = container_of(dev, struct hid_device, dev); | ||
| 181 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid); | ||
| 182 | int i, state = 0; | ||
| 183 | |||
| 184 | if (!drv_data) { | ||
| 185 | hid_err(hid, "Device data not found."); | ||
| 186 | return; | ||
| 187 | } | ||
| 188 | |||
| 189 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { | ||
| 190 | if (led_cdev != drv_data->led[i]) | ||
| 191 | continue; | ||
| 192 | |||
| 193 | state = (drv_data->led_state >> i) & 1; | ||
| 194 | if (value == LED_OFF && state) { | ||
| 195 | drv_data->led_state &= ~(1 << i); | ||
| 196 | steelseries_srws1_set_leds(hid, drv_data->led_state); | ||
| 197 | } else if (value != LED_OFF && !state) { | ||
| 198 | drv_data->led_state |= 1 << i; | ||
| 199 | steelseries_srws1_set_leds(hid, drv_data->led_state); | ||
| 200 | } | ||
| 201 | break; | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 205 | static enum led_brightness steelseries_srws1_led_get_brightness(struct led_classdev *led_cdev) | ||
| 206 | { | ||
| 207 | struct device *dev = led_cdev->dev->parent; | ||
| 208 | struct hid_device *hid = container_of(dev, struct hid_device, dev); | ||
| 209 | struct steelseries_srws1_data *drv_data; | ||
| 210 | int i, value = 0; | ||
| 211 | |||
| 212 | drv_data = hid_get_drvdata(hid); | ||
| 213 | |||
| 214 | if (!drv_data) { | ||
| 215 | hid_err(hid, "Device data not found."); | ||
| 216 | return LED_OFF; | ||
| 217 | } | ||
| 218 | |||
| 219 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) | ||
| 220 | if (led_cdev == drv_data->led[i]) { | ||
| 221 | value = (drv_data->led_state >> i) & 1; | ||
| 222 | break; | ||
| 223 | } | ||
| 224 | |||
| 225 | return value ? LED_FULL : LED_OFF; | ||
| 226 | } | ||
| 227 | |||
| 228 | static int steelseries_srws1_probe(struct hid_device *hdev, | ||
| 229 | const struct hid_device_id *id) | ||
| 230 | { | ||
| 231 | int ret, i; | ||
| 232 | struct led_classdev *led; | ||
| 233 | size_t name_sz; | ||
| 234 | char *name; | ||
| 235 | |||
| 236 | struct steelseries_srws1_data *drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL); | ||
| 237 | |||
| 238 | if (drv_data == NULL) { | ||
| 239 | hid_err(hdev, "can't alloc SRW-S1 memory\n"); | ||
| 240 | return -ENOMEM; | ||
| 241 | } | ||
| 242 | |||
| 243 | hid_set_drvdata(hdev, drv_data); | ||
| 244 | |||
| 245 | ret = hid_parse(hdev); | ||
| 246 | if (ret) { | ||
| 247 | hid_err(hdev, "parse failed\n"); | ||
| 248 | goto err_free; | ||
| 249 | } | ||
| 250 | |||
| 251 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); | ||
| 252 | if (ret) { | ||
| 253 | hid_err(hdev, "hw start failed\n"); | ||
| 254 | goto err_free; | ||
| 255 | } | ||
| 256 | |||
| 257 | /* register led subsystem */ | ||
| 258 | drv_data->led_state = 0; | ||
| 259 | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) | ||
| 260 | drv_data->led[i] = NULL; | ||
| 261 | |||
| 262 | steelseries_srws1_set_leds(hdev, 0); | ||
| 263 | |||
| 264 | name_sz = strlen(hdev->uniq) + 16; | ||
| 265 | |||
| 266 | /* 'ALL', for setting all LEDs simultaneously */ | ||
| 267 | led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); | ||
| 268 | if (!led) { | ||
| 269 | hid_err(hdev, "can't allocate memory for LED ALL\n"); | ||
| 270 | goto err_led; | ||
| 271 | } | ||
| 272 | |||
| 273 | name = (void *)(&led[1]); | ||
| 274 | snprintf(name, name_sz, "SRWS1::%s::RPMALL", hdev->uniq); | ||
| 275 | led->name = name; | ||
| 276 | led->brightness = 0; | ||
| 277 | led->max_brightness = 1; | ||
| 278 | led->brightness_get = steelseries_srws1_led_all_get_brightness; | ||
| 279 | led->brightness_set = steelseries_srws1_led_all_set_brightness; | ||
| 280 | |||
| 281 | drv_data->led[SRWS1_NUMBER_LEDS] = led; | ||
| 282 | ret = led_classdev_register(&hdev->dev, led); | ||
| 283 | if (ret) | ||
| 284 | goto err_led; | ||
| 285 | |||
| 286 | /* Each individual LED */ | ||
| 287 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { | ||
| 288 | led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); | ||
| 289 | if (!led) { | ||
| 290 | hid_err(hdev, "can't allocate memory for LED %d\n", i); | ||
| 291 | goto err_led; | ||
| 292 | } | ||
| 293 | |||
| 294 | name = (void *)(&led[1]); | ||
| 295 | snprintf(name, name_sz, "SRWS1::%s::RPM%d", hdev->uniq, i+1); | ||
| 296 | led->name = name; | ||
| 297 | led->brightness = 0; | ||
| 298 | led->max_brightness = 1; | ||
| 299 | led->brightness_get = steelseries_srws1_led_get_brightness; | ||
| 300 | led->brightness_set = steelseries_srws1_led_set_brightness; | ||
| 301 | |||
| 302 | drv_data->led[i] = led; | ||
| 303 | ret = led_classdev_register(&hdev->dev, led); | ||
| 304 | |||
| 305 | if (ret) { | ||
| 306 | hid_err(hdev, "failed to register LED %d. Aborting.\n", i); | ||
| 307 | err_led: | ||
| 308 | /* Deregister all LEDs (if any) */ | ||
| 309 | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { | ||
| 310 | led = drv_data->led[i]; | ||
| 311 | drv_data->led[i] = NULL; | ||
| 312 | if (!led) | ||
| 313 | continue; | ||
| 314 | led_classdev_unregister(led); | ||
| 315 | kfree(led); | ||
| 316 | } | ||
| 317 | goto out; /* but let the driver continue without LEDs */ | ||
| 318 | } | ||
| 319 | } | ||
| 320 | out: | ||
| 321 | return 0; | ||
| 322 | err_free: | ||
| 323 | kfree(drv_data); | ||
| 324 | return ret; | ||
| 325 | } | ||
| 326 | |||
| 327 | static void steelseries_srws1_remove(struct hid_device *hdev) | ||
| 328 | { | ||
| 329 | int i; | ||
| 330 | struct led_classdev *led; | ||
| 331 | |||
| 332 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev); | ||
| 333 | |||
| 334 | if (drv_data) { | ||
| 335 | /* Deregister LEDs (if any) */ | ||
| 336 | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { | ||
| 337 | led = drv_data->led[i]; | ||
| 338 | drv_data->led[i] = NULL; | ||
| 339 | if (!led) | ||
| 340 | continue; | ||
| 341 | led_classdev_unregister(led); | ||
| 342 | kfree(led); | ||
| 343 | } | ||
| 344 | |||
| 345 | } | ||
| 346 | |||
| 347 | hid_hw_stop(hdev); | ||
| 348 | kfree(drv_data); | ||
| 349 | return; | ||
| 350 | } | ||
| 351 | #endif | ||
| 352 | |||
| 353 | static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc, | ||
| 354 | unsigned int *rsize) | ||
| 355 | { | ||
| 356 | if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8 | ||
| 357 | && rdesc[29] == 0xbb && rdesc[40] == 0xc5) { | ||
| 358 | hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n"); | ||
| 359 | rdesc = steelseries_srws1_rdesc_fixed; | ||
| 360 | *rsize = sizeof(steelseries_srws1_rdesc_fixed); | ||
| 361 | } | ||
| 362 | return rdesc; | ||
| 363 | } | ||
| 364 | |||
| 365 | static const struct hid_device_id steelseries_srws1_devices[] = { | ||
| 366 | { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, | ||
| 367 | { } | ||
| 368 | }; | ||
| 369 | MODULE_DEVICE_TABLE(hid, steelseries_srws1_devices); | ||
| 370 | |||
| 371 | static struct hid_driver steelseries_srws1_driver = { | ||
| 372 | .name = "steelseries_srws1", | ||
| 373 | .id_table = steelseries_srws1_devices, | ||
| 374 | #if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) | ||
| 375 | .probe = steelseries_srws1_probe, | ||
| 376 | .remove = steelseries_srws1_remove, | ||
| 377 | #endif | ||
| 378 | .report_fixup = steelseries_srws1_report_fixup | ||
| 379 | }; | ||
| 380 | |||
| 381 | static int __init steelseries_srws1_init(void) | ||
| 382 | { | ||
| 383 | return hid_register_driver(&steelseries_srws1_driver); | ||
| 384 | } | ||
| 385 | |||
| 386 | static void __exit steelseries_srws1_exit(void) | ||
| 387 | { | ||
| 388 | hid_unregister_driver(&steelseries_srws1_driver); | ||
| 389 | } | ||
| 390 | |||
| 391 | module_init(steelseries_srws1_init); | ||
| 392 | module_exit(steelseries_srws1_exit); | ||
| 393 | MODULE_LICENSE("GPL"); | ||
