From e57a67da600408c6f15c453a48c386d0f17188dd Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Fri, 14 Dec 2012 20:57:34 -0200 Subject: HID: hid-sony: fix troubles with Sony remote clones There are some Sony clone gamepads that are incompatible with PS3 since firmware 3.50, as they decided to prevent those devices to work, without any good technical reason. I was one of those 'blessed' people affected by their niceness with their customers. Marcelo also has another device with a similar problem. Perhaps due to Sony's way to block the device, damaging the device's eeprom, or perhaps because they just have a different, broken Report descriptor, there are 3 buttons that don't work on both devices (the ones equivalent to square, round and X). What it happens is that the descriptor generate weird EV_ABS events to those buttons, instead of EV_MSC/EV_KEY. A fix that seems to be enough for them is to return the original sixaxis table instead of the broken one. That's what this patch does. Yet, there are some missing entries at the used keytable. On my tests, all keys are now producing the right events, but the reported keycodes look weird: "square" key: (Button.0010 = 1) 1355524363.460835: event type EV_MSC(0x04): scancode = 0x90010 1355524363.460835: event type EV_KEY(0x01) key_up: BTN_DEAD(0x0001) "round" key: (Button.000e = 1) 1355524410.908705: event type EV_MSC(0x04): scancode = 0x9000e 1355524410.908705: event type EV_KEY(0x01) key_down: (0x0001) 1355524410.971788: event type EV_MSC(0x04): scancode = 0x9000e 1355524410.971788: event type EV_KEY(0x01) key_up: (0x0001) "X" key: (Button.000f = 1) 1355524384.880813: event type EV_MSC(0x04): scancode = 0x9000f 1355524384.880813: event type EV_KEY(0x01) key_down: (0x0001) 1355524384.979815: event type EV_MSC(0x04): scancode = 0x9000f 1355524384.979815: event type EV_KEY(0x01) key_up: (0x0001) The rationale is likely due to those entries at rdesc table, where the Kernel were not likely able to parse: Button.000d ---> Key.? Button.000e ---> Key.? Button.000f ---> Key.? Button.0010 ---> Key.BtnDead Button.0011 ---> Key.? Button.0012 ---> Key.? Button.0013 ---> Key.? As a reference, this is the rdisc used on my clone (a Mad Catz model 8846): 05 01 09 04 a1 01 a1 02 85 01 75 08 95 01 15 00 26 ff 00 81 03 75 01 95 0d 15 00 25 01 35 00 45 01 05 09 19 01 29 0d 81 02 75 01 95 03 06 00 ff 81 03 05 01 25 07 46 3b 01 75 04 95 01 65 14 09 39 81 42 65 00 75 01 95 0c 06 00 ff 81 03 15 00 26 ff 00 05 01 09 01 a1 00 75 08 95 04 15 00 15 00 15 00 35 00 35 00 46 ff 00 09 30 09 31 09 32 09 35 81 02 c0 05 01 75 08 95 27 09 01 81 02 75 08 95 30 09 01 91 02 75 08 95 30 09 01 b1 02 c0 a1 02 85 02 75 08 95 30 09 01 b1 02 c0 a1 02 85 ee 75 08 95 30 09 01 b1 02 c0 a1 02 85 ef 75 08 95 30 09 01 b1 02 c0 c0 This is what's returned on Marcelo's device (not sure what is the brand name of his device): 05 01 09 04 a1 01 a1 02 85 01 75 08 95 01 15 00 26 ff 00 81 03 75 01 95 13 15 00 25 01 35 00 45 01 05 09 19 01 29 13 81 02 75 01 95 0d 06 00 ff 81 03 15 00 26 ff 00 05 01 09 01 a1 00 75 08 95 04 35 00 46 ff 00 09 30 09 31 09 32 09 35 81 02 c0 05 01 95 13 09 01 81 02 95 0c 81 01 75 10 95 04 26 ff 03 46 ff 03 09 01 81 02 c0 a1 02 85 02 75 08 95 30 09 01 b1 02 c0 a1 02 85 ee 75 08 95 30 09 01 b1 02 c0 a1 02 85 ef 75 08 95 30 09 01 b1 02 c0 c0 Reported-by: Marcelo Leitner Signed-off-by: Mauro Carvalho Chehab Tested-by: Marcelo Leitner Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 7f33ebf..16df4d8 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -33,6 +33,28 @@ static const u8 sixaxis_rdesc_fixup[] = { 0x03, 0x46, 0xFF, 0x03, 0x09, 0x01, 0x81, 0x02 }; +static const u8 sixaxis_rdesc_fixup2[] = { + 0x05, 0x01, 0x09, 0x04, 0xa1, 0x01, 0xa1, 0x02, + 0x85, 0x01, 0x75, 0x08, 0x95, 0x01, 0x15, 0x00, + 0x26, 0xff, 0x00, 0x81, 0x03, 0x75, 0x01, 0x95, + 0x13, 0x15, 0x00, 0x25, 0x01, 0x35, 0x00, 0x45, + 0x01, 0x05, 0x09, 0x19, 0x01, 0x29, 0x13, 0x81, + 0x02, 0x75, 0x01, 0x95, 0x0d, 0x06, 0x00, 0xff, + 0x81, 0x03, 0x15, 0x00, 0x26, 0xff, 0x00, 0x05, + 0x01, 0x09, 0x01, 0xa1, 0x00, 0x75, 0x08, 0x95, + 0x04, 0x35, 0x00, 0x46, 0xff, 0x00, 0x09, 0x30, + 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x81, 0x02, + 0xc0, 0x05, 0x01, 0x95, 0x13, 0x09, 0x01, 0x81, + 0x02, 0x95, 0x0c, 0x81, 0x01, 0x75, 0x10, 0x95, + 0x04, 0x26, 0xff, 0x03, 0x46, 0xff, 0x03, 0x09, + 0x01, 0x81, 0x02, 0xc0, 0xa1, 0x02, 0x85, 0x02, + 0x75, 0x08, 0x95, 0x30, 0x09, 0x01, 0xb1, 0x02, + 0xc0, 0xa1, 0x02, 0x85, 0xee, 0x75, 0x08, 0x95, + 0x30, 0x09, 0x01, 0xb1, 0x02, 0xc0, 0xa1, 0x02, + 0x85, 0xef, 0x75, 0x08, 0x95, 0x30, 0x09, 0x01, + 0xb1, 0x02, 0xc0, 0xc0, +}; + struct sony_sc { unsigned long quirks; }; @@ -56,6 +78,12 @@ static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc, hid_info(hdev, "Fixing up Sony Sixaxis report descriptor\n"); memcpy((void *)&rdesc[83], (void *)&sixaxis_rdesc_fixup, sizeof(sixaxis_rdesc_fixup)); + } else if (sc->quirks & SIXAXIS_CONTROLLER_USB && + *rsize > sizeof(sixaxis_rdesc_fixup2)) { + hid_info(hdev, "Sony Sixaxis clone detected. Using original report descriptor (size: %d clone; %d new)\n", + *rsize, (int)sizeof(sixaxis_rdesc_fixup2)); + *rsize = sizeof(sixaxis_rdesc_fixup2); + memcpy(rdesc, &sixaxis_rdesc_fixup2, *rsize); } return rdesc; } -- cgit v0.10.2 From a464918419f94a0043d2f549d6defb4c3f69f68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Luis=20V=C3=A1zquez=20Cao?= Date: Tue, 15 Jan 2013 19:40:48 +0900 Subject: HID: add support for Sony RF receiver with USB product id 0x0374 Some Vaio desktop computers, among them the VGC-LN51JGB multimedia PC, have a RF receiver, multi-interface USB device 054c:0374, that is used to connect a wireless keyboard and a wireless mouse. The keyboard works flawlessly, but the mouse (VGP-WMS3 in my case) does not seem to be generating any pointer events. The problem is that the mouse pointer is wrongly declared as a constant non-data variable in the report descriptor (see lsusb and usbhid-dump output below), with the consequence that it is ignored by the HID code. Add this device to the have-special-driver list and fix up the report descriptor in the Sony-specific driver which happens to already have a fixup for a similar firmware bug. # lsusb -vd 054C:0374 Bus 003 Device 002: ID 054c:0374 Sony Corp. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 8 idVendor 0x054c Sony Corp. idProduct 0x0374 iSerial 0 [...] Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 2 Mouse iInterface 2 RF Receiver [...] Report Descriptor: (length is 100) [...] Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x30 ] 48 Direction-X Item(Local ): Usage, data= [ 0x31 ] 49 Direction-Y Item(Global): Report Count, data= [ 0x02 ] 2 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Logical Minimum, data= [ 0x81 ] 129 Item(Global): Logical Maximum, data= [ 0x7f ] 127 Item(Main ): Input, data= [ 0x07 ] 7 Constant Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield # usbhid-dump 003:002:001:DESCRIPTOR 1357910009.758544 05 01 09 02 A1 01 05 01 09 02 A1 02 85 01 09 01 A1 00 05 09 19 01 29 05 95 05 75 01 15 00 25 01 81 02 75 03 95 01 81 01 05 01 09 30 09 31 95 02 75 08 15 81 25 7F 81 07 A1 02 85 01 09 38 35 00 45 00 15 81 25 7F 95 01 75 08 81 06 C0 A1 02 85 01 05 0C 15 81 25 7F 95 01 75 08 0A 38 02 81 06 C0 C0 C0 C0 Cc: linux-input@vger.kernel.org Cc: linux-usb@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Fernando Luis Vazquez Cao Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index eb2ee11..1651728 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1697,6 +1697,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE) }, { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) }, { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300) }, { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb304) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 4dfa605..50ac909 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -706,6 +706,7 @@ #define USB_VENDOR_ID_SONY 0x054c #define USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE 0x024b +#define USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE 0x0374 #define USB_DEVICE_ID_SONY_PS3_BDREMOTE 0x0306 #define USB_DEVICE_ID_SONY_PS3_CONTROLLER 0x0268 #define USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER 0x042f diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 16df4d8..6d2d459 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -67,7 +67,7 @@ static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc, if ((sc->quirks & VAIO_RDESC_CONSTANT) && *rsize >= 56 && rdesc[54] == 0x81 && rdesc[55] == 0x07) { - hid_info(hdev, "Fixing up Sony Vaio VGX report descriptor\n"); + hid_info(hdev, "Fixing up Sony RF Receiver report descriptor\n"); rdesc[55] = 0x06; } @@ -245,6 +245,8 @@ static const struct hid_device_id sony_devices[] = { .driver_data = SIXAXIS_CONTROLLER_BT }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE), .driver_data = VAIO_RDESC_CONSTANT }, + { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE), + .driver_data = VAIO_RDESC_CONSTANT }, { } }; MODULE_DEVICE_TABLE(hid, sony_devices); -- cgit v0.10.2 From 99d249021abd4341771523ed8dd7946276103432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Luis=20V=C3=A1zquez=20Cao?= Date: Tue, 22 Jan 2013 15:20:38 +0900 Subject: HID: clean up quirk for Sony RF receivers Document what the fix-up is does and make it more robust by ensuring that it is only applied to the USB interface that corresponds to the mouse (sony_report_fixup() is called once per interface during probing). Cc: linux-input@vger.kernel.org Cc: linux-usb@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Fernando Luis Vazquez Cao Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 6d2d459..9769c00 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -65,9 +65,19 @@ static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc, { struct sony_sc *sc = hid_get_drvdata(hdev); - if ((sc->quirks & VAIO_RDESC_CONSTANT) && - *rsize >= 56 && rdesc[54] == 0x81 && rdesc[55] == 0x07) { + /* + * Some Sony RF receivers wrongly declare the mouse pointer as a + * a constant non-data variable. + */ + if ((sc->quirks & VAIO_RDESC_CONSTANT) && *rsize >= 56 && + /* usage page: generic desktop controls */ + /* rdesc[0] == 0x05 && rdesc[1] == 0x01 && */ + /* usage: mouse */ + rdesc[2] == 0x09 && rdesc[3] == 0x02 && + /* input (usage page for x,y axes): constant, variable, relative */ + rdesc[54] == 0x81 && rdesc[55] == 0x07) { hid_info(hdev, "Fixing up Sony RF Receiver report descriptor\n"); + /* input: data, variable, relative */ rdesc[55] = 0x06; } -- cgit v0.10.2 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 diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index e7d6a13..37df92f 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -596,6 +596,12 @@ config HID_SPEEDLINK ---help--- Support for Speedlink Vicious and Divine Cezanne mouse. +config HID_STEELSERIES_SRWS1 + tristate "Steelseries SRW-S1 steering wheel support" + depends on USB_HID + ---help--- + Support for Steelseries SRW-S1 steering wheel + config HID_SUNPLUS tristate "Sunplus wireless desktop" depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index b622157..bbebe0a 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 diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index eb2ee11..65cda7f 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1697,6 +1697,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) }, { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, { HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) }, { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300) }, { HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb304) }, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 4dfa605..f5976f3 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -723,6 +723,9 @@ #define USB_VENDOR_ID_STANTUM_SITRONIX 0x1403 #define USB_DEVICE_ID_MTP_SITRONIX 0x5001 +#define USB_VENDOR_ID_STEELSERIES 0x1038 +#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410 + #define USB_VENDOR_ID_SUN 0x0430 #define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab diff --git a/drivers/hid/hid-steelseries-srws1.c b/drivers/hid/hid-steelseries-srws1.c new file mode 100644 index 0000000..161917c --- /dev/null +++ b/drivers/hid/hid-steelseries-srws1.c @@ -0,0 +1,58 @@ +/* + * 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 "hid-ids.h" + +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[11] = 0x01; + rdesc[13] = 0x30; + rdesc[29] = 0x31; + rdesc[40] = 0x32; + } + 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, + .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 v0.10.2 From 5492606dc39b2f1ce67cf718f09ade373a35f4eb Mon Sep 17 00:00:00 2001 From: Simon Wood Date: Thu, 31 Jan 2013 08:07:08 -0700 Subject: USB: HID: Steelseries SRW-S1 Add support for dials This patch to the SRW-S1 driver re-writes the HID descriptor to insert a section for the 3 dials on the device, previously these were contained within a 'Manufacturer Specific' usage page. Signed-off-by: Simon Wood Tested-by: John Murphy Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-steelseries-srws1.c b/drivers/hid/hid-steelseries-srws1.c index 161917c..e95434d 100644 --- a/drivers/hid/hid-steelseries-srws1.c +++ b/drivers/hid/hid-steelseries-srws1.c @@ -17,16 +17,94 @@ #include "hid-ids.h" +/* 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 */ +}; + 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[11] = 0x01; - rdesc[13] = 0x30; - rdesc[29] = 0x31; - rdesc[40] = 0x32; + rdesc = steelseries_srws1_rdesc_fixed; + *rsize = sizeof(steelseries_srws1_rdesc_fixed); } return rdesc; } -- cgit v0.10.2 From 2e2daff3a51f2d10155b03f461f4e29eaf80dcbd Mon Sep 17 00:00:00 2001 From: Simon Wood Date: Thu, 31 Jan 2013 08:07:09 -0700 Subject: USB: HID: Steelseries SRW-S1 Add support for LEDs This patch to the SRW-S1 driver adds support for the LED RPM meter on the front of the device. The LEDs are controlled via /sys/class/leds interface, with an individual control for each of the 15 LEDs. Signed-off-by: Simon Wood Tested-by: John Murphy Signed-off-by: Jiri Kosina diff --git a/Documentation/ABI/testing/sysfs-driver-hid-srws1 b/Documentation/ABI/testing/sysfs-driver-hid-srws1 new file mode 100644 index 0000000..c27b34d --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-srws1 @@ -0,0 +1,20 @@ +What: /sys/class/leds/SRWS1::::RPM1 +What: /sys/class/leds/SRWS1::::RPM2 +What: /sys/class/leds/SRWS1::::RPM3 +What: /sys/class/leds/SRWS1::::RPM4 +What: /sys/class/leds/SRWS1::::RPM5 +What: /sys/class/leds/SRWS1::::RPM6 +What: /sys/class/leds/SRWS1::::RPM7 +What: /sys/class/leds/SRWS1::::RPM8 +What: /sys/class/leds/SRWS1::::RPM9 +What: /sys/class/leds/SRWS1::::RPM10 +What: /sys/class/leds/SRWS1::::RPM11 +What: /sys/class/leds/SRWS1::::RPM12 +What: /sys/class/leds/SRWS1::::RPM13 +What: /sys/class/leds/SRWS1::::RPM14 +What: /sys/class/leds/SRWS1::::RPM15 +Date: Jan 2013 +KernelVersion: 3.9 +Contact: Simon Wood +Description: Provides a control for turning on/off the LEDs which form + an RPM meter on the front of the controller diff --git a/drivers/hid/hid-steelseries-srws1.c b/drivers/hid/hid-steelseries-srws1.c index e95434d..a7386699 100644 --- a/drivers/hid/hid-steelseries-srws1.c +++ b/drivers/hid/hid-steelseries-srws1.c @@ -12,11 +12,21 @@ */ #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 @@ -97,6 +107,191 @@ static __u8 steelseries_srws1_rdesc_fixed[] = { 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_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; i++) + drv_data->led[i] = NULL; + + steelseries_srws1_set_leds(hdev, 0); + + name_sz = strlen(hdev->uniq) + 15; + + 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; 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; 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) { @@ -118,6 +313,10 @@ 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 }; -- cgit v0.10.2 From e25d780581dc4d261c66e072a59c34782bd03e0a Mon Sep 17 00:00:00 2001 From: Simon Wood Date: Thu, 31 Jan 2013 08:07:10 -0700 Subject: USB: HID: Steelseries SRW-S1 Add support controlling all LEDs simultaneously This patch to the SRW-S1 driver adds the ability to control all LEDs simultaneously as testing showed that it was slow (noticably!!) when seting or clearing all the LEDs in turn. It adds a 'RPMALL' LED, whose behavoir is asserted to all the LEDs in the bar graph, individual LEDs can subsequently be turned on/off individually. Signed-off-by: Simon Wood Tested-by: John Murphy Signed-off-by: Jiri Kosina diff --git a/Documentation/ABI/testing/sysfs-driver-hid-srws1 b/Documentation/ABI/testing/sysfs-driver-hid-srws1 index c27b34d..d0eba70 100644 --- a/Documentation/ABI/testing/sysfs-driver-hid-srws1 +++ b/Documentation/ABI/testing/sysfs-driver-hid-srws1 @@ -13,6 +13,7 @@ What: /sys/class/leds/SRWS1::::RPM12 What: /sys/class/leds/SRWS1::::RPM13 What: /sys/class/leds/SRWS1::::RPM14 What: /sys/class/leds/SRWS1::::RPM15 +What: /sys/class/leds/SRWS1::::RPMALL Date: Jan 2013 KernelVersion: 3.9 Contact: Simon Wood diff --git a/drivers/hid/hid-steelseries-srws1.c b/drivers/hid/hid-steelseries-srws1.c index a7386699..365bc9e 100644 --- a/drivers/hid/hid-steelseries-srws1.c +++ b/drivers/hid/hid-steelseries-srws1.c @@ -136,6 +136,42 @@ static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds) /* Note: LED change does not show on device until the device is read/polled */ } +static void steelseries_srws1_led_all_set_brightness(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct device *dev = led_cdev->dev->parent; + struct hid_device *hid = container_of(dev, struct hid_device, dev); + struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid); + + if (!drv_data) { + hid_err(hid, "Device data not found."); + return; + } + + if (value == LED_OFF) + drv_data->led_state = 0; + else + drv_data->led_state = (1 << (SRWS1_NUMBER_LEDS + 1)) - 1; + + steelseries_srws1_set_leds(hid, drv_data->led_state); +} + +static enum led_brightness steelseries_srws1_led_all_get_brightness(struct led_classdev *led_cdev) +{ + struct device *dev = led_cdev->dev->parent; + struct hid_device *hid = container_of(dev, struct hid_device, dev); + struct steelseries_srws1_data *drv_data; + + drv_data = hid_get_drvdata(hid); + + if (!drv_data) { + hid_err(hid, "Device data not found."); + return LED_OFF; + } + + return (drv_data->led_state >> SRWS1_NUMBER_LEDS) ? LED_FULL : LED_OFF; +} + static void steelseries_srws1_led_set_brightness(struct led_classdev *led_cdev, enum led_brightness value) { @@ -219,13 +255,34 @@ static int steelseries_srws1_probe(struct hid_device *hdev, /* register led subsystem */ drv_data->led_state = 0; - for (i = 0; i < SRWS1_NUMBER_LEDS; i++) + for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) drv_data->led[i] = NULL; steelseries_srws1_set_leds(hdev, 0); - name_sz = strlen(hdev->uniq) + 15; + name_sz = strlen(hdev->uniq) + 16; + + /* 'ALL', for setting all LEDs simultaneously */ + led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); + if (!led) { + hid_err(hdev, "can't allocate memory for LED ALL\n"); + goto err_led; + } + + name = (void *)(&led[1]); + snprintf(name, name_sz, "SRWS1::%s::RPMALL", hdev->uniq); + led->name = name; + led->brightness = 0; + led->max_brightness = 1; + led->brightness_get = steelseries_srws1_led_all_get_brightness; + led->brightness_set = steelseries_srws1_led_all_set_brightness; + + drv_data->led[SRWS1_NUMBER_LEDS] = led; + ret = led_classdev_register(&hdev->dev, led); + if (ret) + goto err_led; + /* Each individual LED */ for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); if (!led) { @@ -248,7 +305,7 @@ static int steelseries_srws1_probe(struct hid_device *hdev, hid_err(hdev, "failed to register LED %d. Aborting.\n", i); err_led: /* Deregister all LEDs (if any) */ - for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { + for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { led = drv_data->led[i]; drv_data->led[i] = NULL; if (!led) @@ -275,7 +332,7 @@ static void steelseries_srws1_remove(struct hid_device *hdev) if (drv_data) { /* Deregister LEDs (if any) */ - for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { + for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { led = drv_data->led[i]; drv_data->led[i] = NULL; if (!led) -- cgit v0.10.2 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 diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 37df92f..6d5d266 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 bbebe0a..247f2b8 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 365bc9e..0000000 --- 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 0000000..365bc9e --- /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 v0.10.2 From 7e41576247b782a21c05f7ea8a78a6db119ba789 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 31 Jan 2013 16:51:47 +0100 Subject: HID: steelseries: fix out of bound array access The last field of the driver_data->leds[] array is used to store the special toggle for setting all leds simultaneously, so we need to allocate appropriate number of led_classdev pointers. Signed-off-by: Jiri Kosina diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c index 365bc9e..2ed995c 100644 --- a/drivers/hid/hid-steelseries.c +++ b/drivers/hid/hid-steelseries.c @@ -23,7 +23,8 @@ #define SRWS1_NUMBER_LEDS 15 struct steelseries_srws1_data { __u16 led_state; - struct led_classdev *led[SRWS1_NUMBER_LEDS]; + /* the last element is used for setting all leds simultaneously */ + struct led_classdev *led[SRWS1_NUMBER_LEDS + 1]; }; #endif -- cgit v0.10.2