projects/linux·blob master · refs

684 lines · 20.3 KiB

1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * HID driver for Steelseries devices
4 *
5 * Copyright (c) 2013 Simon Wood
6 */
7
8/*
9 */
10
11#include <linux/device.h>
12#include <linux/dmi.h>
13#include <linux/hid.h>
14#include <linux/module.h>
15#include <linux/usb.h>
16#include <linux/leds.h>
17#include <linux/led-class-multicolor.h>
18#include <linux/slab.h>
19
20#include "hid-ids.h"
21
22#define STEELSERIES_SRWS1 BIT(0)
23#define STEELSERIES_MSI_RGB BIT(1)
24
25#define STEELSERIES_MSI_RGB_WVALUE 0x0300 /* Feature report, ID 0 */
26#define STEELSERIES_MSI_RGB_REPORT_LEN 524
27#define STEELSERIES_MSI_RGB_OPCODE 0x0c
28#define STEELSERIES_MSI_RGB_KLC_MODE 0x66
29#define STEELSERIES_MSI_RGB_ALC_MODE 0x06
30
31#define STEELSERIES_HAS_LEDS_MULTICOLOR \
32 (IS_BUILTIN(CONFIG_LEDS_CLASS_MULTICOLOR) || \
33 (IS_MODULE(CONFIG_LEDS_CLASS_MULTICOLOR) && IS_MODULE(CONFIG_HID_STEELSERIES)))
34
35struct steelseries_device {
36 struct hid_device *hdev;
37 unsigned long quirks;
38
39#if STEELSERIES_HAS_LEDS_MULTICOLOR
40 struct led_classdev_mc mc_cdev;
41 struct mc_subled subled_info[3];
42 struct mutex rgb_lock; /* protects rgb_buf */
43 u8 *rgb_buf;
44#endif
45};
46
47#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
48 (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
49#define SRWS1_NUMBER_LEDS 15
50struct steelseries_srws1_data {
51 __u16 led_state;
52 /* the last element is used for setting all leds simultaneously */
53 struct led_classdev *led[SRWS1_NUMBER_LEDS + 1];
54};
55#endif
56
57/* Fixed report descriptor for Steelseries SRW-S1 wheel controller
58 *
59 * The original descriptor hides the sensitivity and assists dials
60 * a custom vendor usage page. This inserts a patch to make them
61 * appear in the 'Generic Desktop' usage.
62 */
63
64static const __u8 steelseries_srws1_rdesc_fixed[] = {
650x05, 0x01, /* Usage Page (Desktop) */
660x09, 0x08, /* Usage (MultiAxis), Changed */
670xA1, 0x01, /* Collection (Application), */
680xA1, 0x02, /* Collection (Logical), */
690x95, 0x01, /* Report Count (1), */
700x05, 0x01, /* Changed Usage Page (Desktop), */
710x09, 0x30, /* Changed Usage (X), */
720x16, 0xF8, 0xF8, /* Logical Minimum (-1800), */
730x26, 0x08, 0x07, /* Logical Maximum (1800), */
740x65, 0x14, /* Unit (Degrees), */
750x55, 0x0F, /* Unit Exponent (15), */
760x75, 0x10, /* Report Size (16), */
770x81, 0x02, /* Input (Variable), */
780x09, 0x31, /* Changed Usage (Y), */
790x15, 0x00, /* Logical Minimum (0), */
800x26, 0xFF, 0x03, /* Logical Maximum (1023), */
810x75, 0x0C, /* Report Size (12), */
820x81, 0x02, /* Input (Variable), */
830x09, 0x32, /* Changed Usage (Z), */
840x15, 0x00, /* Logical Minimum (0), */
850x26, 0xFF, 0x03, /* Logical Maximum (1023), */
860x75, 0x0C, /* Report Size (12), */
870x81, 0x02, /* Input (Variable), */
880x05, 0x01, /* Usage Page (Desktop), */
890x09, 0x39, /* Usage (Hat Switch), */
900x25, 0x07, /* Logical Maximum (7), */
910x35, 0x00, /* Physical Minimum (0), */
920x46, 0x3B, 0x01, /* Physical Maximum (315), */
930x65, 0x14, /* Unit (Degrees), */
940x75, 0x04, /* Report Size (4), */
950x95, 0x01, /* Report Count (1), */
960x81, 0x02, /* Input (Variable), */
970x25, 0x01, /* Logical Maximum (1), */
980x45, 0x01, /* Physical Maximum (1), */
990x65, 0x00, /* Unit, */
1000x75, 0x01, /* Report Size (1), */
1010x95, 0x03, /* Report Count (3), */
1020x81, 0x01, /* Input (Constant), */
1030x05, 0x09, /* Usage Page (Button), */
1040x19, 0x01, /* Usage Minimum (01h), */
1050x29, 0x11, /* Usage Maximum (11h), */
1060x95, 0x11, /* Report Count (17), */
1070x81, 0x02, /* Input (Variable), */
108 /* ---- Dial patch starts here ---- */
1090x05, 0x01, /* Usage Page (Desktop), */
1100x09, 0x33, /* Usage (RX), */
1110x75, 0x04, /* Report Size (4), */
1120x95, 0x02, /* Report Count (2), */
1130x15, 0x00, /* Logical Minimum (0), */
1140x25, 0x0b, /* Logical Maximum (b), */
1150x81, 0x02, /* Input (Variable), */
1160x09, 0x35, /* Usage (RZ), */
1170x75, 0x04, /* Report Size (4), */
1180x95, 0x01, /* Report Count (1), */
1190x25, 0x03, /* Logical Maximum (3), */
1200x81, 0x02, /* Input (Variable), */
121 /* ---- Dial patch ends here ---- */
1220x06, 0x00, 0xFF, /* Usage Page (FF00h), */
1230x09, 0x01, /* Usage (01h), */
1240x75, 0x04, /* Changed Report Size (4), */
1250x95, 0x0D, /* Changed Report Count (13), */
1260x81, 0x02, /* Input (Variable), */
1270xC0, /* End Collection, */
1280xA1, 0x02, /* Collection (Logical), */
1290x09, 0x02, /* Usage (02h), */
1300x75, 0x08, /* Report Size (8), */
1310x95, 0x10, /* Report Count (16), */
1320x91, 0x02, /* Output (Variable), */
1330xC0, /* End Collection, */
1340xC0 /* End Collection */
135};
136
137#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
138 (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
139static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds)
140{
141 struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list;
142 struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
143 __s32 *value = report->field[0]->value;
144
145 value[0] = 0x40;
146 value[1] = leds & 0xFF;
147 value[2] = leds >> 8;
148 value[3] = 0x00;
149 value[4] = 0x00;
150 value[5] = 0x00;
151 value[6] = 0x00;
152 value[7] = 0x00;
153 value[8] = 0x00;
154 value[9] = 0x00;
155 value[10] = 0x00;
156 value[11] = 0x00;
157 value[12] = 0x00;
158 value[13] = 0x00;
159 value[14] = 0x00;
160 value[15] = 0x00;
161
162 hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
163
164 /* Note: LED change does not show on device until the device is read/polled */
165}
166
167static void steelseries_srws1_led_all_set_brightness(struct led_classdev *led_cdev,
168 enum led_brightness value)
169{
170 struct device *dev = led_cdev->dev->parent;
171 struct hid_device *hid = to_hid_device(dev);
172 struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid);
173
174 if (!drv_data) {
175 hid_err(hid, "Device data not found.");
176 return;
177 }
178
179 if (value == LED_OFF)
180 drv_data->led_state = 0;
181 else
182 drv_data->led_state = (1 << (SRWS1_NUMBER_LEDS + 1)) - 1;
183
184 steelseries_srws1_set_leds(hid, drv_data->led_state);
185}
186
187static enum led_brightness steelseries_srws1_led_all_get_brightness(struct led_classdev *led_cdev)
188{
189 struct device *dev = led_cdev->dev->parent;
190 struct hid_device *hid = to_hid_device(dev);
191 struct steelseries_srws1_data *drv_data;
192
193 drv_data = hid_get_drvdata(hid);
194
195 if (!drv_data) {
196 hid_err(hid, "Device data not found.");
197 return LED_OFF;
198 }
199
200 return (drv_data->led_state >> SRWS1_NUMBER_LEDS) ? LED_FULL : LED_OFF;
201}
202
203static void steelseries_srws1_led_set_brightness(struct led_classdev *led_cdev,
204 enum led_brightness value)
205{
206 struct device *dev = led_cdev->dev->parent;
207 struct hid_device *hid = to_hid_device(dev);
208 struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid);
209 int i, state = 0;
210
211 if (!drv_data) {
212 hid_err(hid, "Device data not found.");
213 return;
214 }
215
216 for (i = 0; i < SRWS1_NUMBER_LEDS; i++) {
217 if (led_cdev != drv_data->led[i])
218 continue;
219
220 state = (drv_data->led_state >> i) & 1;
221 if (value == LED_OFF && state) {
222 drv_data->led_state &= ~(1 << i);
223 steelseries_srws1_set_leds(hid, drv_data->led_state);
224 } else if (value != LED_OFF && !state) {
225 drv_data->led_state |= 1 << i;
226 steelseries_srws1_set_leds(hid, drv_data->led_state);
227 }
228 break;
229 }
230}
231
232static enum led_brightness steelseries_srws1_led_get_brightness(struct led_classdev *led_cdev)
233{
234 struct device *dev = led_cdev->dev->parent;
235 struct hid_device *hid = to_hid_device(dev);
236 struct steelseries_srws1_data *drv_data;
237 int i, value = 0;
238
239 drv_data = hid_get_drvdata(hid);
240
241 if (!drv_data) {
242 hid_err(hid, "Device data not found.");
243 return LED_OFF;
244 }
245
246 for (i = 0; i < SRWS1_NUMBER_LEDS; i++)
247 if (led_cdev == drv_data->led[i]) {
248 value = (drv_data->led_state >> i) & 1;
249 break;
250 }
251
252 return value ? LED_FULL : LED_OFF;
253}
254
255static int steelseries_srws1_probe(struct hid_device *hdev,
256 const struct hid_device_id *id)
257{
258 int ret, i;
259 struct led_classdev *led;
260 struct steelseries_srws1_data *drv_data;
261 size_t name_sz;
262 char *name;
263
264 drv_data = devm_kzalloc(&hdev->dev, sizeof(*drv_data), GFP_KERNEL);
265 if (drv_data == NULL) {
266 hid_err(hdev, "can't alloc SRW-S1 memory\n");
267 return -ENOMEM;
268 }
269
270 hid_set_drvdata(hdev, drv_data);
271
272 ret = hid_parse(hdev);
273 if (ret) {
274 hid_err(hdev, "parse failed\n");
275 goto err;
276 }
277
278 if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 16)) {
279 ret = -ENODEV;
280 goto err;
281 }
282
283 ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
284 if (ret) {
285 hid_err(hdev, "hw start failed\n");
286 goto err;
287 }
288
289 /* register led subsystem */
290 drv_data->led_state = 0;
291 for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++)
292 drv_data->led[i] = NULL;
293
294 steelseries_srws1_set_leds(hdev, 0);
295
296 name_sz = strlen(hdev->uniq) + 16;
297
298 /* 'ALL', for setting all LEDs simultaneously */
299 led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
300 if (!led) {
301 hid_err(hdev, "can't allocate memory for LED ALL\n");
302 goto out;
303 }
304
305 name = (void *)(&led[1]);
306 snprintf(name, name_sz, "SRWS1::%s::RPMALL", hdev->uniq);
307 led->name = name;
308 led->brightness = 0;
309 led->max_brightness = 1;
310 led->brightness_get = steelseries_srws1_led_all_get_brightness;
311 led->brightness_set = steelseries_srws1_led_all_set_brightness;
312
313 drv_data->led[SRWS1_NUMBER_LEDS] = led;
314 ret = devm_led_classdev_register(&hdev->dev, led);
315 if (ret) {
316 hid_err(hdev, "failed to register LED %d. Aborting.\n", SRWS1_NUMBER_LEDS);
317 goto out; /* let the driver continue without LEDs */
318 }
319
320 /* Each individual LED */
321 for (i = 0; i < SRWS1_NUMBER_LEDS; i++) {
322 led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
323 if (!led) {
324 hid_err(hdev, "can't allocate memory for LED %d\n", i);
325 break;
326 }
327
328 name = (void *)(&led[1]);
329 snprintf(name, name_sz, "SRWS1::%s::RPM%d", hdev->uniq, i+1);
330 led->name = name;
331 led->brightness = 0;
332 led->max_brightness = 1;
333 led->brightness_get = steelseries_srws1_led_get_brightness;
334 led->brightness_set = steelseries_srws1_led_set_brightness;
335
336 drv_data->led[i] = led;
337 ret = devm_led_classdev_register(&hdev->dev, led);
338
339 if (ret) {
340 hid_err(hdev, "failed to register LED %d. Aborting.\n", i);
341 break; /* but let the driver continue without LEDs */
342 }
343 }
344out:
345 return 0;
346err:
347 return ret;
348}
349#endif
350
351static const struct dmi_system_id steelseries_msi_rgb_dmi_table[] = {
352 {
353 .matches = {
354 DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
355 DMI_MATCH(DMI_PRODUCT_NAME, "Raider A18 HX A9WJG"),
356 DMI_MATCH(DMI_BOARD_NAME, "MS-182L"),
357 },
358 },
359 { }
360};
361
362static struct usb_interface *steelseries_hid_to_usb_intf(struct hid_device *hdev)
363{
364 if (!hid_is_usb(hdev))
365 return NULL;
366
367 return to_usb_interface(hdev->dev.parent);
368}
369
370static bool steelseries_msi_rgb_is_interface0(struct hid_device *hdev)
371{
372 struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
373 struct usb_device *udev;
374
375 if (!intf)
376 return false;
377
378 udev = interface_to_usbdev(intf);
379
380 return intf == usb_ifnum_to_if(udev, 0);
381}
382
383#if STEELSERIES_HAS_LEDS_MULTICOLOR
384
385static struct usb_device *steelseries_hid_to_usb_dev(struct hid_device *hdev)
386{
387 struct usb_interface *intf = steelseries_hid_to_usb_intf(hdev);
388
389 if (!intf)
390 return NULL;
391
392 return interface_to_usbdev(intf);
393}
394
395static int steelseries_msi_rgb_set_blocking(struct led_classdev *led_cdev,
396 enum led_brightness brightness)
397{
398 struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(led_cdev);
399 struct steelseries_device *sd = container_of(mc_cdev,
400 struct steelseries_device,
401 mc_cdev);
402 struct hid_device *hdev = sd->hdev;
403 struct usb_device *udev = steelseries_hid_to_usb_dev(hdev);
404 int i, ret;
405 u8 r, g, b;
406
407 static const u8 keys[] = {
408 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
409 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
410 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
411 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
412 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
413 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x33, 0x34,
414 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c,
415 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44,
416 0x45, 0x46, 0x47, 0x49, 0x4b, 0x4c, 0x4e, 0x4f,
417 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
418 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
419 0x60, 0x61, 0x62, 0x63, 0x64, 0x66, 0xe0, 0xe1,
420 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xf0
421 };
422 static const u8 alc_zones[] = { 0x00, 0x01, 0x02, 0x03 };
423
424 if (!udev)
425 return -ENODEV;
426
427 mutex_lock(&sd->rgb_lock);
428
429 led_mc_calc_color_components(mc_cdev, brightness);
430
431 r = mc_cdev->subled_info[0].brightness;
432 g = mc_cdev->subled_info[1].brightness;
433 b = mc_cdev->subled_info[2].brightness;
434
435 /*
436 * Report layout (524 bytes):
437 * Byte 0: Opcode (0x0c)
438 * Byte 1: 0x00
439 * Byte 2: Mode (0x66 for Keyboard, 0x06 for Lightbar)
440 * Byte 3: 0x00
441 * Bytes 4+: 4-byte chunks per LED (Index, R, G, B)
442 */
443 memset(sd->rgb_buf, 0, STEELSERIES_MSI_RGB_REPORT_LEN);
444 sd->rgb_buf[0] = STEELSERIES_MSI_RGB_OPCODE;
445 sd->rgb_buf[1] = 0x00;
446 sd->rgb_buf[3] = 0x00;
447
448 for (i = 0; i < (STEELSERIES_MSI_RGB_REPORT_LEN - 4) / 4; i++)
449 sd->rgb_buf[4 + i * 4] = 0xff;
450
451 if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC) {
452 sd->rgb_buf[2] = STEELSERIES_MSI_RGB_KLC_MODE;
453 for (i = 0; i < ARRAY_SIZE(keys); i++) {
454 sd->rgb_buf[4 + i * 4] = keys[i];
455 sd->rgb_buf[5 + i * 4] = r;
456 sd->rgb_buf[6 + i * 4] = g;
457 sd->rgb_buf[7 + i * 4] = b;
458 }
459 } else {
460 sd->rgb_buf[2] = STEELSERIES_MSI_RGB_ALC_MODE;
461 for (i = 0; i < ARRAY_SIZE(alc_zones); i++) {
462 sd->rgb_buf[4 + i * 4] = alc_zones[i];
463 sd->rgb_buf[5 + i * 4] = r;
464 sd->rgb_buf[6 + i * 4] = g;
465 sd->rgb_buf[7 + i * 4] = b;
466 }
467 }
468
469 /*
470 * Send the vendor report verbatim with usb_control_msg(): byte 0 is a
471 * protocol opcode (0x0c), not a HID report ID, and the controller
472 * expects it under report ID 0 (wValue 0x0300). hid_hw_raw_request()
473 * would write the report number into byte 0, so the direct control
474 * transfer is used to keep the payload byte-identical to the tested
475 * userspace implementation.
476 */
477 ret = hid_hw_power(hdev, PM_HINT_FULLON);
478 if (ret < 0)
479 goto out_unlock;
480
481 ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
482 HID_REQ_SET_REPORT,
483 USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
484 STEELSERIES_MSI_RGB_WVALUE, 0,
485 sd->rgb_buf, STEELSERIES_MSI_RGB_REPORT_LEN,
486 USB_CTRL_SET_TIMEOUT);
487
488 hid_hw_power(hdev, PM_HINT_NORMAL);
489
490out_unlock:
491 mutex_unlock(&sd->rgb_lock);
492 return ret < 0 ? ret : 0;
493}
494
495static void steelseries_msi_rgb_free_buf(void *data)
496{
497 kfree(data);
498}
499
500static int steelseries_msi_rgb_register(struct steelseries_device *sd)
501{
502 struct hid_device *hdev = sd->hdev;
503 struct led_classdev *led_cdev;
504 int ret;
505
506 sd->rgb_buf = kzalloc(STEELSERIES_MSI_RGB_REPORT_LEN, GFP_KERNEL);
507 if (!sd->rgb_buf)
508 return -ENOMEM;
509
510 ret = devm_add_action_or_reset(&hdev->dev,
511 steelseries_msi_rgb_free_buf,
512 sd->rgb_buf);
513 if (ret) {
514 sd->rgb_buf = NULL;
515 return ret;
516 }
517
518 ret = devm_mutex_init(&hdev->dev, &sd->rgb_lock);
519 if (ret) {
520 devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
521 sd->rgb_buf);
522 kfree(sd->rgb_buf);
523 sd->rgb_buf = NULL;
524 return ret;
525 }
526
527 sd->subled_info[0].color_index = LED_COLOR_ID_RED;
528 sd->subled_info[1].color_index = LED_COLOR_ID_GREEN;
529 sd->subled_info[2].color_index = LED_COLOR_ID_BLUE;
530 sd->subled_info[0].intensity = 255;
531 sd->subled_info[1].intensity = 255;
532 sd->subled_info[2].intensity = 255;
533 sd->subled_info[0].channel = 0;
534 sd->subled_info[1].channel = 1;
535 sd->subled_info[2].channel = 2;
536
537 sd->mc_cdev.subled_info = sd->subled_info;
538 sd->mc_cdev.num_colors = 3;
539
540 led_cdev = &sd->mc_cdev.led_cdev;
541 if (hdev->product == USB_DEVICE_ID_STEELSERIES_MSI_KLC)
542 led_cdev->name = "steelseries::kbd_backlight";
543 else
544 led_cdev->name = "steelseries::lightbar";
545
546 led_cdev->max_brightness = 255;
547 led_cdev->brightness_set_blocking = steelseries_msi_rgb_set_blocking;
548
549 ret = devm_led_classdev_multicolor_register(&hdev->dev, &sd->mc_cdev);
550 if (ret) {
551 devm_remove_action(&hdev->dev, steelseries_msi_rgb_free_buf,
552 sd->rgb_buf);
553 kfree(sd->rgb_buf);
554 sd->rgb_buf = NULL;
555 return ret;
556 }
557
558 return 0;
559}
560#else
561static int steelseries_msi_rgb_register(struct steelseries_device *sd)
562{
563 return -ENODEV;
564}
565#endif
566
567static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id)
568{
569 struct steelseries_device *sd;
570 int ret;
571
572 if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
573#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
574 (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
575 return steelseries_srws1_probe(hdev, id);
576#else
577 return -ENODEV;
578#endif
579 }
580
581 sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
582 if (!sd)
583 return -ENOMEM;
584 hid_set_drvdata(hdev, sd);
585 sd->hdev = hdev;
586 sd->quirks = id->driver_data;
587
588 if (sd->quirks & STEELSERIES_MSI_RGB) {
589 if (!dmi_check_system(steelseries_msi_rgb_dmi_table) ||
590 !steelseries_msi_rgb_is_interface0(hdev)) {
591 hid_dbg(hdev, "MSI RGB quirk not applicable, using generic HID path\n");
592 sd->quirks &= ~STEELSERIES_MSI_RGB;
593 }
594 }
595
596 ret = hid_parse(hdev);
597 if (ret)
598 return ret;
599
600 ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
601 if (ret)
602 return ret;
603
604 ret = hid_hw_open(hdev);
605 if (ret)
606 goto err_stop;
607
608 if (sd->quirks & STEELSERIES_MSI_RGB) {
609 ret = steelseries_msi_rgb_register(sd);
610 if (ret) {
611 hid_warn(hdev,
612 "Failed to register MSI RGB LEDs: %d, continuing without RGB support\n",
613 ret);
614 sd->quirks &= ~STEELSERIES_MSI_RGB;
615 }
616 }
617
618 return 0;
619
620err_stop:
621 hid_hw_stop(hdev);
622 return ret;
623}
624
625static void steelseries_remove(struct hid_device *hdev)
626{
627 if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
628#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
629 (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
630 hid_hw_stop(hdev);
631#endif
632 return;
633 }
634
635 hid_hw_close(hdev);
636 hid_hw_stop(hdev);
637}
638
639static const __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev,
640 __u8 *rdesc, unsigned int *rsize)
641{
642 if (hdev->vendor != USB_VENDOR_ID_STEELSERIES ||
643 hdev->product != USB_DEVICE_ID_STEELSERIES_SRWS1)
644 return rdesc;
645
646 if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8
647 && rdesc[29] == 0xbb && rdesc[40] == 0xc5) {
648 hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n");
649 *rsize = sizeof(steelseries_srws1_rdesc_fixed);
650 return steelseries_srws1_rdesc_fixed;
651 }
652 return rdesc;
653}
654
655static const struct hid_device_id steelseries_devices[] = {
656 { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1),
657 .driver_data = STEELSERIES_SRWS1 },
658
659#if STEELSERIES_HAS_LEDS_MULTICOLOR
660 { /* MSI Raider A18 KLC */
661 HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_KLC),
662 .driver_data = STEELSERIES_MSI_RGB },
663
664 { /* MSI Raider A18 ALC */
665 HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_MSI_ALC),
666 .driver_data = STEELSERIES_MSI_RGB },
667#endif
668
669 { }
670};
671MODULE_DEVICE_TABLE(hid, steelseries_devices);
672
673static struct hid_driver steelseries_driver = {
674 .name = "steelseries",
675 .id_table = steelseries_devices,
676 .probe = steelseries_probe,
677 .remove = steelseries_remove,
678 .report_fixup = steelseries_srws1_report_fixup,
679};
680
681module_hid_driver(steelseries_driver);
682MODULE_DESCRIPTION("HID driver for Steelseries devices");
683MODULE_LICENSE("GPL");
684MODULE_AUTHOR("Simon Wood <[email protected]>");