1 | // SPDX-License-Identifier: GPL-2.0-or-later |
---|---|
2 | /* |
3 | * HID driver for Corsair Void headsets |
4 | * |
5 | * Copyright (C) 2023-2024 Stuart Hayhurst |
6 | */ |
7 | |
8 | /* -------------------------------------------------------------------------- */ |
9 | /* Receiver report information: (ID 100) */ |
10 | /* -------------------------------------------------------------------------- */ |
11 | /* |
12 | * When queried, the receiver reponds with 5 bytes to describe the battery |
13 | * The power button, mute button and moving the mic also trigger this report |
14 | * This includes power button + mic + connection + battery status and capacity |
15 | * The information below may not be perfect, it's been gathered through guesses |
16 | * |
17 | * 0: REPORT ID |
18 | * 100 for the battery packet |
19 | * |
20 | * 1: POWER BUTTON + (?) |
21 | * Largest bit is 1 when power button pressed |
22 | * |
23 | * 2: BATTERY CAPACITY + MIC STATUS |
24 | * Battery capacity: |
25 | * Seems to report ~54 higher than reality when charging |
26 | * Capped at 100, charging or not |
27 | * Microphone status: |
28 | * Largest bit is set to 1 when the mic is physically up |
29 | * No bits change when the mic is muted, only when physically moved |
30 | * This report is sent every time the mic is moved, no polling required |
31 | * |
32 | * 3: CONNECTION STATUS |
33 | * 16: Wired headset |
34 | * 38: Initialising |
35 | * 49: Lost connection |
36 | * 51: Disconnected, searching |
37 | * 52: Disconnected, not searching |
38 | * 177: Normal |
39 | * |
40 | * 4: BATTERY STATUS |
41 | * 0: Disconnected |
42 | * 1: Normal |
43 | * 2: Low |
44 | * 3: Critical - sent during shutdown |
45 | * 4: Fully charged |
46 | * 5: Charging |
47 | */ |
48 | /* -------------------------------------------------------------------------- */ |
49 | |
50 | /* -------------------------------------------------------------------------- */ |
51 | /* Receiver report information: (ID 102) */ |
52 | /* -------------------------------------------------------------------------- */ |
53 | /* |
54 | * When queried, the recevier responds with 4 bytes to describe the firmware |
55 | * The first 2 bytes are for the receiver, the second 2 are the headset |
56 | * The headset firmware version will be 0 if no headset is connected |
57 | * |
58 | * 0: Recevier firmware major version |
59 | * Major version of the receiver's firmware |
60 | * |
61 | * 1: Recevier firmware minor version |
62 | * Minor version of the receiver's firmware |
63 | * |
64 | * 2: Headset firmware major version |
65 | * Major version of the headset's firmware |
66 | * |
67 | * 3: Headset firmware minor version |
68 | * Minor version of the headset's firmware |
69 | */ |
70 | /* -------------------------------------------------------------------------- */ |
71 | |
72 | #include <linux/bitfield.h> |
73 | #include <linux/bitops.h> |
74 | #include <linux/device.h> |
75 | #include <linux/hid.h> |
76 | #include <linux/module.h> |
77 | #include <linux/power_supply.h> |
78 | #include <linux/usb.h> |
79 | #include <linux/workqueue.h> |
80 | #include <asm/byteorder.h> |
81 | |
82 | #include "hid-ids.h" |
83 | |
84 | #define CORSAIR_VOID_DEVICE(id, type) { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, (id)), \ |
85 | .driver_data = (type) } |
86 | #define CORSAIR_VOID_WIRELESS_DEVICE(id) CORSAIR_VOID_DEVICE((id), CORSAIR_VOID_WIRELESS) |
87 | #define CORSAIR_VOID_WIRED_DEVICE(id) CORSAIR_VOID_DEVICE((id), CORSAIR_VOID_WIRED) |
88 | |
89 | #define CORSAIR_VOID_STATUS_REQUEST_ID 0xC9 |
90 | #define CORSAIR_VOID_NOTIF_REQUEST_ID 0xCA |
91 | #define CORSAIR_VOID_SIDETONE_REQUEST_ID 0xFF |
92 | #define CORSAIR_VOID_STATUS_REPORT_ID 0x64 |
93 | #define CORSAIR_VOID_FIRMWARE_REPORT_ID 0x66 |
94 | |
95 | #define CORSAIR_VOID_USB_SIDETONE_REQUEST 0x1 |
96 | #define CORSAIR_VOID_USB_SIDETONE_REQUEST_TYPE 0x21 |
97 | #define CORSAIR_VOID_USB_SIDETONE_VALUE 0x200 |
98 | #define CORSAIR_VOID_USB_SIDETONE_INDEX 0xB00 |
99 | |
100 | #define CORSAIR_VOID_MIC_MASK GENMASK(7, 7) |
101 | #define CORSAIR_VOID_CAPACITY_MASK GENMASK(6, 0) |
102 | |
103 | #define CORSAIR_VOID_WIRELESS_CONNECTED 177 |
104 | |
105 | #define CORSAIR_VOID_SIDETONE_MAX_WIRELESS 55 |
106 | #define CORSAIR_VOID_SIDETONE_MAX_WIRED 4096 |
107 | |
108 | enum { |
109 | CORSAIR_VOID_WIRELESS, |
110 | CORSAIR_VOID_WIRED, |
111 | }; |
112 | |
113 | enum { |
114 | CORSAIR_VOID_BATTERY_NORMAL = 1, |
115 | CORSAIR_VOID_BATTERY_LOW = 2, |
116 | CORSAIR_VOID_BATTERY_CRITICAL = 3, |
117 | CORSAIR_VOID_BATTERY_CHARGED = 4, |
118 | CORSAIR_VOID_BATTERY_CHARGING = 5, |
119 | }; |
120 | |
121 | enum { |
122 | CORSAIR_VOID_ADD_BATTERY = 0, |
123 | CORSAIR_VOID_REMOVE_BATTERY = 1, |
124 | CORSAIR_VOID_UPDATE_BATTERY = 2, |
125 | }; |
126 | |
127 | static enum power_supply_property corsair_void_battery_props[] = { |
128 | POWER_SUPPLY_PROP_STATUS, |
129 | POWER_SUPPLY_PROP_PRESENT, |
130 | POWER_SUPPLY_PROP_CAPACITY, |
131 | POWER_SUPPLY_PROP_CAPACITY_LEVEL, |
132 | POWER_SUPPLY_PROP_SCOPE, |
133 | POWER_SUPPLY_PROP_MODEL_NAME, |
134 | POWER_SUPPLY_PROP_MANUFACTURER, |
135 | }; |
136 | |
137 | struct corsair_void_battery_data { |
138 | int status; |
139 | bool present; |
140 | int capacity; |
141 | int capacity_level; |
142 | }; |
143 | |
144 | struct corsair_void_drvdata { |
145 | struct hid_device *hid_dev; |
146 | struct device *dev; |
147 | |
148 | char *name; |
149 | bool is_wired; |
150 | unsigned int sidetone_max; |
151 | |
152 | struct corsair_void_battery_data battery_data; |
153 | bool mic_up; |
154 | bool connected; |
155 | int fw_receiver_major; |
156 | int fw_receiver_minor; |
157 | int fw_headset_major; |
158 | int fw_headset_minor; |
159 | |
160 | struct power_supply *battery; |
161 | struct power_supply_desc battery_desc; |
162 | |
163 | struct delayed_work delayed_status_work; |
164 | struct delayed_work delayed_firmware_work; |
165 | |
166 | unsigned long battery_work_flags; |
167 | struct work_struct battery_work; |
168 | }; |
169 | |
170 | /* |
171 | * Functions to process receiver data |
172 | */ |
173 | |
174 | static void corsair_void_set_wireless_status(struct corsair_void_drvdata *drvdata) |
175 | { |
176 | struct usb_interface *usb_if = to_usb_interface(drvdata->dev->parent); |
177 | |
178 | if (drvdata->is_wired) |
179 | return; |
180 | |
181 | usb_set_wireless_status(iface: usb_if, status: drvdata->connected ? |
182 | USB_WIRELESS_STATUS_CONNECTED : |
183 | USB_WIRELESS_STATUS_DISCONNECTED); |
184 | } |
185 | |
186 | static void corsair_void_set_unknown_batt(struct corsair_void_drvdata *drvdata) |
187 | { |
188 | struct corsair_void_battery_data *battery_data = &drvdata->battery_data; |
189 | |
190 | battery_data->status = POWER_SUPPLY_STATUS_UNKNOWN; |
191 | battery_data->present = false; |
192 | battery_data->capacity = 0; |
193 | battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; |
194 | } |
195 | |
196 | /* Reset data that may change between wireless connections */ |
197 | static void corsair_void_set_unknown_wireless_data(struct corsair_void_drvdata *drvdata) |
198 | { |
199 | /* Only 0 out headset, receiver is always known if relevant */ |
200 | drvdata->fw_headset_major = 0; |
201 | drvdata->fw_headset_minor = 0; |
202 | |
203 | drvdata->connected = false; |
204 | drvdata->mic_up = false; |
205 | |
206 | corsair_void_set_wireless_status(drvdata); |
207 | } |
208 | |
209 | static void corsair_void_process_receiver(struct corsair_void_drvdata *drvdata, |
210 | int raw_battery_capacity, |
211 | int raw_connection_status, |
212 | int raw_battery_status) |
213 | { |
214 | struct corsair_void_battery_data *battery_data = &drvdata->battery_data; |
215 | struct corsair_void_battery_data orig_battery_data; |
216 | |
217 | /* Save initial battery data, to compare later */ |
218 | orig_battery_data = *battery_data; |
219 | |
220 | /* Headset not connected, or it's wired */ |
221 | if (raw_connection_status != CORSAIR_VOID_WIRELESS_CONNECTED) |
222 | goto unknown_battery; |
223 | |
224 | /* Battery information unavailable */ |
225 | if (raw_battery_status == 0) |
226 | goto unknown_battery; |
227 | |
228 | /* Battery must be connected then */ |
229 | battery_data->present = true; |
230 | battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; |
231 | |
232 | /* Set battery status */ |
233 | switch (raw_battery_status) { |
234 | case CORSAIR_VOID_BATTERY_NORMAL: |
235 | case CORSAIR_VOID_BATTERY_LOW: |
236 | case CORSAIR_VOID_BATTERY_CRITICAL: |
237 | battery_data->status = POWER_SUPPLY_STATUS_DISCHARGING; |
238 | if (raw_battery_status == CORSAIR_VOID_BATTERY_LOW) |
239 | battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_LOW; |
240 | else if (raw_battery_status == CORSAIR_VOID_BATTERY_CRITICAL) |
241 | battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; |
242 | |
243 | break; |
244 | case CORSAIR_VOID_BATTERY_CHARGED: |
245 | battery_data->status = POWER_SUPPLY_STATUS_FULL; |
246 | break; |
247 | case CORSAIR_VOID_BATTERY_CHARGING: |
248 | battery_data->status = POWER_SUPPLY_STATUS_CHARGING; |
249 | break; |
250 | default: |
251 | hid_warn(drvdata->hid_dev, "unknown battery status '%d'", |
252 | raw_battery_status); |
253 | goto unknown_battery; |
254 | break; |
255 | } |
256 | |
257 | battery_data->capacity = raw_battery_capacity; |
258 | corsair_void_set_wireless_status(drvdata); |
259 | |
260 | goto success; |
261 | unknown_battery: |
262 | corsair_void_set_unknown_batt(drvdata); |
263 | success: |
264 | |
265 | /* Inform power supply if battery values changed */ |
266 | if (memcmp(p: &orig_battery_data, q: battery_data, size: sizeof(*battery_data))) { |
267 | set_bit(nr: CORSAIR_VOID_UPDATE_BATTERY, |
268 | addr: &drvdata->battery_work_flags); |
269 | schedule_work(work: &drvdata->battery_work); |
270 | } |
271 | } |
272 | |
273 | /* |
274 | * Functions to report stored data |
275 | */ |
276 | |
277 | static int corsair_void_battery_get_property(struct power_supply *psy, |
278 | enum power_supply_property prop, |
279 | union power_supply_propval *val) |
280 | { |
281 | struct corsair_void_drvdata *drvdata = power_supply_get_drvdata(psy); |
282 | |
283 | switch (prop) { |
284 | case POWER_SUPPLY_PROP_SCOPE: |
285 | val->intval = POWER_SUPPLY_SCOPE_DEVICE; |
286 | break; |
287 | case POWER_SUPPLY_PROP_MODEL_NAME: |
288 | if (!strncmp(drvdata->hid_dev->name, "Corsair ", 8)) |
289 | val->strval = drvdata->hid_dev->name + 8; |
290 | else |
291 | val->strval = drvdata->hid_dev->name; |
292 | break; |
293 | case POWER_SUPPLY_PROP_MANUFACTURER: |
294 | val->strval = "Corsair"; |
295 | break; |
296 | case POWER_SUPPLY_PROP_STATUS: |
297 | val->intval = drvdata->battery_data.status; |
298 | break; |
299 | case POWER_SUPPLY_PROP_PRESENT: |
300 | val->intval = drvdata->battery_data.present; |
301 | break; |
302 | case POWER_SUPPLY_PROP_CAPACITY: |
303 | val->intval = drvdata->battery_data.capacity; |
304 | break; |
305 | case POWER_SUPPLY_PROP_CAPACITY_LEVEL: |
306 | val->intval = drvdata->battery_data.capacity_level; |
307 | break; |
308 | default: |
309 | return -EINVAL; |
310 | } |
311 | |
312 | return 0; |
313 | } |
314 | |
315 | static ssize_t microphone_up_show(struct device *dev, |
316 | struct device_attribute *attr, char *buf) |
317 | { |
318 | struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev); |
319 | |
320 | if (!drvdata->connected) |
321 | return -ENODEV; |
322 | |
323 | return sysfs_emit(buf, fmt: "%d\n", drvdata->mic_up); |
324 | } |
325 | |
326 | static ssize_t fw_version_receiver_show(struct device *dev, |
327 | struct device_attribute *attr, |
328 | char *buf) |
329 | { |
330 | struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev); |
331 | |
332 | if (drvdata->fw_receiver_major == 0 && drvdata->fw_receiver_minor == 0) |
333 | return -ENODATA; |
334 | |
335 | return sysfs_emit(buf, fmt: "%d.%02d\n", drvdata->fw_receiver_major, |
336 | drvdata->fw_receiver_minor); |
337 | } |
338 | |
339 | |
340 | static ssize_t fw_version_headset_show(struct device *dev, |
341 | struct device_attribute *attr, |
342 | char *buf) |
343 | { |
344 | struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev); |
345 | |
346 | if (drvdata->fw_headset_major == 0 && drvdata->fw_headset_minor == 0) |
347 | return -ENODATA; |
348 | |
349 | return sysfs_emit(buf, fmt: "%d.%02d\n", drvdata->fw_headset_major, |
350 | drvdata->fw_headset_minor); |
351 | } |
352 | |
353 | static ssize_t sidetone_max_show(struct device *dev, |
354 | struct device_attribute *attr, |
355 | char *buf) |
356 | { |
357 | struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev); |
358 | |
359 | return sysfs_emit(buf, fmt: "%d\n", drvdata->sidetone_max); |
360 | } |
361 | |
362 | /* |
363 | * Functions to send data to headset |
364 | */ |
365 | |
366 | static ssize_t send_alert_store(struct device *dev, |
367 | struct device_attribute *attr, |
368 | const char *buf, size_t count) |
369 | { |
370 | struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev); |
371 | struct hid_device *hid_dev = drvdata->hid_dev; |
372 | unsigned char alert_id; |
373 | unsigned char *send_buf __free(kfree) = NULL; |
374 | int ret; |
375 | |
376 | if (!drvdata->connected || drvdata->is_wired) |
377 | return -ENODEV; |
378 | |
379 | /* Only accept 0 or 1 for alert ID */ |
380 | if (kstrtou8(s: buf, base: 10, res: &alert_id) || alert_id >= 2) |
381 | return -EINVAL; |
382 | |
383 | send_buf = kmalloc(3, GFP_KERNEL); |
384 | if (!send_buf) |
385 | return -ENOMEM; |
386 | |
387 | /* Packet format to send alert with ID alert_id */ |
388 | send_buf[0] = CORSAIR_VOID_NOTIF_REQUEST_ID; |
389 | send_buf[1] = 0x02; |
390 | send_buf[2] = alert_id; |
391 | |
392 | ret = hid_hw_raw_request(hdev: hid_dev, CORSAIR_VOID_NOTIF_REQUEST_ID, |
393 | buf: send_buf, len: 3, rtype: HID_OUTPUT_REPORT, |
394 | reqtype: HID_REQ_SET_REPORT); |
395 | if (ret < 0) |
396 | hid_warn(hid_dev, "failed to send alert request (reason: %d)", |
397 | ret); |
398 | else |
399 | ret = count; |
400 | |
401 | return ret; |
402 | } |
403 | |
404 | static int corsair_void_set_sidetone_wired(struct device *dev, const char *buf, |
405 | unsigned int sidetone) |
406 | { |
407 | struct usb_interface *usb_if = to_usb_interface(dev->parent); |
408 | struct usb_device *usb_dev = interface_to_usbdev(usb_if); |
409 | |
410 | /* Packet format to set sidetone for wired headsets */ |
411 | __le16 sidetone_le = cpu_to_le16(sidetone); |
412 | |
413 | return usb_control_msg_send(dev: usb_dev, endpoint: 0, |
414 | CORSAIR_VOID_USB_SIDETONE_REQUEST, |
415 | CORSAIR_VOID_USB_SIDETONE_REQUEST_TYPE, |
416 | CORSAIR_VOID_USB_SIDETONE_VALUE, |
417 | CORSAIR_VOID_USB_SIDETONE_INDEX, |
418 | data: &sidetone_le, size: 2, USB_CTRL_SET_TIMEOUT, |
419 | GFP_KERNEL); |
420 | } |
421 | |
422 | static int corsair_void_set_sidetone_wireless(struct device *dev, |
423 | const char *buf, |
424 | unsigned char sidetone) |
425 | { |
426 | struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev); |
427 | struct hid_device *hid_dev = drvdata->hid_dev; |
428 | unsigned char *send_buf __free(kfree) = NULL; |
429 | |
430 | send_buf = kmalloc(12, GFP_KERNEL); |
431 | if (!send_buf) |
432 | return -ENOMEM; |
433 | |
434 | /* Packet format to set sidetone for wireless headsets */ |
435 | send_buf[0] = CORSAIR_VOID_SIDETONE_REQUEST_ID; |
436 | send_buf[1] = 0x0B; |
437 | send_buf[2] = 0x00; |
438 | send_buf[3] = 0xFF; |
439 | send_buf[4] = 0x04; |
440 | send_buf[5] = 0x0E; |
441 | send_buf[6] = 0xFF; |
442 | send_buf[7] = 0x05; |
443 | send_buf[8] = 0x01; |
444 | send_buf[9] = 0x04; |
445 | send_buf[10] = 0x00; |
446 | send_buf[11] = sidetone + 200; |
447 | |
448 | return hid_hw_raw_request(hdev: hid_dev, CORSAIR_VOID_SIDETONE_REQUEST_ID, |
449 | buf: send_buf, len: 12, rtype: HID_FEATURE_REPORT, |
450 | reqtype: HID_REQ_SET_REPORT); |
451 | } |
452 | |
453 | static ssize_t set_sidetone_store(struct device *dev, |
454 | struct device_attribute *attr, |
455 | const char *buf, size_t count) |
456 | { |
457 | struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev); |
458 | struct hid_device *hid_dev = drvdata->hid_dev; |
459 | unsigned int sidetone; |
460 | int ret; |
461 | |
462 | if (!drvdata->connected) |
463 | return -ENODEV; |
464 | |
465 | /* sidetone must be between 0 and drvdata->sidetone_max inclusive */ |
466 | if (kstrtouint(s: buf, base: 10, res: &sidetone) || sidetone > drvdata->sidetone_max) |
467 | return -EINVAL; |
468 | |
469 | if (drvdata->is_wired) |
470 | ret = corsair_void_set_sidetone_wired(dev, buf, sidetone); |
471 | else |
472 | ret = corsair_void_set_sidetone_wireless(dev, buf, sidetone); |
473 | |
474 | if (ret < 0) |
475 | hid_warn(hid_dev, "failed to send sidetone (reason: %d)", ret); |
476 | else |
477 | ret = count; |
478 | |
479 | return ret; |
480 | } |
481 | |
482 | static int corsair_void_request_status(struct hid_device *hid_dev, int id) |
483 | { |
484 | unsigned char *send_buf __free(kfree) = NULL; |
485 | |
486 | send_buf = kmalloc(2, GFP_KERNEL); |
487 | if (!send_buf) |
488 | return -ENOMEM; |
489 | |
490 | /* Packet format to request data item (status / firmware) refresh */ |
491 | send_buf[0] = CORSAIR_VOID_STATUS_REQUEST_ID; |
492 | send_buf[1] = id; |
493 | |
494 | /* Send request for data refresh */ |
495 | return hid_hw_raw_request(hdev: hid_dev, CORSAIR_VOID_STATUS_REQUEST_ID, |
496 | buf: send_buf, len: 2, rtype: HID_OUTPUT_REPORT, |
497 | reqtype: HID_REQ_SET_REPORT); |
498 | } |
499 | |
500 | /* |
501 | * Headset connect / disconnect handlers and work handlers |
502 | */ |
503 | |
504 | static void corsair_void_status_work_handler(struct work_struct *work) |
505 | { |
506 | struct corsair_void_drvdata *drvdata; |
507 | struct delayed_work *delayed_work; |
508 | int battery_ret; |
509 | |
510 | delayed_work = to_delayed_work(work); |
511 | drvdata = container_of(delayed_work, struct corsair_void_drvdata, |
512 | delayed_status_work); |
513 | |
514 | battery_ret = corsair_void_request_status(hid_dev: drvdata->hid_dev, |
515 | CORSAIR_VOID_STATUS_REPORT_ID); |
516 | if (battery_ret < 0) { |
517 | hid_warn(drvdata->hid_dev, |
518 | "failed to request battery (reason: %d)", battery_ret); |
519 | } |
520 | } |
521 | |
522 | static void corsair_void_firmware_work_handler(struct work_struct *work) |
523 | { |
524 | struct corsair_void_drvdata *drvdata; |
525 | struct delayed_work *delayed_work; |
526 | int firmware_ret; |
527 | |
528 | delayed_work = to_delayed_work(work); |
529 | drvdata = container_of(delayed_work, struct corsair_void_drvdata, |
530 | delayed_firmware_work); |
531 | |
532 | firmware_ret = corsair_void_request_status(hid_dev: drvdata->hid_dev, |
533 | CORSAIR_VOID_FIRMWARE_REPORT_ID); |
534 | if (firmware_ret < 0) { |
535 | hid_warn(drvdata->hid_dev, |
536 | "failed to request firmware (reason: %d)", firmware_ret); |
537 | } |
538 | |
539 | } |
540 | |
541 | static void corsair_void_add_battery(struct corsair_void_drvdata *drvdata) |
542 | { |
543 | struct power_supply_config psy_cfg = {}; |
544 | struct power_supply *new_supply; |
545 | |
546 | if (drvdata->battery) |
547 | return; |
548 | |
549 | psy_cfg.drv_data = drvdata; |
550 | new_supply = power_supply_register(parent: drvdata->dev, |
551 | desc: &drvdata->battery_desc, |
552 | cfg: &psy_cfg); |
553 | |
554 | if (IS_ERR(ptr: new_supply)) { |
555 | hid_err(drvdata->hid_dev, |
556 | "failed to register battery '%s' (reason: %ld)\n", |
557 | drvdata->battery_desc.name, |
558 | PTR_ERR(new_supply)); |
559 | return; |
560 | } |
561 | |
562 | if (power_supply_powers(psy: new_supply, dev: drvdata->dev)) { |
563 | power_supply_unregister(psy: new_supply); |
564 | return; |
565 | } |
566 | |
567 | drvdata->battery = new_supply; |
568 | } |
569 | |
570 | static void corsair_void_battery_work_handler(struct work_struct *work) |
571 | { |
572 | struct corsair_void_drvdata *drvdata = container_of(work, |
573 | struct corsair_void_drvdata, battery_work); |
574 | |
575 | bool add_battery = test_and_clear_bit(nr: CORSAIR_VOID_ADD_BATTERY, |
576 | addr: &drvdata->battery_work_flags); |
577 | bool remove_battery = test_and_clear_bit(nr: CORSAIR_VOID_REMOVE_BATTERY, |
578 | addr: &drvdata->battery_work_flags); |
579 | bool update_battery = test_and_clear_bit(nr: CORSAIR_VOID_UPDATE_BATTERY, |
580 | addr: &drvdata->battery_work_flags); |
581 | |
582 | if (add_battery && !remove_battery) { |
583 | corsair_void_add_battery(drvdata); |
584 | } else if (remove_battery && !add_battery && drvdata->battery) { |
585 | power_supply_unregister(psy: drvdata->battery); |
586 | drvdata->battery = NULL; |
587 | } |
588 | |
589 | if (update_battery && drvdata->battery) |
590 | power_supply_changed(psy: drvdata->battery); |
591 | |
592 | } |
593 | |
594 | static void corsair_void_headset_connected(struct corsair_void_drvdata *drvdata) |
595 | { |
596 | set_bit(nr: CORSAIR_VOID_ADD_BATTERY, addr: &drvdata->battery_work_flags); |
597 | schedule_work(work: &drvdata->battery_work); |
598 | schedule_delayed_work(dwork: &drvdata->delayed_firmware_work, |
599 | delay: msecs_to_jiffies(m: 100)); |
600 | } |
601 | |
602 | static void corsair_void_headset_disconnected(struct corsair_void_drvdata *drvdata) |
603 | { |
604 | set_bit(nr: CORSAIR_VOID_REMOVE_BATTERY, addr: &drvdata->battery_work_flags); |
605 | schedule_work(work: &drvdata->battery_work); |
606 | |
607 | corsair_void_set_unknown_wireless_data(drvdata); |
608 | corsair_void_set_unknown_batt(drvdata); |
609 | } |
610 | |
611 | /* |
612 | * Driver setup, probing and HID event handling |
613 | */ |
614 | |
615 | static DEVICE_ATTR_RO(fw_version_receiver); |
616 | static DEVICE_ATTR_RO(fw_version_headset); |
617 | static DEVICE_ATTR_RO(microphone_up); |
618 | static DEVICE_ATTR_RO(sidetone_max); |
619 | |
620 | static DEVICE_ATTR_WO(send_alert); |
621 | static DEVICE_ATTR_WO(set_sidetone); |
622 | |
623 | static struct attribute *corsair_void_attrs[] = { |
624 | &dev_attr_fw_version_receiver.attr, |
625 | &dev_attr_fw_version_headset.attr, |
626 | &dev_attr_microphone_up.attr, |
627 | &dev_attr_send_alert.attr, |
628 | &dev_attr_set_sidetone.attr, |
629 | &dev_attr_sidetone_max.attr, |
630 | NULL, |
631 | }; |
632 | |
633 | static const struct attribute_group corsair_void_attr_group = { |
634 | .attrs = corsair_void_attrs, |
635 | }; |
636 | |
637 | static int corsair_void_probe(struct hid_device *hid_dev, |
638 | const struct hid_device_id *hid_id) |
639 | { |
640 | int ret; |
641 | struct corsair_void_drvdata *drvdata; |
642 | char *name; |
643 | |
644 | if (!hid_is_usb(hdev: hid_dev)) |
645 | return -EINVAL; |
646 | |
647 | drvdata = devm_kzalloc(dev: &hid_dev->dev, size: sizeof(*drvdata), |
648 | GFP_KERNEL); |
649 | if (!drvdata) |
650 | return -ENOMEM; |
651 | |
652 | hid_set_drvdata(hdev: hid_dev, data: drvdata); |
653 | dev_set_drvdata(dev: &hid_dev->dev, data: drvdata); |
654 | |
655 | drvdata->dev = &hid_dev->dev; |
656 | drvdata->hid_dev = hid_dev; |
657 | drvdata->is_wired = hid_id->driver_data == CORSAIR_VOID_WIRED; |
658 | |
659 | drvdata->sidetone_max = CORSAIR_VOID_SIDETONE_MAX_WIRELESS; |
660 | if (drvdata->is_wired) |
661 | drvdata->sidetone_max = CORSAIR_VOID_SIDETONE_MAX_WIRED; |
662 | |
663 | /* Set initial values for no wireless headset attached */ |
664 | /* If a headset is attached, it'll be prompted later */ |
665 | corsair_void_set_unknown_wireless_data(drvdata); |
666 | corsair_void_set_unknown_batt(drvdata); |
667 | |
668 | /* Receiver version won't be reset after init */ |
669 | /* Headset version already set via set_unknown_wireless_data */ |
670 | drvdata->fw_receiver_major = 0; |
671 | drvdata->fw_receiver_minor = 0; |
672 | |
673 | ret = hid_parse(hdev: hid_dev); |
674 | if (ret) { |
675 | hid_err(hid_dev, "parse failed (reason: %d)\n", ret); |
676 | return ret; |
677 | } |
678 | |
679 | name = devm_kasprintf(dev: drvdata->dev, GFP_KERNEL, |
680 | fmt: "corsair-void-%d-battery", hid_dev->id); |
681 | if (!name) |
682 | return -ENOMEM; |
683 | |
684 | drvdata->battery_desc.name = name; |
685 | drvdata->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY; |
686 | drvdata->battery_desc.properties = corsair_void_battery_props; |
687 | drvdata->battery_desc.num_properties = ARRAY_SIZE(corsair_void_battery_props); |
688 | drvdata->battery_desc.get_property = corsair_void_battery_get_property; |
689 | |
690 | drvdata->battery = NULL; |
691 | INIT_WORK(&drvdata->battery_work, corsair_void_battery_work_handler); |
692 | |
693 | ret = sysfs_create_group(kobj: &hid_dev->dev.kobj, grp: &corsair_void_attr_group); |
694 | if (ret) |
695 | return ret; |
696 | |
697 | /* Any failures after here will need to call hid_hw_stop */ |
698 | ret = hid_hw_start(hdev: hid_dev, HID_CONNECT_DEFAULT); |
699 | if (ret) { |
700 | hid_err(hid_dev, "hid_hw_start failed (reason: %d)\n", ret); |
701 | goto failed_after_sysfs; |
702 | } |
703 | |
704 | /* Refresh battery data, in case wireless headset is already connected */ |
705 | INIT_DELAYED_WORK(&drvdata->delayed_status_work, |
706 | corsair_void_status_work_handler); |
707 | schedule_delayed_work(dwork: &drvdata->delayed_status_work, |
708 | delay: msecs_to_jiffies(m: 100)); |
709 | |
710 | /* Refresh firmware versions */ |
711 | INIT_DELAYED_WORK(&drvdata->delayed_firmware_work, |
712 | corsair_void_firmware_work_handler); |
713 | schedule_delayed_work(dwork: &drvdata->delayed_firmware_work, |
714 | delay: msecs_to_jiffies(m: 100)); |
715 | |
716 | return 0; |
717 | |
718 | failed_after_sysfs: |
719 | sysfs_remove_group(kobj: &hid_dev->dev.kobj, grp: &corsair_void_attr_group); |
720 | return ret; |
721 | } |
722 | |
723 | static void corsair_void_remove(struct hid_device *hid_dev) |
724 | { |
725 | struct corsair_void_drvdata *drvdata = hid_get_drvdata(hdev: hid_dev); |
726 | |
727 | hid_hw_stop(hdev: hid_dev); |
728 | cancel_work_sync(work: &drvdata->battery_work); |
729 | if (drvdata->battery) |
730 | power_supply_unregister(psy: drvdata->battery); |
731 | |
732 | cancel_delayed_work_sync(dwork: &drvdata->delayed_status_work); |
733 | cancel_delayed_work_sync(dwork: &drvdata->delayed_firmware_work); |
734 | sysfs_remove_group(kobj: &hid_dev->dev.kobj, grp: &corsair_void_attr_group); |
735 | } |
736 | |
737 | static int corsair_void_raw_event(struct hid_device *hid_dev, |
738 | struct hid_report *hid_report, |
739 | u8 *data, int size) |
740 | { |
741 | struct corsair_void_drvdata *drvdata = hid_get_drvdata(hdev: hid_dev); |
742 | bool was_connected = drvdata->connected; |
743 | |
744 | /* Description of packets are documented at the top of this file */ |
745 | if (hid_report->id == CORSAIR_VOID_STATUS_REPORT_ID) { |
746 | drvdata->mic_up = FIELD_GET(CORSAIR_VOID_MIC_MASK, data[2]); |
747 | drvdata->connected = (data[3] == CORSAIR_VOID_WIRELESS_CONNECTED) || |
748 | drvdata->is_wired; |
749 | |
750 | corsair_void_process_receiver(drvdata, |
751 | FIELD_GET(CORSAIR_VOID_CAPACITY_MASK, data[2]), |
752 | raw_connection_status: data[3], raw_battery_status: data[4]); |
753 | } else if (hid_report->id == CORSAIR_VOID_FIRMWARE_REPORT_ID) { |
754 | drvdata->fw_receiver_major = data[1]; |
755 | drvdata->fw_receiver_minor = data[2]; |
756 | drvdata->fw_headset_major = data[3]; |
757 | drvdata->fw_headset_minor = data[4]; |
758 | } |
759 | |
760 | /* Handle wireless headset connect / disconnect */ |
761 | if ((was_connected != drvdata->connected) && !drvdata->is_wired) { |
762 | if (drvdata->connected) |
763 | corsair_void_headset_connected(drvdata); |
764 | else |
765 | corsair_void_headset_disconnected(drvdata); |
766 | } |
767 | |
768 | return 0; |
769 | } |
770 | |
771 | static const struct hid_device_id corsair_void_devices[] = { |
772 | /* Corsair Void Wireless */ |
773 | CORSAIR_VOID_WIRELESS_DEVICE(0x0a0c), |
774 | CORSAIR_VOID_WIRELESS_DEVICE(0x0a2b), |
775 | CORSAIR_VOID_WIRELESS_DEVICE(0x1b23), |
776 | CORSAIR_VOID_WIRELESS_DEVICE(0x1b25), |
777 | CORSAIR_VOID_WIRELESS_DEVICE(0x1b27), |
778 | |
779 | /* Corsair Void USB */ |
780 | CORSAIR_VOID_WIRED_DEVICE(0x0a0f), |
781 | CORSAIR_VOID_WIRED_DEVICE(0x1b1c), |
782 | CORSAIR_VOID_WIRED_DEVICE(0x1b29), |
783 | CORSAIR_VOID_WIRED_DEVICE(0x1b2a), |
784 | |
785 | /* Corsair Void Surround */ |
786 | CORSAIR_VOID_WIRED_DEVICE(0x0a30), |
787 | CORSAIR_VOID_WIRED_DEVICE(0x0a31), |
788 | |
789 | /* Corsair Void Pro Wireless */ |
790 | CORSAIR_VOID_WIRELESS_DEVICE(0x0a14), |
791 | CORSAIR_VOID_WIRELESS_DEVICE(0x0a16), |
792 | CORSAIR_VOID_WIRELESS_DEVICE(0x0a1a), |
793 | |
794 | /* Corsair Void Pro USB */ |
795 | CORSAIR_VOID_WIRED_DEVICE(0x0a17), |
796 | CORSAIR_VOID_WIRED_DEVICE(0x0a1d), |
797 | |
798 | /* Corsair Void Pro Surround */ |
799 | CORSAIR_VOID_WIRED_DEVICE(0x0a18), |
800 | CORSAIR_VOID_WIRED_DEVICE(0x0a1e), |
801 | CORSAIR_VOID_WIRED_DEVICE(0x0a1f), |
802 | |
803 | /* Corsair Void Elite Wireless */ |
804 | CORSAIR_VOID_WIRELESS_DEVICE(0x0a51), |
805 | CORSAIR_VOID_WIRELESS_DEVICE(0x0a55), |
806 | CORSAIR_VOID_WIRELESS_DEVICE(0x0a75), |
807 | |
808 | /* Corsair Void Elite USB */ |
809 | CORSAIR_VOID_WIRED_DEVICE(0x0a52), |
810 | CORSAIR_VOID_WIRED_DEVICE(0x0a56), |
811 | |
812 | /* Corsair Void Elite Surround */ |
813 | CORSAIR_VOID_WIRED_DEVICE(0x0a53), |
814 | CORSAIR_VOID_WIRED_DEVICE(0x0a57), |
815 | |
816 | {} |
817 | }; |
818 | |
819 | MODULE_DEVICE_TABLE(hid, corsair_void_devices); |
820 | |
821 | static struct hid_driver corsair_void_driver = { |
822 | .name = "hid-corsair-void", |
823 | .id_table = corsair_void_devices, |
824 | .probe = corsair_void_probe, |
825 | .remove = corsair_void_remove, |
826 | .raw_event = corsair_void_raw_event, |
827 | }; |
828 | |
829 | module_hid_driver(corsair_void_driver); |
830 | |
831 | MODULE_LICENSE("GPL"); |
832 | MODULE_AUTHOR("Stuart Hayhurst <stuart.a.hayhurst@gmail.com>"); |
833 | MODULE_DESCRIPTION("HID driver for Corsair Void headsets"); |
834 |
Definitions
- corsair_void_battery_props
- corsair_void_battery_data
- corsair_void_drvdata
- corsair_void_set_wireless_status
- corsair_void_set_unknown_batt
- corsair_void_set_unknown_wireless_data
- corsair_void_process_receiver
- corsair_void_battery_get_property
- microphone_up_show
- fw_version_receiver_show
- fw_version_headset_show
- sidetone_max_show
- send_alert_store
- corsair_void_set_sidetone_wired
- corsair_void_set_sidetone_wireless
- set_sidetone_store
- corsair_void_request_status
- corsair_void_status_work_handler
- corsair_void_firmware_work_handler
- corsair_void_add_battery
- corsair_void_battery_work_handler
- corsair_void_headset_connected
- corsair_void_headset_disconnected
- corsair_void_attrs
- corsair_void_attr_group
- corsair_void_probe
- corsair_void_remove
- corsair_void_raw_event
- corsair_void_devices
Improve your Profiling and Debugging skills
Find out more