Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

HID: corsair-void: Add Corsair Void headset family driver

Introduce a driver for the Corsair Void family of headsets, supporting:
- Battery reporting (power_supply)
- Sidetone setting support
- Physical microphone location reporting
- Headset and receiver firmware version reporting
- Built-in alert triggering
- USB wireless_status

Tested with a Void Pro Wireless, Void Elite Wireless and a Void Elite Wired

Signed-off-by: Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>

authored by

Stuart Hayhurst and committed by
Jiri Kosina
6ea2a6fd f23aa4c0

+871 -1
+38
Documentation/ABI/testing/sysfs-driver-hid-corsair-void
··· 1 + What: /sys/bus/hid/drivers/hid-corsair-void/<dev>/fw_version_headset 2 + Date: January 2024 3 + KernelVersion: 6.13 4 + Contact: Stuart Hayhurst <stuart.a.hayhurst@gmail.com> 5 + Description: (R) The firmware version of the headset 6 + * Returns -ENODATA if no version was reported 7 + 8 + What: /sys/bus/hid/drivers/hid-corsair-void/<dev>/fw_version_receiver 9 + Date: January 2024 10 + KernelVersion: 6.13 11 + Contact: Stuart Hayhurst <stuart.a.hayhurst@gmail.com> 12 + Description: (R) The firmware version of the receiver 13 + 14 + What: /sys/bus/hid/drivers/hid-corsair-void/<dev>/microphone_up 15 + Date: July 2023 16 + KernelVersion: 6.13 17 + Contact: Stuart Hayhurst <stuart.a.hayhurst@gmail.com> 18 + Description: (R) Get the physical position of the microphone 19 + * 1 -> Microphone up 20 + * 0 -> Microphone down 21 + 22 + What: /sys/bus/hid/drivers/hid-corsair-void/<dev>/send_alert 23 + Date: July 2023 24 + KernelVersion: 6.13 25 + Contact: Stuart Hayhurst <stuart.a.hayhurst@gmail.com> 26 + Description: (W) Play a built-in notification from the headset (0 / 1) 27 + 28 + What: /sys/bus/hid/drivers/hid-corsair-void/<dev>/set_sidetone 29 + Date: December 2023 30 + KernelVersion: 6.13 31 + Contact: Stuart Hayhurst <stuart.a.hayhurst@gmail.com> 32 + Description: (W) Set the sidetone volume (0 - sidetone_max) 33 + 34 + What: /sys/bus/hid/drivers/hid-corsair-void/<dev>/sidetone_max 35 + Date: July 2024 36 + KernelVersion: 6.13 37 + Contact: Stuart Hayhurst <stuart.a.hayhurst@gmail.com> 38 + Description: (R) Report the maximum sidetone volume
+3
drivers/hid/Kconfig
··· 213 213 config HID_CORSAIR 214 214 tristate "Corsair devices" 215 215 depends on USB_HID && LEDS_CLASS 216 + select POWER_SUPPLY 216 217 help 217 218 Support for Corsair devices that are not fully compliant with the 218 219 HID standard. 220 + Support for Corsair Void headsets. 219 221 220 222 Supported devices: 221 223 - Vengeance K90 222 224 - Scimitar PRO RGB 225 + - Corsair Void headsets 223 226 224 227 config HID_COUGAR 225 228 tristate "Cougar devices"
+1 -1
drivers/hid/Makefile
··· 38 38 obj-$(CONFIG_HID_CHERRY) += hid-cherry.o 39 39 obj-$(CONFIG_HID_CHICONY) += hid-chicony.o 40 40 obj-$(CONFIG_HID_CMEDIA) += hid-cmedia.o 41 - obj-$(CONFIG_HID_CORSAIR) += hid-corsair.o 41 + obj-$(CONFIG_HID_CORSAIR) += hid-corsair.o hid-corsair-void.o 42 42 obj-$(CONFIG_HID_COUGAR) += hid-cougar.o 43 43 obj-$(CONFIG_HID_CP2112) += hid-cp2112.o 44 44 obj-$(CONFIG_HID_CYPRESS) += hid-cypress.o
+829
drivers/hid/hid-corsair-void.c
··· 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/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> 83 + 84 + #include "hid-ids.h" 85 + 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) 90 + 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 96 + 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 101 + 102 + #define CORSAIR_VOID_MIC_MASK GENMASK(7, 7) 103 + #define CORSAIR_VOID_CAPACITY_MASK GENMASK(6, 0) 104 + 105 + #define CORSAIR_VOID_WIRELESS_CONNECTED 177 106 + 107 + #define CORSAIR_VOID_SIDETONE_MAX_WIRELESS 55 108 + #define CORSAIR_VOID_SIDETONE_MAX_WIRED 4096 109 + 110 + enum { 111 + CORSAIR_VOID_WIRELESS, 112 + CORSAIR_VOID_WIRED, 113 + }; 114 + 115 + enum { 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, 121 + }; 122 + 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, 131 + }; 132 + 133 + struct corsair_void_battery_data { 134 + int status; 135 + bool present; 136 + int capacity; 137 + int capacity_level; 138 + }; 139 + 140 + struct corsair_void_drvdata { 141 + struct hid_device *hid_dev; 142 + struct device *dev; 143 + 144 + char *name; 145 + bool is_wired; 146 + unsigned int sidetone_max; 147 + 148 + struct corsair_void_battery_data battery_data; 149 + bool mic_up; 150 + bool connected; 151 + int fw_receiver_major; 152 + int fw_receiver_minor; 153 + int fw_headset_major; 154 + int fw_headset_minor; 155 + 156 + struct power_supply *battery; 157 + struct power_supply_desc battery_desc; 158 + struct mutex battery_mutex; 159 + 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; 164 + }; 165 + 166 + /* 167 + * Functions to process receiver data 168 + */ 169 + 170 + static void corsair_void_set_wireless_status(struct corsair_void_drvdata *drvdata) 171 + { 172 + struct usb_interface *usb_if = to_usb_interface(drvdata->dev->parent); 173 + 174 + if (drvdata->is_wired) 175 + return; 176 + 177 + usb_set_wireless_status(usb_if, drvdata->connected ? 178 + USB_WIRELESS_STATUS_CONNECTED : 179 + USB_WIRELESS_STATUS_DISCONNECTED); 180 + } 181 + 182 + static void corsair_void_set_unknown_batt(struct corsair_void_drvdata *drvdata) 183 + { 184 + struct corsair_void_battery_data *battery_data = &drvdata->battery_data; 185 + 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; 190 + } 191 + 192 + /* Reset data that may change between wireless connections */ 193 + static void corsair_void_set_unknown_wireless_data(struct corsair_void_drvdata *drvdata) 194 + { 195 + /* Only 0 out headset, receiver is always known if relevant */ 196 + drvdata->fw_headset_major = 0; 197 + drvdata->fw_headset_minor = 0; 198 + 199 + drvdata->connected = false; 200 + drvdata->mic_up = false; 201 + 202 + corsair_void_set_wireless_status(drvdata); 203 + } 204 + 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) 209 + { 210 + struct corsair_void_battery_data *battery_data = &drvdata->battery_data; 211 + struct corsair_void_battery_data orig_battery_data; 212 + 213 + /* Save initial battery data, to compare later */ 214 + orig_battery_data = *battery_data; 215 + 216 + /* Headset not connected, or it's wired */ 217 + if (raw_connection_status != CORSAIR_VOID_WIRELESS_CONNECTED) 218 + goto unknown_battery; 219 + 220 + /* Battery information unavailable */ 221 + if (raw_battery_status == 0) 222 + goto unknown_battery; 223 + 224 + /* Battery must be connected then */ 225 + battery_data->present = true; 226 + battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; 227 + 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; 238 + 239 + break; 240 + case CORSAIR_VOID_BATTERY_CHARGED: 241 + battery_data->status = POWER_SUPPLY_STATUS_FULL; 242 + break; 243 + case CORSAIR_VOID_BATTERY_CHARGING: 244 + battery_data->status = POWER_SUPPLY_STATUS_CHARGING; 245 + break; 246 + default: 247 + hid_warn(drvdata->hid_dev, "unknown battery status '%d'", 248 + raw_battery_status); 249 + goto unknown_battery; 250 + break; 251 + } 252 + 253 + battery_data->capacity = raw_battery_capacity; 254 + corsair_void_set_wireless_status(drvdata); 255 + 256 + goto success; 257 + unknown_battery: 258 + corsair_void_set_unknown_batt(drvdata); 259 + success: 260 + 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); 266 + } 267 + } 268 + } 269 + } 270 + 271 + /* 272 + * Functions to report stored data 273 + */ 274 + 275 + static int corsair_void_battery_get_property(struct power_supply *psy, 276 + enum power_supply_property prop, 277 + union power_supply_propval *val) 278 + { 279 + struct corsair_void_drvdata *drvdata = power_supply_get_drvdata(psy); 280 + 281 + switch (prop) { 282 + case POWER_SUPPLY_PROP_SCOPE: 283 + val->intval = POWER_SUPPLY_SCOPE_DEVICE; 284 + break; 285 + case POWER_SUPPLY_PROP_MODEL_NAME: 286 + if (!strncmp(drvdata->hid_dev->name, "Corsair ", 8)) 287 + val->strval = drvdata->hid_dev->name + 8; 288 + else 289 + val->strval = drvdata->hid_dev->name; 290 + break; 291 + case POWER_SUPPLY_PROP_MANUFACTURER: 292 + val->strval = "Corsair"; 293 + break; 294 + case POWER_SUPPLY_PROP_STATUS: 295 + val->intval = drvdata->battery_data.status; 296 + break; 297 + case POWER_SUPPLY_PROP_PRESENT: 298 + val->intval = drvdata->battery_data.present; 299 + break; 300 + case POWER_SUPPLY_PROP_CAPACITY: 301 + val->intval = drvdata->battery_data.capacity; 302 + break; 303 + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: 304 + val->intval = drvdata->battery_data.capacity_level; 305 + break; 306 + default: 307 + return -EINVAL; 308 + } 309 + 310 + return 0; 311 + } 312 + 313 + static ssize_t microphone_up_show(struct device *dev, 314 + struct device_attribute *attr, char *buf) 315 + { 316 + struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev); 317 + 318 + if (!drvdata->connected) 319 + return -ENODEV; 320 + 321 + return sysfs_emit(buf, "%d\n", drvdata->mic_up); 322 + } 323 + 324 + static ssize_t fw_version_receiver_show(struct device *dev, 325 + struct device_attribute *attr, 326 + char *buf) 327 + { 328 + struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev); 329 + 330 + if (drvdata->fw_receiver_major == 0 && drvdata->fw_receiver_minor == 0) 331 + return -ENODATA; 332 + 333 + return sysfs_emit(buf, "%d.%02d\n", drvdata->fw_receiver_major, 334 + drvdata->fw_receiver_minor); 335 + } 336 + 337 + 338 + static ssize_t fw_version_headset_show(struct device *dev, 339 + struct device_attribute *attr, 340 + char *buf) 341 + { 342 + struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev); 343 + 344 + if (drvdata->fw_headset_major == 0 && drvdata->fw_headset_minor == 0) 345 + return -ENODATA; 346 + 347 + return sysfs_emit(buf, "%d.%02d\n", drvdata->fw_headset_major, 348 + drvdata->fw_headset_minor); 349 + } 350 + 351 + static ssize_t sidetone_max_show(struct device *dev, 352 + struct device_attribute *attr, 353 + char *buf) 354 + { 355 + struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev); 356 + 357 + return sysfs_emit(buf, "%d\n", drvdata->sidetone_max); 358 + } 359 + 360 + /* 361 + * Functions to send data to headset 362 + */ 363 + 364 + static ssize_t send_alert_store(struct device *dev, 365 + struct device_attribute *attr, 366 + const char *buf, size_t count) 367 + { 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; 372 + int ret; 373 + 374 + if (!drvdata->connected || drvdata->is_wired) 375 + return -ENODEV; 376 + 377 + /* Only accept 0 or 1 for alert ID */ 378 + if (kstrtou8(buf, 10, &alert_id) || alert_id >= 2) 379 + return -EINVAL; 380 + 381 + send_buf = kmalloc(3, GFP_KERNEL); 382 + if (!send_buf) 383 + return -ENOMEM; 384 + 385 + /* Packet format to send alert with ID alert_id */ 386 + send_buf[0] = CORSAIR_VOID_NOTIF_REQUEST_ID; 387 + send_buf[1] = 0x02; 388 + send_buf[2] = alert_id; 389 + 390 + ret = hid_hw_raw_request(hid_dev, CORSAIR_VOID_NOTIF_REQUEST_ID, 391 + send_buf, 3, HID_OUTPUT_REPORT, 392 + HID_REQ_SET_REPORT); 393 + if (ret < 0) 394 + hid_warn(hid_dev, "failed to send alert request (reason: %d)", 395 + ret); 396 + else 397 + ret = count; 398 + 399 + return ret; 400 + } 401 + 402 + static int corsair_void_set_sidetone_wired(struct device *dev, const char *buf, 403 + unsigned int sidetone) 404 + { 405 + struct usb_interface *usb_if = to_usb_interface(dev->parent); 406 + struct usb_device *usb_dev = interface_to_usbdev(usb_if); 407 + 408 + /* Packet format to set sidetone for wired headsets */ 409 + __le16 sidetone_le = cpu_to_le16(sidetone); 410 + 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, 417 + GFP_KERNEL); 418 + } 419 + 420 + static int corsair_void_set_sidetone_wireless(struct device *dev, 421 + const char *buf, 422 + unsigned char sidetone) 423 + { 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; 427 + 428 + send_buf = kmalloc(12, GFP_KERNEL); 429 + if (!send_buf) 430 + return -ENOMEM; 431 + 432 + /* Packet format to set sidetone for wireless headsets */ 433 + send_buf[0] = CORSAIR_VOID_SIDETONE_REQUEST_ID; 434 + send_buf[1] = 0x0B; 435 + send_buf[2] = 0x00; 436 + send_buf[3] = 0xFF; 437 + send_buf[4] = 0x04; 438 + send_buf[5] = 0x0E; 439 + send_buf[6] = 0xFF; 440 + send_buf[7] = 0x05; 441 + send_buf[8] = 0x01; 442 + send_buf[9] = 0x04; 443 + send_buf[10] = 0x00; 444 + send_buf[11] = sidetone + 200; 445 + 446 + return hid_hw_raw_request(hid_dev, CORSAIR_VOID_SIDETONE_REQUEST_ID, 447 + send_buf, 12, HID_FEATURE_REPORT, 448 + HID_REQ_SET_REPORT); 449 + } 450 + 451 + static ssize_t set_sidetone_store(struct device *dev, 452 + struct device_attribute *attr, 453 + const char *buf, size_t count) 454 + { 455 + struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev); 456 + struct hid_device *hid_dev = drvdata->hid_dev; 457 + unsigned int sidetone; 458 + int ret; 459 + 460 + if (!drvdata->connected) 461 + return -ENODEV; 462 + 463 + /* sidetone must be between 0 and drvdata->sidetone_max inclusive */ 464 + if (kstrtouint(buf, 10, &sidetone) || sidetone > drvdata->sidetone_max) 465 + return -EINVAL; 466 + 467 + if (drvdata->is_wired) 468 + ret = corsair_void_set_sidetone_wired(dev, buf, sidetone); 469 + else 470 + ret = corsair_void_set_sidetone_wireless(dev, buf, sidetone); 471 + 472 + if (ret < 0) 473 + hid_warn(hid_dev, "failed to send sidetone (reason: %d)", ret); 474 + else 475 + ret = count; 476 + 477 + return ret; 478 + } 479 + 480 + static int corsair_void_request_status(struct hid_device *hid_dev, int id) 481 + { 482 + unsigned char *send_buf __free(kfree) = NULL; 483 + 484 + send_buf = kmalloc(2, GFP_KERNEL); 485 + if (!send_buf) 486 + return -ENOMEM; 487 + 488 + /* Packet format to request data item (status / firmware) refresh */ 489 + send_buf[0] = CORSAIR_VOID_STATUS_REQUEST_ID; 490 + send_buf[1] = id; 491 + 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, 495 + HID_REQ_SET_REPORT); 496 + } 497 + 498 + /* 499 + * Headset connect / disconnect handlers and work handlers 500 + */ 501 + 502 + static void corsair_void_status_work_handler(struct work_struct *work) 503 + { 504 + struct corsair_void_drvdata *drvdata; 505 + struct delayed_work *delayed_work; 506 + int battery_ret; 507 + 508 + delayed_work = container_of(work, struct delayed_work, work); 509 + drvdata = container_of(delayed_work, struct corsair_void_drvdata, 510 + delayed_status_work); 511 + 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); 517 + } 518 + } 519 + 520 + static void corsair_void_firmware_work_handler(struct work_struct *work) 521 + { 522 + struct corsair_void_drvdata *drvdata; 523 + struct delayed_work *delayed_work; 524 + int firmware_ret; 525 + 526 + delayed_work = container_of(work, struct delayed_work, work); 527 + drvdata = container_of(delayed_work, struct corsair_void_drvdata, 528 + delayed_firmware_work); 529 + 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); 535 + } 536 + 537 + } 538 + 539 + static void corsair_void_battery_remove_work_handler(struct work_struct *work) 540 + { 541 + struct corsair_void_drvdata *drvdata; 542 + 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; 549 + } 550 + } 551 + } 552 + 553 + static void corsair_void_battery_add_work_handler(struct work_struct *work) 554 + { 555 + struct corsair_void_drvdata *drvdata; 556 + struct power_supply_config psy_cfg; 557 + struct power_supply *new_supply; 558 + 559 + drvdata = container_of(work, struct corsair_void_drvdata, 560 + battery_add_work); 561 + guard(mutex)(&drvdata->battery_mutex); 562 + if (drvdata->battery) 563 + return; 564 + 565 + psy_cfg.drv_data = drvdata; 566 + new_supply = power_supply_register(drvdata->dev, 567 + &drvdata->battery_desc, 568 + &psy_cfg); 569 + 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)); 575 + return; 576 + } 577 + 578 + if (power_supply_powers(new_supply, drvdata->dev)) { 579 + power_supply_unregister(new_supply); 580 + return; 581 + } 582 + 583 + drvdata->battery = new_supply; 584 + } 585 + 586 + static void corsair_void_headset_connected(struct corsair_void_drvdata *drvdata) 587 + { 588 + schedule_work(&drvdata->battery_add_work); 589 + schedule_delayed_work(&drvdata->delayed_firmware_work, 590 + msecs_to_jiffies(100)); 591 + } 592 + 593 + static void corsair_void_headset_disconnected(struct corsair_void_drvdata *drvdata) 594 + { 595 + schedule_work(&drvdata->battery_remove_work); 596 + 597 + corsair_void_set_unknown_wireless_data(drvdata); 598 + corsair_void_set_unknown_batt(drvdata); 599 + } 600 + 601 + /* 602 + * Driver setup, probing and HID event handling 603 + */ 604 + 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); 609 + 610 + static DEVICE_ATTR_WO(send_alert); 611 + static DEVICE_ATTR_WO(set_sidetone); 612 + 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, 620 + NULL, 621 + }; 622 + 623 + static const struct attribute_group corsair_void_attr_group = { 624 + .attrs = corsair_void_attrs, 625 + }; 626 + 627 + static int corsair_void_probe(struct hid_device *hid_dev, 628 + const struct hid_device_id *hid_id) 629 + { 630 + int ret; 631 + struct corsair_void_drvdata *drvdata; 632 + char *name; 633 + 634 + if (!hid_is_usb(hid_dev)) 635 + return -EINVAL; 636 + 637 + drvdata = devm_kzalloc(&hid_dev->dev, sizeof(*drvdata), 638 + GFP_KERNEL); 639 + if (!drvdata) 640 + return -ENOMEM; 641 + 642 + hid_set_drvdata(hid_dev, drvdata); 643 + dev_set_drvdata(&hid_dev->dev, drvdata); 644 + 645 + drvdata->dev = &hid_dev->dev; 646 + drvdata->hid_dev = hid_dev; 647 + drvdata->is_wired = hid_id->driver_data == CORSAIR_VOID_WIRED; 648 + 649 + drvdata->sidetone_max = CORSAIR_VOID_SIDETONE_MAX_WIRELESS; 650 + if (drvdata->is_wired) 651 + drvdata->sidetone_max = CORSAIR_VOID_SIDETONE_MAX_WIRED; 652 + 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); 657 + 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; 662 + 663 + ret = hid_parse(hid_dev); 664 + if (ret) { 665 + hid_err(hid_dev, "parse failed (reason: %d)\n", ret); 666 + return ret; 667 + } 668 + 669 + name = devm_kasprintf(drvdata->dev, GFP_KERNEL, 670 + "corsair-void-%d-battery", hid_dev->id); 671 + if (!name) 672 + return -ENOMEM; 673 + 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; 679 + 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); 686 + if (ret) 687 + return ret; 688 + 689 + ret = sysfs_create_group(&hid_dev->dev.kobj, &corsair_void_attr_group); 690 + if (ret) 691 + return ret; 692 + 693 + /* Any failures after here will need to call hid_hw_stop */ 694 + ret = hid_hw_start(hid_dev, HID_CONNECT_DEFAULT); 695 + if (ret) { 696 + hid_err(hid_dev, "hid_hw_start failed (reason: %d)\n", ret); 697 + goto failed_after_sysfs; 698 + } 699 + 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)); 705 + 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)); 711 + 712 + return 0; 713 + 714 + failed_after_sysfs: 715 + sysfs_remove_group(&hid_dev->dev.kobj, &corsair_void_attr_group); 716 + return ret; 717 + } 718 + 719 + static void corsair_void_remove(struct hid_device *hid_dev) 720 + { 721 + struct corsair_void_drvdata *drvdata = hid_get_drvdata(hid_dev); 722 + 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); 728 + 729 + cancel_delayed_work_sync(&drvdata->delayed_firmware_work); 730 + sysfs_remove_group(&hid_dev->dev.kobj, &corsair_void_attr_group); 731 + } 732 + 733 + static int corsair_void_raw_event(struct hid_device *hid_dev, 734 + struct hid_report *hid_report, 735 + u8 *data, int size) 736 + { 737 + struct corsair_void_drvdata *drvdata = hid_get_drvdata(hid_dev); 738 + bool was_connected = drvdata->connected; 739 + 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) || 744 + drvdata->is_wired; 745 + 746 + corsair_void_process_receiver(drvdata, 747 + FIELD_GET(CORSAIR_VOID_CAPACITY_MASK, data[2]), 748 + data[3], data[4]); 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]; 754 + } 755 + 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); 760 + else 761 + corsair_void_headset_disconnected(drvdata); 762 + } 763 + 764 + return 0; 765 + } 766 + 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), 774 + 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), 780 + 781 + /* Corsair Void Surround */ 782 + CORSAIR_VOID_WIRED_DEVICE(0x0a30), 783 + CORSAIR_VOID_WIRED_DEVICE(0x0a31), 784 + 785 + /* Corsair Void Pro Wireless */ 786 + CORSAIR_VOID_WIRELESS_DEVICE(0x0a14), 787 + CORSAIR_VOID_WIRELESS_DEVICE(0x0a16), 788 + CORSAIR_VOID_WIRELESS_DEVICE(0x0a1a), 789 + 790 + /* Corsair Void Pro USB */ 791 + CORSAIR_VOID_WIRED_DEVICE(0x0a17), 792 + CORSAIR_VOID_WIRED_DEVICE(0x0a1d), 793 + 794 + /* Corsair Void Pro Surround */ 795 + CORSAIR_VOID_WIRED_DEVICE(0x0a18), 796 + CORSAIR_VOID_WIRED_DEVICE(0x0a1e), 797 + CORSAIR_VOID_WIRED_DEVICE(0x0a1f), 798 + 799 + /* Corsair Void Elite Wireless */ 800 + CORSAIR_VOID_WIRELESS_DEVICE(0x0a51), 801 + CORSAIR_VOID_WIRELESS_DEVICE(0x0a55), 802 + CORSAIR_VOID_WIRELESS_DEVICE(0x0a75), 803 + 804 + /* Corsair Void Elite USB */ 805 + CORSAIR_VOID_WIRED_DEVICE(0x0a52), 806 + CORSAIR_VOID_WIRED_DEVICE(0x0a56), 807 + 808 + /* Corsair Void Elite Surround */ 809 + CORSAIR_VOID_WIRED_DEVICE(0x0a53), 810 + CORSAIR_VOID_WIRED_DEVICE(0x0a57), 811 + 812 + {} 813 + }; 814 + 815 + MODULE_DEVICE_TABLE(hid, corsair_void_devices); 816 + 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, 823 + }; 824 + 825 + module_hid_driver(corsair_void_driver); 826 + 827 + MODULE_LICENSE("GPL"); 828 + MODULE_AUTHOR("Stuart Hayhurst <stuart.a.hayhurst@gmail.com>"); 829 + MODULE_DESCRIPTION("HID driver for Corsair Void headsets");