1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * HID driver for Corsair Void headsets
5 * Copyright (C) 2023-2024 Stuart Hayhurst
8 /* -------------------------------------------------------------------------- */
9 /* Receiver report information: (ID 100) */
10 /* -------------------------------------------------------------------------- */
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
18 * 100 for the battery packet
20 * 1: POWER BUTTON + (?)
21 * Largest bit is 1 when power button pressed
23 * 2: BATTERY CAPACITY + MIC STATUS
25 * Seems to report ~54 higher than reality when charging
26 * Capped at 100, charging or not
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
32 * 3: CONNECTION STATUS
36 * 51: Disconnected, searching
37 * 52: Disconnected, not searching
44 * 3: Critical - sent during shutdown
48 /* -------------------------------------------------------------------------- */
50 /* -------------------------------------------------------------------------- */
51 /* Receiver report information: (ID 102) */
52 /* -------------------------------------------------------------------------- */
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
58 * 0: Recevier firmware major version
59 * Major version of the receiver's firmware
61 * 1: Recevier firmware minor version
62 * Minor version of the receiver's firmware
64 * 2: Headset firmware major version
65 * Major version of the headset's firmware
67 * 3: Headset firmware minor version
68 * Minor version of the headset's firmware
70 /* -------------------------------------------------------------------------- */
72 #include <linux/bitfield.h>
73 #include <linux/bitops.h>
74 #include <linux/cleanup.h>
75 #include <linux/device.h>
76 #include <linux/hid.h>
77 #include <linux/module.h>
78 #include <linux/mutex.h>
79 #include <linux/power_supply.h>
80 #include <linux/usb.h>
81 #include <linux/workqueue.h>
82 #include <asm/byteorder.h>
86 #define CORSAIR_VOID_DEVICE(id, type) { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, (id)), \
87 .driver_data = (type) }
88 #define CORSAIR_VOID_WIRELESS_DEVICE(id) CORSAIR_VOID_DEVICE((id), CORSAIR_VOID_WIRELESS)
89 #define CORSAIR_VOID_WIRED_DEVICE(id) CORSAIR_VOID_DEVICE((id), CORSAIR_VOID_WIRED)
91 #define CORSAIR_VOID_STATUS_REQUEST_ID 0xC9
92 #define CORSAIR_VOID_NOTIF_REQUEST_ID 0xCA
93 #define CORSAIR_VOID_SIDETONE_REQUEST_ID 0xFF
94 #define CORSAIR_VOID_STATUS_REPORT_ID 0x64
95 #define CORSAIR_VOID_FIRMWARE_REPORT_ID 0x66
97 #define CORSAIR_VOID_USB_SIDETONE_REQUEST 0x1
98 #define CORSAIR_VOID_USB_SIDETONE_REQUEST_TYPE 0x21
99 #define CORSAIR_VOID_USB_SIDETONE_VALUE 0x200
100 #define CORSAIR_VOID_USB_SIDETONE_INDEX 0xB00
102 #define CORSAIR_VOID_MIC_MASK GENMASK(7, 7)
103 #define CORSAIR_VOID_CAPACITY_MASK GENMASK(6, 0)
105 #define CORSAIR_VOID_WIRELESS_CONNECTED 177
107 #define CORSAIR_VOID_SIDETONE_MAX_WIRELESS 55
108 #define CORSAIR_VOID_SIDETONE_MAX_WIRED 4096
111 CORSAIR_VOID_WIRELESS
,
116 CORSAIR_VOID_BATTERY_NORMAL
= 1,
117 CORSAIR_VOID_BATTERY_LOW
= 2,
118 CORSAIR_VOID_BATTERY_CRITICAL
= 3,
119 CORSAIR_VOID_BATTERY_CHARGED
= 4,
120 CORSAIR_VOID_BATTERY_CHARGING
= 5,
123 static enum power_supply_property corsair_void_battery_props
[] = {
124 POWER_SUPPLY_PROP_STATUS
,
125 POWER_SUPPLY_PROP_PRESENT
,
126 POWER_SUPPLY_PROP_CAPACITY
,
127 POWER_SUPPLY_PROP_CAPACITY_LEVEL
,
128 POWER_SUPPLY_PROP_SCOPE
,
129 POWER_SUPPLY_PROP_MODEL_NAME
,
130 POWER_SUPPLY_PROP_MANUFACTURER
,
133 struct corsair_void_battery_data
{
140 struct corsair_void_drvdata
{
141 struct hid_device
*hid_dev
;
146 unsigned int sidetone_max
;
148 struct corsair_void_battery_data battery_data
;
151 int fw_receiver_major
;
152 int fw_receiver_minor
;
153 int fw_headset_major
;
154 int fw_headset_minor
;
156 struct power_supply
*battery
;
157 struct power_supply_desc battery_desc
;
158 struct mutex battery_mutex
;
160 struct delayed_work delayed_status_work
;
161 struct delayed_work delayed_firmware_work
;
162 struct work_struct battery_remove_work
;
163 struct work_struct battery_add_work
;
167 * Functions to process receiver data
170 static void corsair_void_set_wireless_status(struct corsair_void_drvdata
*drvdata
)
172 struct usb_interface
*usb_if
= to_usb_interface(drvdata
->dev
->parent
);
174 if (drvdata
->is_wired
)
177 usb_set_wireless_status(usb_if
, drvdata
->connected
?
178 USB_WIRELESS_STATUS_CONNECTED
:
179 USB_WIRELESS_STATUS_DISCONNECTED
);
182 static void corsair_void_set_unknown_batt(struct corsair_void_drvdata
*drvdata
)
184 struct corsair_void_battery_data
*battery_data
= &drvdata
->battery_data
;
186 battery_data
->status
= POWER_SUPPLY_STATUS_UNKNOWN
;
187 battery_data
->present
= false;
188 battery_data
->capacity
= 0;
189 battery_data
->capacity_level
= POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN
;
192 /* Reset data that may change between wireless connections */
193 static void corsair_void_set_unknown_wireless_data(struct corsair_void_drvdata
*drvdata
)
195 /* Only 0 out headset, receiver is always known if relevant */
196 drvdata
->fw_headset_major
= 0;
197 drvdata
->fw_headset_minor
= 0;
199 drvdata
->connected
= false;
200 drvdata
->mic_up
= false;
202 corsair_void_set_wireless_status(drvdata
);
205 static void corsair_void_process_receiver(struct corsair_void_drvdata
*drvdata
,
206 int raw_battery_capacity
,
207 int raw_connection_status
,
208 int raw_battery_status
)
210 struct corsair_void_battery_data
*battery_data
= &drvdata
->battery_data
;
211 struct corsair_void_battery_data orig_battery_data
;
213 /* Save initial battery data, to compare later */
214 orig_battery_data
= *battery_data
;
216 /* Headset not connected, or it's wired */
217 if (raw_connection_status
!= CORSAIR_VOID_WIRELESS_CONNECTED
)
218 goto unknown_battery
;
220 /* Battery information unavailable */
221 if (raw_battery_status
== 0)
222 goto unknown_battery
;
224 /* Battery must be connected then */
225 battery_data
->present
= true;
226 battery_data
->capacity_level
= POWER_SUPPLY_CAPACITY_LEVEL_NORMAL
;
228 /* Set battery status */
229 switch (raw_battery_status
) {
230 case CORSAIR_VOID_BATTERY_NORMAL
:
231 case CORSAIR_VOID_BATTERY_LOW
:
232 case CORSAIR_VOID_BATTERY_CRITICAL
:
233 battery_data
->status
= POWER_SUPPLY_STATUS_DISCHARGING
;
234 if (raw_battery_status
== CORSAIR_VOID_BATTERY_LOW
)
235 battery_data
->capacity_level
= POWER_SUPPLY_CAPACITY_LEVEL_LOW
;
236 else if (raw_battery_status
== CORSAIR_VOID_BATTERY_CRITICAL
)
237 battery_data
->capacity_level
= POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL
;
240 case CORSAIR_VOID_BATTERY_CHARGED
:
241 battery_data
->status
= POWER_SUPPLY_STATUS_FULL
;
243 case CORSAIR_VOID_BATTERY_CHARGING
:
244 battery_data
->status
= POWER_SUPPLY_STATUS_CHARGING
;
247 hid_warn(drvdata
->hid_dev
, "unknown battery status '%d'",
249 goto unknown_battery
;
253 battery_data
->capacity
= raw_battery_capacity
;
254 corsair_void_set_wireless_status(drvdata
);
258 corsair_void_set_unknown_batt(drvdata
);
261 /* Inform power supply if battery values changed */
262 if (memcmp(&orig_battery_data
, battery_data
, sizeof(*battery_data
))) {
263 scoped_guard(mutex
, &drvdata
->battery_mutex
) {
264 if (drvdata
->battery
) {
265 power_supply_changed(drvdata
->battery
);
272 * Functions to report stored data
275 static int corsair_void_battery_get_property(struct power_supply
*psy
,
276 enum power_supply_property prop
,
277 union power_supply_propval
*val
)
279 struct corsair_void_drvdata
*drvdata
= power_supply_get_drvdata(psy
);
282 case POWER_SUPPLY_PROP_SCOPE
:
283 val
->intval
= POWER_SUPPLY_SCOPE_DEVICE
;
285 case POWER_SUPPLY_PROP_MODEL_NAME
:
286 if (!strncmp(drvdata
->hid_dev
->name
, "Corsair ", 8))
287 val
->strval
= drvdata
->hid_dev
->name
+ 8;
289 val
->strval
= drvdata
->hid_dev
->name
;
291 case POWER_SUPPLY_PROP_MANUFACTURER
:
292 val
->strval
= "Corsair";
294 case POWER_SUPPLY_PROP_STATUS
:
295 val
->intval
= drvdata
->battery_data
.status
;
297 case POWER_SUPPLY_PROP_PRESENT
:
298 val
->intval
= drvdata
->battery_data
.present
;
300 case POWER_SUPPLY_PROP_CAPACITY
:
301 val
->intval
= drvdata
->battery_data
.capacity
;
303 case POWER_SUPPLY_PROP_CAPACITY_LEVEL
:
304 val
->intval
= drvdata
->battery_data
.capacity_level
;
313 static ssize_t
microphone_up_show(struct device
*dev
,
314 struct device_attribute
*attr
, char *buf
)
316 struct corsair_void_drvdata
*drvdata
= dev_get_drvdata(dev
);
318 if (!drvdata
->connected
)
321 return sysfs_emit(buf
, "%d\n", drvdata
->mic_up
);
324 static ssize_t
fw_version_receiver_show(struct device
*dev
,
325 struct device_attribute
*attr
,
328 struct corsair_void_drvdata
*drvdata
= dev_get_drvdata(dev
);
330 if (drvdata
->fw_receiver_major
== 0 && drvdata
->fw_receiver_minor
== 0)
333 return sysfs_emit(buf
, "%d.%02d\n", drvdata
->fw_receiver_major
,
334 drvdata
->fw_receiver_minor
);
338 static ssize_t
fw_version_headset_show(struct device
*dev
,
339 struct device_attribute
*attr
,
342 struct corsair_void_drvdata
*drvdata
= dev_get_drvdata(dev
);
344 if (drvdata
->fw_headset_major
== 0 && drvdata
->fw_headset_minor
== 0)
347 return sysfs_emit(buf
, "%d.%02d\n", drvdata
->fw_headset_major
,
348 drvdata
->fw_headset_minor
);
351 static ssize_t
sidetone_max_show(struct device
*dev
,
352 struct device_attribute
*attr
,
355 struct corsair_void_drvdata
*drvdata
= dev_get_drvdata(dev
);
357 return sysfs_emit(buf
, "%d\n", drvdata
->sidetone_max
);
361 * Functions to send data to headset
364 static ssize_t
send_alert_store(struct device
*dev
,
365 struct device_attribute
*attr
,
366 const char *buf
, size_t count
)
368 struct corsair_void_drvdata
*drvdata
= dev_get_drvdata(dev
);
369 struct hid_device
*hid_dev
= drvdata
->hid_dev
;
370 unsigned char alert_id
;
371 unsigned char *send_buf
__free(kfree
) = NULL
;
374 if (!drvdata
->connected
|| drvdata
->is_wired
)
377 /* Only accept 0 or 1 for alert ID */
378 if (kstrtou8(buf
, 10, &alert_id
) || alert_id
>= 2)
381 send_buf
= kmalloc(3, GFP_KERNEL
);
385 /* Packet format to send alert with ID alert_id */
386 send_buf
[0] = CORSAIR_VOID_NOTIF_REQUEST_ID
;
388 send_buf
[2] = alert_id
;
390 ret
= hid_hw_raw_request(hid_dev
, CORSAIR_VOID_NOTIF_REQUEST_ID
,
391 send_buf
, 3, HID_OUTPUT_REPORT
,
394 hid_warn(hid_dev
, "failed to send alert request (reason: %d)",
402 static int corsair_void_set_sidetone_wired(struct device
*dev
, const char *buf
,
403 unsigned int sidetone
)
405 struct usb_interface
*usb_if
= to_usb_interface(dev
->parent
);
406 struct usb_device
*usb_dev
= interface_to_usbdev(usb_if
);
408 /* Packet format to set sidetone for wired headsets */
409 __le16 sidetone_le
= cpu_to_le16(sidetone
);
411 return usb_control_msg_send(usb_dev
, 0,
412 CORSAIR_VOID_USB_SIDETONE_REQUEST
,
413 CORSAIR_VOID_USB_SIDETONE_REQUEST_TYPE
,
414 CORSAIR_VOID_USB_SIDETONE_VALUE
,
415 CORSAIR_VOID_USB_SIDETONE_INDEX
,
416 &sidetone_le
, 2, USB_CTRL_SET_TIMEOUT
,
420 static int corsair_void_set_sidetone_wireless(struct device
*dev
,
422 unsigned char sidetone
)
424 struct corsair_void_drvdata
*drvdata
= dev_get_drvdata(dev
);
425 struct hid_device
*hid_dev
= drvdata
->hid_dev
;
426 unsigned char *send_buf
__free(kfree
) = NULL
;
428 send_buf
= kmalloc(12, GFP_KERNEL
);
432 /* Packet format to set sidetone for wireless headsets */
433 send_buf
[0] = CORSAIR_VOID_SIDETONE_REQUEST_ID
;
444 send_buf
[11] = sidetone
+ 200;
446 return hid_hw_raw_request(hid_dev
, CORSAIR_VOID_SIDETONE_REQUEST_ID
,
447 send_buf
, 12, HID_FEATURE_REPORT
,
451 static ssize_t
set_sidetone_store(struct device
*dev
,
452 struct device_attribute
*attr
,
453 const char *buf
, size_t count
)
455 struct corsair_void_drvdata
*drvdata
= dev_get_drvdata(dev
);
456 struct hid_device
*hid_dev
= drvdata
->hid_dev
;
457 unsigned int sidetone
;
460 if (!drvdata
->connected
)
463 /* sidetone must be between 0 and drvdata->sidetone_max inclusive */
464 if (kstrtouint(buf
, 10, &sidetone
) || sidetone
> drvdata
->sidetone_max
)
467 if (drvdata
->is_wired
)
468 ret
= corsair_void_set_sidetone_wired(dev
, buf
, sidetone
);
470 ret
= corsair_void_set_sidetone_wireless(dev
, buf
, sidetone
);
473 hid_warn(hid_dev
, "failed to send sidetone (reason: %d)", ret
);
480 static int corsair_void_request_status(struct hid_device
*hid_dev
, int id
)
482 unsigned char *send_buf
__free(kfree
) = NULL
;
484 send_buf
= kmalloc(2, GFP_KERNEL
);
488 /* Packet format to request data item (status / firmware) refresh */
489 send_buf
[0] = CORSAIR_VOID_STATUS_REQUEST_ID
;
492 /* Send request for data refresh */
493 return hid_hw_raw_request(hid_dev
, CORSAIR_VOID_STATUS_REQUEST_ID
,
494 send_buf
, 2, HID_OUTPUT_REPORT
,
499 * Headset connect / disconnect handlers and work handlers
502 static void corsair_void_status_work_handler(struct work_struct
*work
)
504 struct corsair_void_drvdata
*drvdata
;
505 struct delayed_work
*delayed_work
;
508 delayed_work
= container_of(work
, struct delayed_work
, work
);
509 drvdata
= container_of(delayed_work
, struct corsair_void_drvdata
,
510 delayed_status_work
);
512 battery_ret
= corsair_void_request_status(drvdata
->hid_dev
,
513 CORSAIR_VOID_STATUS_REPORT_ID
);
514 if (battery_ret
< 0) {
515 hid_warn(drvdata
->hid_dev
,
516 "failed to request battery (reason: %d)", battery_ret
);
520 static void corsair_void_firmware_work_handler(struct work_struct
*work
)
522 struct corsair_void_drvdata
*drvdata
;
523 struct delayed_work
*delayed_work
;
526 delayed_work
= container_of(work
, struct delayed_work
, work
);
527 drvdata
= container_of(delayed_work
, struct corsair_void_drvdata
,
528 delayed_firmware_work
);
530 firmware_ret
= corsair_void_request_status(drvdata
->hid_dev
,
531 CORSAIR_VOID_FIRMWARE_REPORT_ID
);
532 if (firmware_ret
< 0) {
533 hid_warn(drvdata
->hid_dev
,
534 "failed to request firmware (reason: %d)", firmware_ret
);
539 static void corsair_void_battery_remove_work_handler(struct work_struct
*work
)
541 struct corsair_void_drvdata
*drvdata
;
543 drvdata
= container_of(work
, struct corsair_void_drvdata
,
544 battery_remove_work
);
545 scoped_guard(mutex
, &drvdata
->battery_mutex
) {
546 if (drvdata
->battery
) {
547 power_supply_unregister(drvdata
->battery
);
548 drvdata
->battery
= NULL
;
553 static void corsair_void_battery_add_work_handler(struct work_struct
*work
)
555 struct corsair_void_drvdata
*drvdata
;
556 struct power_supply_config psy_cfg
;
557 struct power_supply
*new_supply
;
559 drvdata
= container_of(work
, struct corsair_void_drvdata
,
561 guard(mutex
)(&drvdata
->battery_mutex
);
562 if (drvdata
->battery
)
565 psy_cfg
.drv_data
= drvdata
;
566 new_supply
= power_supply_register(drvdata
->dev
,
567 &drvdata
->battery_desc
,
570 if (IS_ERR(new_supply
)) {
571 hid_err(drvdata
->hid_dev
,
572 "failed to register battery '%s' (reason: %ld)\n",
573 drvdata
->battery_desc
.name
,
574 PTR_ERR(new_supply
));
578 if (power_supply_powers(new_supply
, drvdata
->dev
)) {
579 power_supply_unregister(new_supply
);
583 drvdata
->battery
= new_supply
;
586 static void corsair_void_headset_connected(struct corsair_void_drvdata
*drvdata
)
588 schedule_work(&drvdata
->battery_add_work
);
589 schedule_delayed_work(&drvdata
->delayed_firmware_work
,
590 msecs_to_jiffies(100));
593 static void corsair_void_headset_disconnected(struct corsair_void_drvdata
*drvdata
)
595 schedule_work(&drvdata
->battery_remove_work
);
597 corsair_void_set_unknown_wireless_data(drvdata
);
598 corsair_void_set_unknown_batt(drvdata
);
602 * Driver setup, probing and HID event handling
605 static DEVICE_ATTR_RO(fw_version_receiver
);
606 static DEVICE_ATTR_RO(fw_version_headset
);
607 static DEVICE_ATTR_RO(microphone_up
);
608 static DEVICE_ATTR_RO(sidetone_max
);
610 static DEVICE_ATTR_WO(send_alert
);
611 static DEVICE_ATTR_WO(set_sidetone
);
613 static struct attribute
*corsair_void_attrs
[] = {
614 &dev_attr_fw_version_receiver
.attr
,
615 &dev_attr_fw_version_headset
.attr
,
616 &dev_attr_microphone_up
.attr
,
617 &dev_attr_send_alert
.attr
,
618 &dev_attr_set_sidetone
.attr
,
619 &dev_attr_sidetone_max
.attr
,
623 static const struct attribute_group corsair_void_attr_group
= {
624 .attrs
= corsair_void_attrs
,
627 static int corsair_void_probe(struct hid_device
*hid_dev
,
628 const struct hid_device_id
*hid_id
)
631 struct corsair_void_drvdata
*drvdata
;
634 if (!hid_is_usb(hid_dev
))
637 drvdata
= devm_kzalloc(&hid_dev
->dev
, sizeof(*drvdata
),
642 hid_set_drvdata(hid_dev
, drvdata
);
643 dev_set_drvdata(&hid_dev
->dev
, drvdata
);
645 drvdata
->dev
= &hid_dev
->dev
;
646 drvdata
->hid_dev
= hid_dev
;
647 drvdata
->is_wired
= hid_id
->driver_data
== CORSAIR_VOID_WIRED
;
649 drvdata
->sidetone_max
= CORSAIR_VOID_SIDETONE_MAX_WIRELESS
;
650 if (drvdata
->is_wired
)
651 drvdata
->sidetone_max
= CORSAIR_VOID_SIDETONE_MAX_WIRED
;
653 /* Set initial values for no wireless headset attached */
654 /* If a headset is attached, it'll be prompted later */
655 corsair_void_set_unknown_wireless_data(drvdata
);
656 corsair_void_set_unknown_batt(drvdata
);
658 /* Receiver version won't be reset after init */
659 /* Headset version already set via set_unknown_wireless_data */
660 drvdata
->fw_receiver_major
= 0;
661 drvdata
->fw_receiver_minor
= 0;
663 ret
= hid_parse(hid_dev
);
665 hid_err(hid_dev
, "parse failed (reason: %d)\n", ret
);
669 name
= devm_kasprintf(drvdata
->dev
, GFP_KERNEL
,
670 "corsair-void-%d-battery", hid_dev
->id
);
674 drvdata
->battery_desc
.name
= name
;
675 drvdata
->battery_desc
.type
= POWER_SUPPLY_TYPE_BATTERY
;
676 drvdata
->battery_desc
.properties
= corsair_void_battery_props
;
677 drvdata
->battery_desc
.num_properties
= ARRAY_SIZE(corsair_void_battery_props
);
678 drvdata
->battery_desc
.get_property
= corsair_void_battery_get_property
;
680 drvdata
->battery
= NULL
;
681 INIT_WORK(&drvdata
->battery_remove_work
,
682 corsair_void_battery_remove_work_handler
);
683 INIT_WORK(&drvdata
->battery_add_work
,
684 corsair_void_battery_add_work_handler
);
685 ret
= devm_mutex_init(drvdata
->dev
, &drvdata
->battery_mutex
);
689 ret
= sysfs_create_group(&hid_dev
->dev
.kobj
, &corsair_void_attr_group
);
693 /* Any failures after here will need to call hid_hw_stop */
694 ret
= hid_hw_start(hid_dev
, HID_CONNECT_DEFAULT
);
696 hid_err(hid_dev
, "hid_hw_start failed (reason: %d)\n", ret
);
697 goto failed_after_sysfs
;
700 /* Refresh battery data, in case wireless headset is already connected */
701 INIT_DELAYED_WORK(&drvdata
->delayed_status_work
,
702 corsair_void_status_work_handler
);
703 schedule_delayed_work(&drvdata
->delayed_status_work
,
704 msecs_to_jiffies(100));
706 /* Refresh firmware versions */
707 INIT_DELAYED_WORK(&drvdata
->delayed_firmware_work
,
708 corsair_void_firmware_work_handler
);
709 schedule_delayed_work(&drvdata
->delayed_firmware_work
,
710 msecs_to_jiffies(100));
715 sysfs_remove_group(&hid_dev
->dev
.kobj
, &corsair_void_attr_group
);
719 static void corsair_void_remove(struct hid_device
*hid_dev
)
721 struct corsair_void_drvdata
*drvdata
= hid_get_drvdata(hid_dev
);
723 hid_hw_stop(hid_dev
);
724 cancel_work_sync(&drvdata
->battery_remove_work
);
725 cancel_work_sync(&drvdata
->battery_add_work
);
726 if (drvdata
->battery
)
727 power_supply_unregister(drvdata
->battery
);
729 cancel_delayed_work_sync(&drvdata
->delayed_firmware_work
);
730 sysfs_remove_group(&hid_dev
->dev
.kobj
, &corsair_void_attr_group
);
733 static int corsair_void_raw_event(struct hid_device
*hid_dev
,
734 struct hid_report
*hid_report
,
737 struct corsair_void_drvdata
*drvdata
= hid_get_drvdata(hid_dev
);
738 bool was_connected
= drvdata
->connected
;
740 /* Description of packets are documented at the top of this file */
741 if (hid_report
->id
== CORSAIR_VOID_STATUS_REPORT_ID
) {
742 drvdata
->mic_up
= FIELD_GET(CORSAIR_VOID_MIC_MASK
, data
[2]);
743 drvdata
->connected
= (data
[3] == CORSAIR_VOID_WIRELESS_CONNECTED
) ||
746 corsair_void_process_receiver(drvdata
,
747 FIELD_GET(CORSAIR_VOID_CAPACITY_MASK
, data
[2]),
749 } else if (hid_report
->id
== CORSAIR_VOID_FIRMWARE_REPORT_ID
) {
750 drvdata
->fw_receiver_major
= data
[1];
751 drvdata
->fw_receiver_minor
= data
[2];
752 drvdata
->fw_headset_major
= data
[3];
753 drvdata
->fw_headset_minor
= data
[4];
756 /* Handle wireless headset connect / disconnect */
757 if ((was_connected
!= drvdata
->connected
) && !drvdata
->is_wired
) {
758 if (drvdata
->connected
)
759 corsair_void_headset_connected(drvdata
);
761 corsair_void_headset_disconnected(drvdata
);
767 static const struct hid_device_id corsair_void_devices
[] = {
768 /* Corsair Void Wireless */
769 CORSAIR_VOID_WIRELESS_DEVICE(0x0a0c),
770 CORSAIR_VOID_WIRELESS_DEVICE(0x0a2b),
771 CORSAIR_VOID_WIRELESS_DEVICE(0x1b23),
772 CORSAIR_VOID_WIRELESS_DEVICE(0x1b25),
773 CORSAIR_VOID_WIRELESS_DEVICE(0x1b27),
775 /* Corsair Void USB */
776 CORSAIR_VOID_WIRED_DEVICE(0x0a0f),
777 CORSAIR_VOID_WIRED_DEVICE(0x1b1c),
778 CORSAIR_VOID_WIRED_DEVICE(0x1b29),
779 CORSAIR_VOID_WIRED_DEVICE(0x1b2a),
781 /* Corsair Void Surround */
782 CORSAIR_VOID_WIRED_DEVICE(0x0a30),
783 CORSAIR_VOID_WIRED_DEVICE(0x0a31),
785 /* Corsair Void Pro Wireless */
786 CORSAIR_VOID_WIRELESS_DEVICE(0x0a14),
787 CORSAIR_VOID_WIRELESS_DEVICE(0x0a16),
788 CORSAIR_VOID_WIRELESS_DEVICE(0x0a1a),
790 /* Corsair Void Pro USB */
791 CORSAIR_VOID_WIRED_DEVICE(0x0a17),
792 CORSAIR_VOID_WIRED_DEVICE(0x0a1d),
794 /* Corsair Void Pro Surround */
795 CORSAIR_VOID_WIRED_DEVICE(0x0a18),
796 CORSAIR_VOID_WIRED_DEVICE(0x0a1e),
797 CORSAIR_VOID_WIRED_DEVICE(0x0a1f),
799 /* Corsair Void Elite Wireless */
800 CORSAIR_VOID_WIRELESS_DEVICE(0x0a51),
801 CORSAIR_VOID_WIRELESS_DEVICE(0x0a55),
802 CORSAIR_VOID_WIRELESS_DEVICE(0x0a75),
804 /* Corsair Void Elite USB */
805 CORSAIR_VOID_WIRED_DEVICE(0x0a52),
806 CORSAIR_VOID_WIRED_DEVICE(0x0a56),
808 /* Corsair Void Elite Surround */
809 CORSAIR_VOID_WIRED_DEVICE(0x0a53),
810 CORSAIR_VOID_WIRED_DEVICE(0x0a57),
815 MODULE_DEVICE_TABLE(hid
, corsair_void_devices
);
817 static struct hid_driver corsair_void_driver
= {
818 .name
= "hid-corsair-void",
819 .id_table
= corsair_void_devices
,
820 .probe
= corsair_void_probe
,
821 .remove
= corsair_void_remove
,
822 .raw_event
= corsair_void_raw_event
,
825 module_hid_driver(corsair_void_driver
);
827 MODULE_LICENSE("GPL");
828 MODULE_AUTHOR("Stuart Hayhurst <stuart.a.hayhurst@gmail.com>");
829 MODULE_DESCRIPTION("HID driver for Corsair Void headsets");