Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
75dbb953 | 2 | /* |
a0c76896 | 3 | * HID driver for Steelseries devices |
75dbb953 SW |
4 | * |
5 | * Copyright (c) 2013 Simon Wood | |
a0c76896 | 6 | * Copyright (c) 2023 Bastien Nocera |
75dbb953 SW |
7 | */ |
8 | ||
9 | /* | |
75dbb953 SW |
10 | */ |
11 | ||
12 | #include <linux/device.h> | |
13 | #include <linux/hid.h> | |
14 | #include <linux/module.h> | |
a0c76896 | 15 | #include <linux/usb.h> |
f28bb5ce | 16 | #include <linux/leds.h> |
75dbb953 SW |
17 | |
18 | #include "hid-ids.h" | |
19 | ||
a0c76896 BN |
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 | ||
b52b5061 SW |
37 | #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ |
38 | (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) | |
2e2daff3 SW |
39 | #define SRWS1_NUMBER_LEDS 15 |
40 | struct steelseries_srws1_data { | |
41 | __u16 led_state; | |
7e415762 JK |
42 | /* the last element is used for setting all leds simultaneously */ |
43 | struct led_classdev *led[SRWS1_NUMBER_LEDS + 1]; | |
2e2daff3 SW |
44 | }; |
45 | #endif | |
46 | ||
5492606d SW |
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 | ||
b52b5061 SW |
127 | #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ |
128 | (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) | |
2e2daff3 SW |
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 | ||
d8814272 | 152 | hid_hw_request(hdev, report, HID_REQ_SET_REPORT); |
2e2daff3 SW |
153 | |
154 | /* Note: LED change does not show on device until the device is read/polled */ | |
155 | } | |
156 | ||
e25d7805 SW |
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; | |
ee79a8f8 | 161 | struct hid_device *hid = to_hid_device(dev); |
e25d7805 SW |
162 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(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(hid, 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; | |
ee79a8f8 | 180 | struct hid_device *hid = to_hid_device(dev); |
e25d7805 SW |
181 | struct steelseries_srws1_data *drv_data; |
182 | ||
183 | drv_data = hid_get_drvdata(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 | ||
2e2daff3 SW |
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; | |
ee79a8f8 | 197 | struct hid_device *hid = to_hid_device(dev); |
2e2daff3 SW |
198 | struct steelseries_srws1_data *drv_data = hid_get_drvdata(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(hid, drv_data->led_state); | |
214 | } else if (value != LED_OFF && !state) { | |
215 | drv_data->led_state |= 1 << i; | |
216 | steelseries_srws1_set_leds(hid, 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; | |
ee79a8f8 | 225 | struct hid_device *hid = to_hid_device(dev); |
2e2daff3 SW |
226 | struct steelseries_srws1_data *drv_data; |
227 | int i, value = 0; | |
228 | ||
229 | drv_data = hid_get_drvdata(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(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, drv_data); | |
261 | ||
262 | ret = hid_parse(hdev); | |
263 | if (ret) { | |
264 | hid_err(hdev, "parse failed\n"); | |
265 | goto err_free; | |
266 | } | |
267 | ||
41df7f6d KC |
268 | if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 16)) { |
269 | ret = -ENODEV; | |
270 | goto err_free; | |
271 | } | |
272 | ||
2e2daff3 SW |
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; | |
e25d7805 | 281 | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) |
2e2daff3 SW |
282 | drv_data->led[i] = NULL; |
283 | ||
284 | steelseries_srws1_set_leds(hdev, 0); | |
285 | ||
e25d7805 SW |
286 | name_sz = strlen(hdev->uniq) + 16; |
287 | ||
288 | /* 'ALL', for setting all LEDs simultaneously */ | |
289 | led = kzalloc(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(name, name_sz, "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(&hdev->dev, led); | |
305 | if (ret) | |
306 | goto err_led; | |
2e2daff3 | 307 | |
e25d7805 | 308 | /* Each individual LED */ |
2e2daff3 SW |
309 | for (i = 0; i < SRWS1_NUMBER_LEDS; i++) { |
310 | led = kzalloc(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(name, name_sz, "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(&hdev->dev, 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) */ | |
e25d7805 | 331 | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { |
2e2daff3 SW |
332 | led = drv_data->led[i]; |
333 | drv_data->led[i] = NULL; | |
334 | if (!led) | |
335 | continue; | |
336 | led_classdev_unregister(led); | |
337 | kfree(led); | |
338 | } | |
339 | goto out; /* but let the driver continue without LEDs */ | |
340 | } | |
341 | } | |
342 | out: | |
343 | return 0; | |
344 | err_free: | |
345 | kfree(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) */ | |
e25d7805 | 358 | for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) { |
2e2daff3 SW |
359 | led = drv_data->led[i]; |
360 | drv_data->led[i] = NULL; | |
361 | if (!led) | |
362 | continue; | |
363 | led_classdev_unregister(led); | |
364 | kfree(led); | |
365 | } | |
366 | ||
367 | } | |
368 | ||
369 | hid_hw_stop(hdev); | |
370 | kfree(drv_data); | |
371 | return; | |
372 | } | |
373 | #endif | |
374 | ||
a0c76896 BN |
375 | #define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000 |
376 | ||
377 | #define ARCTIS_1_BATTERY_RESPONSE_LEN 8 | |
e90e7c28 | 378 | static const char arctis_1_battery_request[] = { 0x06, 0x12 }; |
a0c76896 BN |
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(arctis_1_battery_request, sizeof(arctis_1_battery_request), GFP_KERNEL); | |
387 | if (!write_buf) | |
388 | return -ENOMEM; | |
389 | ||
390 | ret = hid_hw_raw_request(hdev, arctis_1_battery_request[0], | |
391 | write_buf, sizeof(arctis_1_battery_request), | |
392 | HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); | |
6c667ef6 | 393 | if (ret < (int)sizeof(arctis_1_battery_request)) { |
a0c76896 BN |
394 | hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret); |
395 | ret = -ENODATA; | |
396 | } | |
397 | kfree(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(intf, 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(&battery_no) - 1; | |
487 | sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL, | |
488 | "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(sd->hdev, false); | |
494 | sd->battery_capacity = 100; | |
495 | ||
496 | sd->battery = devm_power_supply_register(&sd->hdev->dev, | |
497 | &sd->battery_desc, &battery_cfg); | |
498 | if (IS_ERR(sd->battery)) { | |
499 | ret = PTR_ERR(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(sd->battery, &sd->hdev->dev); | |
506 | ||
507 | INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick); | |
508 | steelseries_headset_fetch_battery(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(&hdev->dev, sizeof(*sd), GFP_KERNEL); | |
519 | if (!sd) | |
520 | return -ENOMEM; | |
521 | hid_set_drvdata(hdev, 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(&sd->lock, flags); | |
567 | ||
568 | cancel_delayed_work_sync(&sd->battery_work); | |
569 | ||
570 | hid_hw_stop(hdev); | |
571 | } | |
572 | ||
75dbb953 SW |
573 | static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc, |
574 | unsigned int *rsize) | |
575 | { | |
a0c76896 BN |
576 | if (hdev->vendor != USB_VENDOR_ID_STEELSERIES || |
577 | hdev->product != USB_DEVICE_ID_STEELSERIES_SRWS1) | |
578 | return rdesc; | |
579 | ||
75dbb953 SW |
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"); | |
5492606d SW |
583 | rdesc = steelseries_srws1_rdesc_fixed; |
584 | *rsize = sizeof(steelseries_srws1_rdesc_fixed); | |
75dbb953 SW |
585 | } |
586 | return rdesc; | |
587 | } | |
588 | ||
a0c76896 BN |
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 (read_buf, arctis_1_battery_request, 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(sd->battery); | |
632 | } | |
633 | ||
634 | spin_lock_irqsave(&sd->lock, flags); | |
635 | if (!sd->removed) | |
636 | schedule_delayed_work(&sd->battery_work, | |
637 | msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS)); | |
638 | spin_unlock_irqrestore(&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 | ||
75dbb953 SW |
651 | { } |
652 | }; | |
a0c76896 BN |
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, | |
75dbb953 SW |
662 | }; |
663 | ||
a0c76896 | 664 | module_hid_driver(steelseries_driver); |
75dbb953 | 665 | MODULE_LICENSE("GPL"); |
a0c76896 BN |
666 | MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>"); |
667 | MODULE_AUTHOR("Simon Wood <simon@mungewell.org>"); |