Simon Wood | 75dbb95 | 2013-01-31 08:07:07 -0700 | [diff] [blame] | 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> |
Simon Wood | 2e2daff | 2013-01-31 08:07:09 -0700 | [diff] [blame] | 15 | #include <linux/usb.h> |
Simon Wood | 75dbb95 | 2013-01-31 08:07:07 -0700 | [diff] [blame] | 16 | #include <linux/hid.h> |
| 17 | #include <linux/module.h> |
| 18 | |
Simon Wood | 2e2daff | 2013-01-31 08:07:09 -0700 | [diff] [blame] | 19 | #include "usbhid/usbhid.h" |
Simon Wood | 75dbb95 | 2013-01-31 08:07:07 -0700 | [diff] [blame] | 20 | #include "hid-ids.h" |
| 21 | |
Simon Wood | 2e2daff | 2013-01-31 08:07:09 -0700 | [diff] [blame] | 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 | struct led_classdev *led[SRWS1_NUMBER_LEDS]; |
| 27 | }; |
| 28 | #endif |
| 29 | |
Simon Wood | 5492606 | 2013-01-31 08:07:08 -0700 | [diff] [blame] | 30 | /* Fixed report descriptor for Steelseries SRW-S1 wheel controller |
| 31 | * |
| 32 | * The original descriptor hides the sensitivity and assists dials |
| 33 | * a custom vendor usage page. This inserts a patch to make them |
| 34 | * appear in the 'Generic Desktop' usage. |
| 35 | */ |
| 36 | |
| 37 | static __u8 steelseries_srws1_rdesc_fixed[] = { |
| 38 | 0x05, 0x01, /* Usage Page (Desktop) */ |
| 39 | 0x09, 0x08, /* Usage (MultiAxis), Changed */ |
| 40 | 0xA1, 0x01, /* Collection (Application), */ |
| 41 | 0xA1, 0x02, /* Collection (Logical), */ |
| 42 | 0x95, 0x01, /* Report Count (1), */ |
| 43 | 0x05, 0x01, /* Changed Usage Page (Desktop), */ |
| 44 | 0x09, 0x30, /* Changed Usage (X), */ |
| 45 | 0x16, 0xF8, 0xF8, /* Logical Minimum (-1800), */ |
| 46 | 0x26, 0x08, 0x07, /* Logical Maximum (1800), */ |
| 47 | 0x65, 0x14, /* Unit (Degrees), */ |
| 48 | 0x55, 0x0F, /* Unit Exponent (15), */ |
| 49 | 0x75, 0x10, /* Report Size (16), */ |
| 50 | 0x81, 0x02, /* Input (Variable), */ |
| 51 | 0x09, 0x31, /* Changed Usage (Y), */ |
| 52 | 0x15, 0x00, /* Logical Minimum (0), */ |
| 53 | 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ |
| 54 | 0x75, 0x0C, /* Report Size (12), */ |
| 55 | 0x81, 0x02, /* Input (Variable), */ |
| 56 | 0x09, 0x32, /* Changed Usage (Z), */ |
| 57 | 0x15, 0x00, /* Logical Minimum (0), */ |
| 58 | 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ |
| 59 | 0x75, 0x0C, /* Report Size (12), */ |
| 60 | 0x81, 0x02, /* Input (Variable), */ |
| 61 | 0x05, 0x01, /* Usage Page (Desktop), */ |
| 62 | 0x09, 0x39, /* Usage (Hat Switch), */ |
| 63 | 0x25, 0x07, /* Logical Maximum (7), */ |
| 64 | 0x35, 0x00, /* Physical Minimum (0), */ |
| 65 | 0x46, 0x3B, 0x01, /* Physical Maximum (315), */ |
| 66 | 0x65, 0x14, /* Unit (Degrees), */ |
| 67 | 0x75, 0x04, /* Report Size (4), */ |
| 68 | 0x95, 0x01, /* Report Count (1), */ |
| 69 | 0x81, 0x02, /* Input (Variable), */ |
| 70 | 0x25, 0x01, /* Logical Maximum (1), */ |
| 71 | 0x45, 0x01, /* Physical Maximum (1), */ |
| 72 | 0x65, 0x00, /* Unit, */ |
| 73 | 0x75, 0x01, /* Report Size (1), */ |
| 74 | 0x95, 0x03, /* Report Count (3), */ |
| 75 | 0x81, 0x01, /* Input (Constant), */ |
| 76 | 0x05, 0x09, /* Usage Page (Button), */ |
| 77 | 0x19, 0x01, /* Usage Minimum (01h), */ |
| 78 | 0x29, 0x11, /* Usage Maximum (11h), */ |
| 79 | 0x95, 0x11, /* Report Count (17), */ |
| 80 | 0x81, 0x02, /* Input (Variable), */ |
| 81 | /* ---- Dial patch starts here ---- */ |
| 82 | 0x05, 0x01, /* Usage Page (Desktop), */ |
| 83 | 0x09, 0x33, /* Usage (RX), */ |
| 84 | 0x75, 0x04, /* Report Size (4), */ |
| 85 | 0x95, 0x02, /* Report Count (2), */ |
| 86 | 0x15, 0x00, /* Logical Minimum (0), */ |
| 87 | 0x25, 0x0b, /* Logical Maximum (b), */ |
| 88 | 0x81, 0x02, /* Input (Variable), */ |
| 89 | 0x09, 0x35, /* Usage (RZ), */ |
| 90 | 0x75, 0x04, /* Report Size (4), */ |
| 91 | 0x95, 0x01, /* Report Count (1), */ |
| 92 | 0x25, 0x03, /* Logical Maximum (3), */ |
| 93 | 0x81, 0x02, /* Input (Variable), */ |
| 94 | /* ---- Dial patch ends here ---- */ |
| 95 | 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ |
| 96 | 0x09, 0x01, /* Usage (01h), */ |
| 97 | 0x75, 0x04, /* Changed Report Size (4), */ |
| 98 | 0x95, 0x0D, /* Changed Report Count (13), */ |
| 99 | 0x81, 0x02, /* Input (Variable), */ |
| 100 | 0xC0, /* End Collection, */ |
| 101 | 0xA1, 0x02, /* Collection (Logical), */ |
| 102 | 0x09, 0x02, /* Usage (02h), */ |
| 103 | 0x75, 0x08, /* Report Size (8), */ |
| 104 | 0x95, 0x10, /* Report Count (16), */ |
| 105 | 0x91, 0x02, /* Output (Variable), */ |
| 106 | 0xC0, /* End Collection, */ |
| 107 | 0xC0 /* End Collection */ |
| 108 | }; |
| 109 | |
Simon Wood | 2e2daff | 2013-01-31 08:07:09 -0700 | [diff] [blame] | 110 | #if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) |
| 111 | static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds) |
| 112 | { |
| 113 | struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list; |
| 114 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); |
| 115 | __s32 *value = report->field[0]->value; |
| 116 | |
| 117 | value[0] = 0x40; |
| 118 | value[1] = leds & 0xFF; |
| 119 | value[2] = leds >> 8; |
| 120 | value[3] = 0x00; |
| 121 | value[4] = 0x00; |
| 122 | value[5] = 0x00; |
| 123 | value[6] = 0x00; |
| 124 | value[7] = 0x00; |
| 125 | value[8] = 0x00; |
| 126 | value[9] = 0x00; |
| 127 | value[10] = 0x00; |
| 128 | value[11] = 0x00; |
| 129 | value[12] = 0x00; |
| 130 | value[13] = 0x00; |
| 131 | value[14] = 0x00; |
| 132 | value[15] = 0x00; |
| 133 | |
| 134 | usbhid_submit_report(hdev, report, USB_DIR_OUT); |
| 135 | |
| 136 | /* Note: LED change does not show on device until the device is read/polled */ |
| 137 | } |
| 138 | |
Simon Wood | e25d780 | 2013-01-31 08:07:10 -0700 | [diff] [blame^] | 139 | static void steelseries_srws1_led_all_set_brightness(struct led_classdev *led_cdev, |
| 140 | enum led_brightness value) |
| 141 | { |
| 142 | struct device *dev = led_cdev->dev->parent; |
| 143 | struct hid_device *hid = container_of(dev, struct hid_device, dev); |
| 144 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid); |
| 145 | |
| 146 | if (!drv_data) { |
| 147 | hid_err(hid, "Device data not found."); |
| 148 | return; |
| 149 | } |
| 150 | |
| 151 | if (value == LED_OFF) |
| 152 | drv_data->led_state = 0; |
| 153 | else |
| 154 | drv_data->led_state = (1 << (SRWS1_NUMBER_LEDS + 1)) - 1; |
| 155 | |
| 156 | steelseries_srws1_set_leds(hid, drv_data->led_state); |
| 157 | } |
| 158 | |
| 159 | static enum led_brightness steelseries_srws1_led_all_get_brightness(struct led_classdev *led_cdev) |
| 160 | { |
| 161 | struct device *dev = led_cdev->dev->parent; |
| 162 | struct hid_device *hid = container_of(dev, struct hid_device, dev); |
| 163 | struct steelseries_srws1_data *drv_data; |
| 164 | |
| 165 | drv_data = hid_get_drvdata(hid); |
| 166 | |
| 167 | if (!drv_data) { |
| 168 | hid_err(hid, "Device data not found."); |
| 169 | return LED_OFF; |
| 170 | } |
| 171 | |
| 172 | return (drv_data->led_state >> SRWS1_NUMBER_LEDS) ? LED_FULL : LED_OFF; |
| 173 | } |
| 174 | |
Simon Wood | 2e2daff | 2013-01-31 08:07:09 -0700 | [diff] [blame] | 175 | static void steelseries_srws1_led_set_brightness(struct led_classdev *led_cdev, |
| 176 | enum led_brightness value) |
| 177 | { |
| 178 | struct device *dev = led_cdev->dev->parent; |
| 179 | struct hid_device *hid = container_of(dev, struct hid_device, dev); |
| 180 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid); |
| 181 | int i, state = 0; |
| 182 | |
| 183 | if (!drv_data) { |
| 184 | hid_err(hid, "Device data not found."); |
| 185 | return; |
| 186 | } |
| 187 | |
| 188 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { |
| 189 | if (led_cdev != drv_data->led[i]) |
| 190 | continue; |
| 191 | |
| 192 | state = (drv_data->led_state >> i) & 1; |
| 193 | if (value == LED_OFF && state) { |
| 194 | drv_data->led_state &= ~(1 << i); |
| 195 | steelseries_srws1_set_leds(hid, drv_data->led_state); |
| 196 | } else if (value != LED_OFF && !state) { |
| 197 | drv_data->led_state |= 1 << i; |
| 198 | steelseries_srws1_set_leds(hid, drv_data->led_state); |
| 199 | } |
| 200 | break; |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | static enum led_brightness steelseries_srws1_led_get_brightness(struct led_classdev *led_cdev) |
| 205 | { |
| 206 | struct device *dev = led_cdev->dev->parent; |
| 207 | struct hid_device *hid = container_of(dev, struct hid_device, dev); |
| 208 | struct steelseries_srws1_data *drv_data; |
| 209 | int i, value = 0; |
| 210 | |
| 211 | drv_data = hid_get_drvdata(hid); |
| 212 | |
| 213 | if (!drv_data) { |
| 214 | hid_err(hid, "Device data not found."); |
| 215 | return LED_OFF; |
| 216 | } |
| 217 | |
| 218 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) |
| 219 | if (led_cdev == drv_data->led[i]) { |
| 220 | value = (drv_data->led_state >> i) & 1; |
| 221 | break; |
| 222 | } |
| 223 | |
| 224 | return value ? LED_FULL : LED_OFF; |
| 225 | } |
| 226 | |
| 227 | static int steelseries_srws1_probe(struct hid_device *hdev, |
| 228 | const struct hid_device_id *id) |
| 229 | { |
| 230 | int ret, i; |
| 231 | struct led_classdev *led; |
| 232 | size_t name_sz; |
| 233 | char *name; |
| 234 | |
| 235 | struct steelseries_srws1_data *drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL); |
| 236 | |
| 237 | if (drv_data == NULL) { |
| 238 | hid_err(hdev, "can't alloc SRW-S1 memory\n"); |
| 239 | return -ENOMEM; |
| 240 | } |
| 241 | |
| 242 | hid_set_drvdata(hdev, drv_data); |
| 243 | |
| 244 | ret = hid_parse(hdev); |
| 245 | if (ret) { |
| 246 | hid_err(hdev, "parse failed\n"); |
| 247 | goto err_free; |
| 248 | } |
| 249 | |
| 250 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); |
| 251 | if (ret) { |
| 252 | hid_err(hdev, "hw start failed\n"); |
| 253 | goto err_free; |
| 254 | } |
| 255 | |
| 256 | /* register led subsystem */ |
| 257 | drv_data->led_state = 0; |
Simon Wood | e25d780 | 2013-01-31 08:07:10 -0700 | [diff] [blame^] | 258 | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) |
Simon Wood | 2e2daff | 2013-01-31 08:07:09 -0700 | [diff] [blame] | 259 | drv_data->led[i] = NULL; |
| 260 | |
| 261 | steelseries_srws1_set_leds(hdev, 0); |
| 262 | |
Simon Wood | e25d780 | 2013-01-31 08:07:10 -0700 | [diff] [blame^] | 263 | name_sz = strlen(hdev->uniq) + 16; |
Simon Wood | 2e2daff | 2013-01-31 08:07:09 -0700 | [diff] [blame] | 264 | |
Simon Wood | e25d780 | 2013-01-31 08:07:10 -0700 | [diff] [blame^] | 265 | /* 'ALL', for setting all LEDs simultaneously */ |
| 266 | led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); |
| 267 | if (!led) { |
| 268 | hid_err(hdev, "can't allocate memory for LED ALL\n"); |
| 269 | goto err_led; |
| 270 | } |
| 271 | |
| 272 | name = (void *)(&led[1]); |
| 273 | snprintf(name, name_sz, "SRWS1::%s::RPMALL", hdev->uniq); |
| 274 | led->name = name; |
| 275 | led->brightness = 0; |
| 276 | led->max_brightness = 1; |
| 277 | led->brightness_get = steelseries_srws1_led_all_get_brightness; |
| 278 | led->brightness_set = steelseries_srws1_led_all_set_brightness; |
| 279 | |
| 280 | drv_data->led[SRWS1_NUMBER_LEDS] = led; |
| 281 | ret = led_classdev_register(&hdev->dev, led); |
| 282 | if (ret) |
| 283 | goto err_led; |
| 284 | |
| 285 | /* Each individual LED */ |
Simon Wood | 2e2daff | 2013-01-31 08:07:09 -0700 | [diff] [blame] | 286 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { |
| 287 | led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); |
| 288 | if (!led) { |
| 289 | hid_err(hdev, "can't allocate memory for LED %d\n", i); |
| 290 | goto err_led; |
| 291 | } |
| 292 | |
| 293 | name = (void *)(&led[1]); |
| 294 | snprintf(name, name_sz, "SRWS1::%s::RPM%d", hdev->uniq, i+1); |
| 295 | led->name = name; |
| 296 | led->brightness = 0; |
| 297 | led->max_brightness = 1; |
| 298 | led->brightness_get = steelseries_srws1_led_get_brightness; |
| 299 | led->brightness_set = steelseries_srws1_led_set_brightness; |
| 300 | |
| 301 | drv_data->led[i] = led; |
| 302 | ret = led_classdev_register(&hdev->dev, led); |
| 303 | |
| 304 | if (ret) { |
| 305 | hid_err(hdev, "failed to register LED %d. Aborting.\n", i); |
| 306 | err_led: |
| 307 | /* Deregister all LEDs (if any) */ |
Simon Wood | e25d780 | 2013-01-31 08:07:10 -0700 | [diff] [blame^] | 308 | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { |
Simon Wood | 2e2daff | 2013-01-31 08:07:09 -0700 | [diff] [blame] | 309 | led = drv_data->led[i]; |
| 310 | drv_data->led[i] = NULL; |
| 311 | if (!led) |
| 312 | continue; |
| 313 | led_classdev_unregister(led); |
| 314 | kfree(led); |
| 315 | } |
| 316 | goto out; /* but let the driver continue without LEDs */ |
| 317 | } |
| 318 | } |
| 319 | out: |
| 320 | return 0; |
| 321 | err_free: |
| 322 | kfree(drv_data); |
| 323 | return ret; |
| 324 | } |
| 325 | |
| 326 | static void steelseries_srws1_remove(struct hid_device *hdev) |
| 327 | { |
| 328 | int i; |
| 329 | struct led_classdev *led; |
| 330 | |
| 331 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev); |
| 332 | |
| 333 | if (drv_data) { |
| 334 | /* Deregister LEDs (if any) */ |
Simon Wood | e25d780 | 2013-01-31 08:07:10 -0700 | [diff] [blame^] | 335 | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { |
Simon Wood | 2e2daff | 2013-01-31 08:07:09 -0700 | [diff] [blame] | 336 | led = drv_data->led[i]; |
| 337 | drv_data->led[i] = NULL; |
| 338 | if (!led) |
| 339 | continue; |
| 340 | led_classdev_unregister(led); |
| 341 | kfree(led); |
| 342 | } |
| 343 | |
| 344 | } |
| 345 | |
| 346 | hid_hw_stop(hdev); |
| 347 | kfree(drv_data); |
| 348 | return; |
| 349 | } |
| 350 | #endif |
| 351 | |
Simon Wood | 75dbb95 | 2013-01-31 08:07:07 -0700 | [diff] [blame] | 352 | static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc, |
| 353 | unsigned int *rsize) |
| 354 | { |
| 355 | if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8 |
| 356 | && rdesc[29] == 0xbb && rdesc[40] == 0xc5) { |
| 357 | hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n"); |
Simon Wood | 5492606 | 2013-01-31 08:07:08 -0700 | [diff] [blame] | 358 | rdesc = steelseries_srws1_rdesc_fixed; |
| 359 | *rsize = sizeof(steelseries_srws1_rdesc_fixed); |
Simon Wood | 75dbb95 | 2013-01-31 08:07:07 -0700 | [diff] [blame] | 360 | } |
| 361 | return rdesc; |
| 362 | } |
| 363 | |
| 364 | static const struct hid_device_id steelseries_srws1_devices[] = { |
| 365 | { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, |
| 366 | { } |
| 367 | }; |
| 368 | MODULE_DEVICE_TABLE(hid, steelseries_srws1_devices); |
| 369 | |
| 370 | static struct hid_driver steelseries_srws1_driver = { |
| 371 | .name = "steelseries_srws1", |
| 372 | .id_table = steelseries_srws1_devices, |
Simon Wood | 2e2daff | 2013-01-31 08:07:09 -0700 | [diff] [blame] | 373 | #if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) |
| 374 | .probe = steelseries_srws1_probe, |
| 375 | .remove = steelseries_srws1_remove, |
| 376 | #endif |
Simon Wood | 75dbb95 | 2013-01-31 08:07:07 -0700 | [diff] [blame] | 377 | .report_fixup = steelseries_srws1_report_fixup |
| 378 | }; |
| 379 | |
| 380 | static int __init steelseries_srws1_init(void) |
| 381 | { |
| 382 | return hid_register_driver(&steelseries_srws1_driver); |
| 383 | } |
| 384 | |
| 385 | static void __exit steelseries_srws1_exit(void) |
| 386 | { |
| 387 | hid_unregister_driver(&steelseries_srws1_driver); |
| 388 | } |
| 389 | |
| 390 | module_init(steelseries_srws1_init); |
| 391 | module_exit(steelseries_srws1_exit); |
| 392 | MODULE_LICENSE("GPL"); |