1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * HID driver for Steelseries devices |
4 | * |
5 | * Copyright (c) 2013 Simon Wood |
6 | * Copyright (c) 2023 Bastien Nocera |
7 | */ |
8 | |
9 | /* |
10 | */ |
11 | |
12 | #include <linux/device.h> |
13 | #include <linux/hid.h> |
14 | #include <linux/module.h> |
15 | #include <linux/usb.h> |
16 | #include <linux/leds.h> |
17 | |
18 | #include "hid-ids.h" |
19 | |
20 | #define STEELSERIES_SRWS1 BIT(0) |
21 | #define STEELSERIES_ARCTIS_1 BIT(1) |
22 | |
23 | struct steelseries_device { |
24 | struct hid_device *hdev; |
25 | unsigned long quirks; |
26 | |
27 | struct delayed_work battery_work; |
28 | spinlock_t lock; |
29 | bool removed; |
30 | |
31 | struct power_supply_desc battery_desc; |
32 | struct power_supply *battery; |
33 | uint8_t battery_capacity; |
34 | bool headset_connected; |
35 | }; |
36 | |
37 | #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ |
38 | (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) |
39 | #define SRWS1_NUMBER_LEDS 15 |
40 | struct steelseries_srws1_data { |
41 | __u16 led_state; |
42 | /* the last element is used for setting all leds simultaneously */ |
43 | struct led_classdev *led[SRWS1_NUMBER_LEDS + 1]; |
44 | }; |
45 | #endif |
46 | |
47 | /* Fixed report descriptor for Steelseries SRW-S1 wheel controller |
48 | * |
49 | * The original descriptor hides the sensitivity and assists dials |
50 | * a custom vendor usage page. This inserts a patch to make them |
51 | * appear in the 'Generic Desktop' usage. |
52 | */ |
53 | |
54 | static __u8 steelseries_srws1_rdesc_fixed[] = { |
55 | 0x05, 0x01, /* Usage Page (Desktop) */ |
56 | 0x09, 0x08, /* Usage (MultiAxis), Changed */ |
57 | 0xA1, 0x01, /* Collection (Application), */ |
58 | 0xA1, 0x02, /* Collection (Logical), */ |
59 | 0x95, 0x01, /* Report Count (1), */ |
60 | 0x05, 0x01, /* Changed Usage Page (Desktop), */ |
61 | 0x09, 0x30, /* Changed Usage (X), */ |
62 | 0x16, 0xF8, 0xF8, /* Logical Minimum (-1800), */ |
63 | 0x26, 0x08, 0x07, /* Logical Maximum (1800), */ |
64 | 0x65, 0x14, /* Unit (Degrees), */ |
65 | 0x55, 0x0F, /* Unit Exponent (15), */ |
66 | 0x75, 0x10, /* Report Size (16), */ |
67 | 0x81, 0x02, /* Input (Variable), */ |
68 | 0x09, 0x31, /* Changed Usage (Y), */ |
69 | 0x15, 0x00, /* Logical Minimum (0), */ |
70 | 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ |
71 | 0x75, 0x0C, /* Report Size (12), */ |
72 | 0x81, 0x02, /* Input (Variable), */ |
73 | 0x09, 0x32, /* Changed Usage (Z), */ |
74 | 0x15, 0x00, /* Logical Minimum (0), */ |
75 | 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ |
76 | 0x75, 0x0C, /* Report Size (12), */ |
77 | 0x81, 0x02, /* Input (Variable), */ |
78 | 0x05, 0x01, /* Usage Page (Desktop), */ |
79 | 0x09, 0x39, /* Usage (Hat Switch), */ |
80 | 0x25, 0x07, /* Logical Maximum (7), */ |
81 | 0x35, 0x00, /* Physical Minimum (0), */ |
82 | 0x46, 0x3B, 0x01, /* Physical Maximum (315), */ |
83 | 0x65, 0x14, /* Unit (Degrees), */ |
84 | 0x75, 0x04, /* Report Size (4), */ |
85 | 0x95, 0x01, /* Report Count (1), */ |
86 | 0x81, 0x02, /* Input (Variable), */ |
87 | 0x25, 0x01, /* Logical Maximum (1), */ |
88 | 0x45, 0x01, /* Physical Maximum (1), */ |
89 | 0x65, 0x00, /* Unit, */ |
90 | 0x75, 0x01, /* Report Size (1), */ |
91 | 0x95, 0x03, /* Report Count (3), */ |
92 | 0x81, 0x01, /* Input (Constant), */ |
93 | 0x05, 0x09, /* Usage Page (Button), */ |
94 | 0x19, 0x01, /* Usage Minimum (01h), */ |
95 | 0x29, 0x11, /* Usage Maximum (11h), */ |
96 | 0x95, 0x11, /* Report Count (17), */ |
97 | 0x81, 0x02, /* Input (Variable), */ |
98 | /* ---- Dial patch starts here ---- */ |
99 | 0x05, 0x01, /* Usage Page (Desktop), */ |
100 | 0x09, 0x33, /* Usage (RX), */ |
101 | 0x75, 0x04, /* Report Size (4), */ |
102 | 0x95, 0x02, /* Report Count (2), */ |
103 | 0x15, 0x00, /* Logical Minimum (0), */ |
104 | 0x25, 0x0b, /* Logical Maximum (b), */ |
105 | 0x81, 0x02, /* Input (Variable), */ |
106 | 0x09, 0x35, /* Usage (RZ), */ |
107 | 0x75, 0x04, /* Report Size (4), */ |
108 | 0x95, 0x01, /* Report Count (1), */ |
109 | 0x25, 0x03, /* Logical Maximum (3), */ |
110 | 0x81, 0x02, /* Input (Variable), */ |
111 | /* ---- Dial patch ends here ---- */ |
112 | 0x06, 0x00, 0xFF, /* Usage Page (FF00h), */ |
113 | 0x09, 0x01, /* Usage (01h), */ |
114 | 0x75, 0x04, /* Changed Report Size (4), */ |
115 | 0x95, 0x0D, /* Changed Report Count (13), */ |
116 | 0x81, 0x02, /* Input (Variable), */ |
117 | 0xC0, /* End Collection, */ |
118 | 0xA1, 0x02, /* Collection (Logical), */ |
119 | 0x09, 0x02, /* Usage (02h), */ |
120 | 0x75, 0x08, /* Report Size (8), */ |
121 | 0x95, 0x10, /* Report Count (16), */ |
122 | 0x91, 0x02, /* Output (Variable), */ |
123 | 0xC0, /* End Collection, */ |
124 | 0xC0 /* End Collection */ |
125 | }; |
126 | |
127 | #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ |
128 | (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) |
129 | static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds) |
130 | { |
131 | struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list; |
132 | struct hid_report *report = list_entry(report_list->next, struct hid_report, list); |
133 | __s32 *value = report->field[0]->value; |
134 | |
135 | value[0] = 0x40; |
136 | value[1] = leds & 0xFF; |
137 | value[2] = leds >> 8; |
138 | value[3] = 0x00; |
139 | value[4] = 0x00; |
140 | value[5] = 0x00; |
141 | value[6] = 0x00; |
142 | value[7] = 0x00; |
143 | value[8] = 0x00; |
144 | value[9] = 0x00; |
145 | value[10] = 0x00; |
146 | value[11] = 0x00; |
147 | value[12] = 0x00; |
148 | value[13] = 0x00; |
149 | value[14] = 0x00; |
150 | value[15] = 0x00; |
151 | |
152 | hid_hw_request(hdev, report, reqtype: HID_REQ_SET_REPORT); |
153 | |
154 | /* Note: LED change does not show on device until the device is read/polled */ |
155 | } |
156 | |
157 | static void steelseries_srws1_led_all_set_brightness(struct led_classdev *led_cdev, |
158 | enum led_brightness value) |
159 | { |
160 | struct device *dev = led_cdev->dev->parent; |
161 | struct hid_device *hid = to_hid_device(dev); |
162 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev: hid); |
163 | |
164 | if (!drv_data) { |
165 | hid_err(hid, "Device data not found." ); |
166 | return; |
167 | } |
168 | |
169 | if (value == LED_OFF) |
170 | drv_data->led_state = 0; |
171 | else |
172 | drv_data->led_state = (1 << (SRWS1_NUMBER_LEDS + 1)) - 1; |
173 | |
174 | steelseries_srws1_set_leds(hdev: hid, leds: drv_data->led_state); |
175 | } |
176 | |
177 | static enum led_brightness steelseries_srws1_led_all_get_brightness(struct led_classdev *led_cdev) |
178 | { |
179 | struct device *dev = led_cdev->dev->parent; |
180 | struct hid_device *hid = to_hid_device(dev); |
181 | struct steelseries_srws1_data *drv_data; |
182 | |
183 | drv_data = hid_get_drvdata(hdev: hid); |
184 | |
185 | if (!drv_data) { |
186 | hid_err(hid, "Device data not found." ); |
187 | return LED_OFF; |
188 | } |
189 | |
190 | return (drv_data->led_state >> SRWS1_NUMBER_LEDS) ? LED_FULL : LED_OFF; |
191 | } |
192 | |
193 | static void steelseries_srws1_led_set_brightness(struct led_classdev *led_cdev, |
194 | enum led_brightness value) |
195 | { |
196 | struct device *dev = led_cdev->dev->parent; |
197 | struct hid_device *hid = to_hid_device(dev); |
198 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev: hid); |
199 | int i, state = 0; |
200 | |
201 | if (!drv_data) { |
202 | hid_err(hid, "Device data not found." ); |
203 | return; |
204 | } |
205 | |
206 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { |
207 | if (led_cdev != drv_data->led[i]) |
208 | continue; |
209 | |
210 | state = (drv_data->led_state >> i) & 1; |
211 | if (value == LED_OFF && state) { |
212 | drv_data->led_state &= ~(1 << i); |
213 | steelseries_srws1_set_leds(hdev: hid, leds: drv_data->led_state); |
214 | } else if (value != LED_OFF && !state) { |
215 | drv_data->led_state |= 1 << i; |
216 | steelseries_srws1_set_leds(hdev: hid, leds: drv_data->led_state); |
217 | } |
218 | break; |
219 | } |
220 | } |
221 | |
222 | static enum led_brightness steelseries_srws1_led_get_brightness(struct led_classdev *led_cdev) |
223 | { |
224 | struct device *dev = led_cdev->dev->parent; |
225 | struct hid_device *hid = to_hid_device(dev); |
226 | struct steelseries_srws1_data *drv_data; |
227 | int i, value = 0; |
228 | |
229 | drv_data = hid_get_drvdata(hdev: hid); |
230 | |
231 | if (!drv_data) { |
232 | hid_err(hid, "Device data not found." ); |
233 | return LED_OFF; |
234 | } |
235 | |
236 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) |
237 | if (led_cdev == drv_data->led[i]) { |
238 | value = (drv_data->led_state >> i) & 1; |
239 | break; |
240 | } |
241 | |
242 | return value ? LED_FULL : LED_OFF; |
243 | } |
244 | |
245 | static int steelseries_srws1_probe(struct hid_device *hdev, |
246 | const struct hid_device_id *id) |
247 | { |
248 | int ret, i; |
249 | struct led_classdev *led; |
250 | size_t name_sz; |
251 | char *name; |
252 | |
253 | struct steelseries_srws1_data *drv_data = kzalloc(size: sizeof(*drv_data), GFP_KERNEL); |
254 | |
255 | if (drv_data == NULL) { |
256 | hid_err(hdev, "can't alloc SRW-S1 memory\n" ); |
257 | return -ENOMEM; |
258 | } |
259 | |
260 | hid_set_drvdata(hdev, data: drv_data); |
261 | |
262 | ret = hid_parse(hdev); |
263 | if (ret) { |
264 | hid_err(hdev, "parse failed\n" ); |
265 | goto err_free; |
266 | } |
267 | |
268 | if (!hid_validate_values(hid: hdev, type: HID_OUTPUT_REPORT, id: 0, field_index: 0, report_counts: 16)) { |
269 | ret = -ENODEV; |
270 | goto err_free; |
271 | } |
272 | |
273 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); |
274 | if (ret) { |
275 | hid_err(hdev, "hw start failed\n" ); |
276 | goto err_free; |
277 | } |
278 | |
279 | /* register led subsystem */ |
280 | drv_data->led_state = 0; |
281 | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) |
282 | drv_data->led[i] = NULL; |
283 | |
284 | steelseries_srws1_set_leds(hdev, leds: 0); |
285 | |
286 | name_sz = strlen(hdev->uniq) + 16; |
287 | |
288 | /* 'ALL', for setting all LEDs simultaneously */ |
289 | led = kzalloc(size: sizeof(struct led_classdev)+name_sz, GFP_KERNEL); |
290 | if (!led) { |
291 | hid_err(hdev, "can't allocate memory for LED ALL\n" ); |
292 | goto err_led; |
293 | } |
294 | |
295 | name = (void *)(&led[1]); |
296 | snprintf(buf: name, size: name_sz, fmt: "SRWS1::%s::RPMALL" , hdev->uniq); |
297 | led->name = name; |
298 | led->brightness = 0; |
299 | led->max_brightness = 1; |
300 | led->brightness_get = steelseries_srws1_led_all_get_brightness; |
301 | led->brightness_set = steelseries_srws1_led_all_set_brightness; |
302 | |
303 | drv_data->led[SRWS1_NUMBER_LEDS] = led; |
304 | ret = led_classdev_register(parent: &hdev->dev, led_cdev: led); |
305 | if (ret) |
306 | goto err_led; |
307 | |
308 | /* Each individual LED */ |
309 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { |
310 | led = kzalloc(size: sizeof(struct led_classdev)+name_sz, GFP_KERNEL); |
311 | if (!led) { |
312 | hid_err(hdev, "can't allocate memory for LED %d\n" , i); |
313 | goto err_led; |
314 | } |
315 | |
316 | name = (void *)(&led[1]); |
317 | snprintf(buf: name, size: name_sz, fmt: "SRWS1::%s::RPM%d" , hdev->uniq, i+1); |
318 | led->name = name; |
319 | led->brightness = 0; |
320 | led->max_brightness = 1; |
321 | led->brightness_get = steelseries_srws1_led_get_brightness; |
322 | led->brightness_set = steelseries_srws1_led_set_brightness; |
323 | |
324 | drv_data->led[i] = led; |
325 | ret = led_classdev_register(parent: &hdev->dev, led_cdev: led); |
326 | |
327 | if (ret) { |
328 | hid_err(hdev, "failed to register LED %d. Aborting.\n" , i); |
329 | err_led: |
330 | /* Deregister all LEDs (if any) */ |
331 | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { |
332 | led = drv_data->led[i]; |
333 | drv_data->led[i] = NULL; |
334 | if (!led) |
335 | continue; |
336 | led_classdev_unregister(led_cdev: led); |
337 | kfree(objp: led); |
338 | } |
339 | goto out; /* but let the driver continue without LEDs */ |
340 | } |
341 | } |
342 | out: |
343 | return 0; |
344 | err_free: |
345 | kfree(objp: drv_data); |
346 | return ret; |
347 | } |
348 | |
349 | static void steelseries_srws1_remove(struct hid_device *hdev) |
350 | { |
351 | int i; |
352 | struct led_classdev *led; |
353 | |
354 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev); |
355 | |
356 | if (drv_data) { |
357 | /* Deregister LEDs (if any) */ |
358 | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { |
359 | led = drv_data->led[i]; |
360 | drv_data->led[i] = NULL; |
361 | if (!led) |
362 | continue; |
363 | led_classdev_unregister(led_cdev: led); |
364 | kfree(objp: led); |
365 | } |
366 | |
367 | } |
368 | |
369 | hid_hw_stop(hdev); |
370 | kfree(objp: drv_data); |
371 | return; |
372 | } |
373 | #endif |
374 | |
375 | #define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000 |
376 | |
377 | #define ARCTIS_1_BATTERY_RESPONSE_LEN 8 |
378 | static const char arctis_1_battery_request[] = { 0x06, 0x12 }; |
379 | |
380 | static int steelseries_headset_arctis_1_fetch_battery(struct hid_device *hdev) |
381 | { |
382 | u8 *write_buf; |
383 | int ret; |
384 | |
385 | /* Request battery information */ |
386 | write_buf = kmemdup(p: arctis_1_battery_request, size: sizeof(arctis_1_battery_request), GFP_KERNEL); |
387 | if (!write_buf) |
388 | return -ENOMEM; |
389 | |
390 | ret = hid_hw_raw_request(hdev, reportnum: arctis_1_battery_request[0], |
391 | buf: write_buf, len: sizeof(arctis_1_battery_request), |
392 | rtype: HID_OUTPUT_REPORT, reqtype: HID_REQ_SET_REPORT); |
393 | if (ret < (int)sizeof(arctis_1_battery_request)) { |
394 | hid_err(hdev, "hid_hw_raw_request() failed with %d\n" , ret); |
395 | ret = -ENODATA; |
396 | } |
397 | kfree(objp: write_buf); |
398 | return ret; |
399 | } |
400 | |
401 | static void steelseries_headset_fetch_battery(struct hid_device *hdev) |
402 | { |
403 | struct steelseries_device *sd = hid_get_drvdata(hdev); |
404 | int ret = 0; |
405 | |
406 | if (sd->quirks & STEELSERIES_ARCTIS_1) |
407 | ret = steelseries_headset_arctis_1_fetch_battery(hdev); |
408 | |
409 | if (ret < 0) |
410 | hid_dbg(hdev, |
411 | "Battery query failed (err: %d)\n" , ret); |
412 | } |
413 | |
414 | static void steelseries_headset_battery_timer_tick(struct work_struct *work) |
415 | { |
416 | struct steelseries_device *sd = container_of(work, |
417 | struct steelseries_device, battery_work.work); |
418 | struct hid_device *hdev = sd->hdev; |
419 | |
420 | steelseries_headset_fetch_battery(hdev); |
421 | } |
422 | |
423 | static int steelseries_headset_battery_get_property(struct power_supply *psy, |
424 | enum power_supply_property psp, |
425 | union power_supply_propval *val) |
426 | { |
427 | struct steelseries_device *sd = power_supply_get_drvdata(psy); |
428 | int ret = 0; |
429 | |
430 | switch (psp) { |
431 | case POWER_SUPPLY_PROP_PRESENT: |
432 | val->intval = 1; |
433 | break; |
434 | case POWER_SUPPLY_PROP_STATUS: |
435 | val->intval = sd->headset_connected ? |
436 | POWER_SUPPLY_STATUS_DISCHARGING : |
437 | POWER_SUPPLY_STATUS_UNKNOWN; |
438 | break; |
439 | case POWER_SUPPLY_PROP_SCOPE: |
440 | val->intval = POWER_SUPPLY_SCOPE_DEVICE; |
441 | break; |
442 | case POWER_SUPPLY_PROP_CAPACITY: |
443 | val->intval = sd->battery_capacity; |
444 | break; |
445 | default: |
446 | ret = -EINVAL; |
447 | break; |
448 | } |
449 | return ret; |
450 | } |
451 | |
452 | static void |
453 | steelseries_headset_set_wireless_status(struct hid_device *hdev, |
454 | bool connected) |
455 | { |
456 | struct usb_interface *intf; |
457 | |
458 | if (!hid_is_usb(hdev)) |
459 | return; |
460 | |
461 | intf = to_usb_interface(hdev->dev.parent); |
462 | usb_set_wireless_status(iface: intf, status: connected ? |
463 | USB_WIRELESS_STATUS_CONNECTED : |
464 | USB_WIRELESS_STATUS_DISCONNECTED); |
465 | } |
466 | |
467 | static enum power_supply_property steelseries_headset_battery_props[] = { |
468 | POWER_SUPPLY_PROP_PRESENT, |
469 | POWER_SUPPLY_PROP_STATUS, |
470 | POWER_SUPPLY_PROP_SCOPE, |
471 | POWER_SUPPLY_PROP_CAPACITY, |
472 | }; |
473 | |
474 | static int steelseries_headset_battery_register(struct steelseries_device *sd) |
475 | { |
476 | static atomic_t battery_no = ATOMIC_INIT(0); |
477 | struct power_supply_config battery_cfg = { .drv_data = sd, }; |
478 | unsigned long n; |
479 | int ret; |
480 | |
481 | sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY; |
482 | sd->battery_desc.properties = steelseries_headset_battery_props; |
483 | sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_headset_battery_props); |
484 | sd->battery_desc.get_property = steelseries_headset_battery_get_property; |
485 | sd->battery_desc.use_for_apm = 0; |
486 | n = atomic_inc_return(v: &battery_no) - 1; |
487 | sd->battery_desc.name = devm_kasprintf(dev: &sd->hdev->dev, GFP_KERNEL, |
488 | fmt: "steelseries_headset_battery_%ld" , n); |
489 | if (!sd->battery_desc.name) |
490 | return -ENOMEM; |
491 | |
492 | /* avoid the warning of 0% battery while waiting for the first info */ |
493 | steelseries_headset_set_wireless_status(hdev: sd->hdev, connected: false); |
494 | sd->battery_capacity = 100; |
495 | |
496 | sd->battery = devm_power_supply_register(parent: &sd->hdev->dev, |
497 | desc: &sd->battery_desc, cfg: &battery_cfg); |
498 | if (IS_ERR(ptr: sd->battery)) { |
499 | ret = PTR_ERR(ptr: sd->battery); |
500 | hid_err(sd->hdev, |
501 | "%s:power_supply_register failed with error %d\n" , |
502 | __func__, ret); |
503 | return ret; |
504 | } |
505 | power_supply_powers(psy: sd->battery, dev: &sd->hdev->dev); |
506 | |
507 | INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick); |
508 | steelseries_headset_fetch_battery(hdev: sd->hdev); |
509 | |
510 | return 0; |
511 | } |
512 | |
513 | static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id) |
514 | { |
515 | struct steelseries_device *sd; |
516 | int ret; |
517 | |
518 | sd = devm_kzalloc(dev: &hdev->dev, size: sizeof(*sd), GFP_KERNEL); |
519 | if (!sd) |
520 | return -ENOMEM; |
521 | hid_set_drvdata(hdev, data: sd); |
522 | sd->hdev = hdev; |
523 | sd->quirks = id->driver_data; |
524 | |
525 | if (sd->quirks & STEELSERIES_SRWS1) { |
526 | #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ |
527 | (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) |
528 | return steelseries_srws1_probe(hdev, id); |
529 | #else |
530 | return -ENODEV; |
531 | #endif |
532 | } |
533 | |
534 | ret = hid_parse(hdev); |
535 | if (ret) |
536 | return ret; |
537 | |
538 | spin_lock_init(&sd->lock); |
539 | |
540 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); |
541 | if (ret) |
542 | return ret; |
543 | |
544 | if (steelseries_headset_battery_register(sd) < 0) |
545 | hid_err(sd->hdev, |
546 | "Failed to register battery for headset\n" ); |
547 | |
548 | return ret; |
549 | } |
550 | |
551 | static void steelseries_remove(struct hid_device *hdev) |
552 | { |
553 | struct steelseries_device *sd = hid_get_drvdata(hdev); |
554 | unsigned long flags; |
555 | |
556 | if (sd->quirks & STEELSERIES_SRWS1) { |
557 | #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ |
558 | (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) |
559 | steelseries_srws1_remove(hdev); |
560 | #endif |
561 | return; |
562 | } |
563 | |
564 | spin_lock_irqsave(&sd->lock, flags); |
565 | sd->removed = true; |
566 | spin_unlock_irqrestore(lock: &sd->lock, flags); |
567 | |
568 | cancel_delayed_work_sync(dwork: &sd->battery_work); |
569 | |
570 | hid_hw_stop(hdev); |
571 | } |
572 | |
573 | static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc, |
574 | unsigned int *rsize) |
575 | { |
576 | if (hdev->vendor != USB_VENDOR_ID_STEELSERIES || |
577 | hdev->product != USB_DEVICE_ID_STEELSERIES_SRWS1) |
578 | return rdesc; |
579 | |
580 | if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8 |
581 | && rdesc[29] == 0xbb && rdesc[40] == 0xc5) { |
582 | hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n" ); |
583 | rdesc = steelseries_srws1_rdesc_fixed; |
584 | *rsize = sizeof(steelseries_srws1_rdesc_fixed); |
585 | } |
586 | return rdesc; |
587 | } |
588 | |
589 | static int steelseries_headset_raw_event(struct hid_device *hdev, |
590 | struct hid_report *report, u8 *read_buf, |
591 | int size) |
592 | { |
593 | struct steelseries_device *sd = hid_get_drvdata(hdev); |
594 | int capacity = sd->battery_capacity; |
595 | bool connected = sd->headset_connected; |
596 | unsigned long flags; |
597 | |
598 | /* Not a headset */ |
599 | if (sd->quirks & STEELSERIES_SRWS1) |
600 | return 0; |
601 | |
602 | if (sd->quirks & STEELSERIES_ARCTIS_1) { |
603 | hid_dbg(sd->hdev, |
604 | "Parsing raw event for Arctis 1 headset (%*ph)\n" , size, read_buf); |
605 | if (size < ARCTIS_1_BATTERY_RESPONSE_LEN || |
606 | memcmp (p: read_buf, q: arctis_1_battery_request, size: sizeof(arctis_1_battery_request))) |
607 | return 0; |
608 | if (read_buf[2] == 0x01) { |
609 | connected = false; |
610 | capacity = 100; |
611 | } else { |
612 | connected = true; |
613 | capacity = read_buf[3]; |
614 | } |
615 | } |
616 | |
617 | if (connected != sd->headset_connected) { |
618 | hid_dbg(sd->hdev, |
619 | "Connected status changed from %sconnected to %sconnected\n" , |
620 | sd->headset_connected ? "" : "not " , |
621 | connected ? "" : "not " ); |
622 | sd->headset_connected = connected; |
623 | steelseries_headset_set_wireless_status(hdev, connected); |
624 | } |
625 | |
626 | if (capacity != sd->battery_capacity) { |
627 | hid_dbg(sd->hdev, |
628 | "Battery capacity changed from %d%% to %d%%\n" , |
629 | sd->battery_capacity, capacity); |
630 | sd->battery_capacity = capacity; |
631 | power_supply_changed(psy: sd->battery); |
632 | } |
633 | |
634 | spin_lock_irqsave(&sd->lock, flags); |
635 | if (!sd->removed) |
636 | schedule_delayed_work(dwork: &sd->battery_work, |
637 | delay: msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS)); |
638 | spin_unlock_irqrestore(lock: &sd->lock, flags); |
639 | |
640 | return 0; |
641 | } |
642 | |
643 | static const struct hid_device_id steelseries_devices[] = { |
644 | { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1), |
645 | .driver_data = STEELSERIES_SRWS1 }, |
646 | |
647 | { /* SteelSeries Arctis 1 Wireless for XBox */ |
648 | HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 0x12b6), |
649 | .driver_data = STEELSERIES_ARCTIS_1 }, |
650 | |
651 | { } |
652 | }; |
653 | MODULE_DEVICE_TABLE(hid, steelseries_devices); |
654 | |
655 | static struct hid_driver steelseries_driver = { |
656 | .name = "steelseries" , |
657 | .id_table = steelseries_devices, |
658 | .probe = steelseries_probe, |
659 | .remove = steelseries_remove, |
660 | .report_fixup = steelseries_srws1_report_fixup, |
661 | .raw_event = steelseries_headset_raw_event, |
662 | }; |
663 | |
664 | module_hid_driver(steelseries_driver); |
665 | MODULE_LICENSE("GPL" ); |
666 | MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>" ); |
667 | MODULE_AUTHOR("Simon Wood <simon@mungewell.org>" ); |
668 | |