File Index
Files and directories
Sign in
FILE

hid-corsair-void.c

File preview with syntax highlighting.

linux-6.19.14/drivers/hid/hid-corsair-void.c 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/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
108enum {
109	CORSAIR_VOID_WIRELESS,
110	CORSAIR_VOID_WIRED,
111};
112
113enum {
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
121enum {
122	CORSAIR_VOID_ADD_BATTERY	= 0,
123	CORSAIR_VOID_REMOVE_BATTERY	= 1,
124	CORSAIR_VOID_UPDATE_BATTERY	= 2,
125};
126
127static 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
137struct corsair_void_battery_data {
138	int status;
139	bool present;
140	int capacity;
141	int capacity_level;
142};
143
144struct 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
174static 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(usb_if, drvdata->connected ?
182					USB_WIRELESS_STATUS_CONNECTED :
183					USB_WIRELESS_STATUS_DISCONNECTED);
184}
185
186static 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 */
197static 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
209static 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;
261unknown_battery:
262	corsair_void_set_unknown_batt(drvdata);
263success:
264
265	/* Inform power supply if battery values changed */
266	if (memcmp(&orig_battery_data, battery_data, sizeof(*battery_data))) {
267		set_bit(CORSAIR_VOID_UPDATE_BATTERY,
268			&drvdata->battery_work_flags);
269		schedule_work(&drvdata->battery_work);
270	}
271}
272
273/*
274 * Functions to report stored data
275*/
276
277static 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
315static 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, "%d\n", drvdata->mic_up);
324}
325
326static 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, "%d.%02d\n", drvdata->fw_receiver_major,
336			  drvdata->fw_receiver_minor);
337}
338
339
340static 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, "%d.%02d\n", drvdata->fw_headset_major,
350			  drvdata->fw_headset_minor);
351}
352
353static 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, "%d\n", drvdata->sidetone_max);
360}
361
362/*
363 * Functions to send data to headset
364*/
365
366static 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(buf, 10, &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(hid_dev, CORSAIR_VOID_NOTIF_REQUEST_ID,
393				 send_buf, 3, HID_OUTPUT_REPORT,
394				 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
404static 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(usb_dev, 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				   &sidetone_le, 2, USB_CTRL_SET_TIMEOUT,
419				   GFP_KERNEL);
420}
421
422static 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(hid_dev, CORSAIR_VOID_SIDETONE_REQUEST_ID,
449				  send_buf, 12, HID_FEATURE_REPORT,
450				  HID_REQ_SET_REPORT);
451}
452
453static 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(buf, 10, &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
482static 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(hid_dev, CORSAIR_VOID_STATUS_REQUEST_ID,
496				  send_buf, 2, HID_OUTPUT_REPORT,
497				  HID_REQ_SET_REPORT);
498}
499
500/*
501 * Headset connect / disconnect handlers and work handlers
502*/
503
504static 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(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
522static 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(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
541static 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(drvdata->dev,
551					   &drvdata->battery_desc,
552					   &psy_cfg);
553
554	if (IS_ERR(new_supply)) {
555		hid_err(drvdata->hid_dev,
556			"failed to register battery '%s' (reason: %pe)\n",
557			drvdata->battery_desc.name, new_supply);
558		return;
559	}
560
561	if (power_supply_powers(new_supply, drvdata->dev)) {
562		power_supply_unregister(new_supply);
563		return;
564	}
565
566	drvdata->battery = new_supply;
567}
568
569static void corsair_void_battery_work_handler(struct work_struct *work)
570{
571	struct corsair_void_drvdata *drvdata = container_of(work,
572		struct corsair_void_drvdata, battery_work);
573
574	bool add_battery = test_and_clear_bit(CORSAIR_VOID_ADD_BATTERY,
575					      &drvdata->battery_work_flags);
576	bool remove_battery = test_and_clear_bit(CORSAIR_VOID_REMOVE_BATTERY,
577						 &drvdata->battery_work_flags);
578	bool update_battery = test_and_clear_bit(CORSAIR_VOID_UPDATE_BATTERY,
579						 &drvdata->battery_work_flags);
580
581	if (add_battery && !remove_battery) {
582		corsair_void_add_battery(drvdata);
583	} else if (remove_battery && !add_battery && drvdata->battery) {
584		power_supply_unregister(drvdata->battery);
585		drvdata->battery = NULL;
586	}
587
588	if (update_battery && drvdata->battery)
589		power_supply_changed(drvdata->battery);
590
591}
592
593static void corsair_void_headset_connected(struct corsair_void_drvdata *drvdata)
594{
595	set_bit(CORSAIR_VOID_ADD_BATTERY, &drvdata->battery_work_flags);
596	schedule_work(&drvdata->battery_work);
597	schedule_delayed_work(&drvdata->delayed_firmware_work,
598			      msecs_to_jiffies(100));
599}
600
601static void corsair_void_headset_disconnected(struct corsair_void_drvdata *drvdata)
602{
603	set_bit(CORSAIR_VOID_REMOVE_BATTERY, &drvdata->battery_work_flags);
604	schedule_work(&drvdata->battery_work);
605
606	corsair_void_set_unknown_wireless_data(drvdata);
607	corsair_void_set_unknown_batt(drvdata);
608}
609
610/*
611 * Driver setup, probing and HID event handling
612*/
613
614static DEVICE_ATTR_RO(fw_version_receiver);
615static DEVICE_ATTR_RO(fw_version_headset);
616static DEVICE_ATTR_RO(microphone_up);
617static DEVICE_ATTR_RO(sidetone_max);
618
619static DEVICE_ATTR_WO(send_alert);
620static DEVICE_ATTR_WO(set_sidetone);
621
622static struct attribute *corsair_void_attrs[] = {
623	&dev_attr_fw_version_receiver.attr,
624	&dev_attr_fw_version_headset.attr,
625	&dev_attr_microphone_up.attr,
626	&dev_attr_send_alert.attr,
627	&dev_attr_set_sidetone.attr,
628	&dev_attr_sidetone_max.attr,
629	NULL,
630};
631
632static const struct attribute_group corsair_void_attr_group = {
633	.attrs = corsair_void_attrs,
634};
635
636static int corsair_void_probe(struct hid_device *hid_dev,
637			      const struct hid_device_id *hid_id)
638{
639	int ret;
640	struct corsair_void_drvdata *drvdata;
641	char *name;
642
643	if (!hid_is_usb(hid_dev))
644		return -EINVAL;
645
646	drvdata = devm_kzalloc(&hid_dev->dev, sizeof(*drvdata),
647			       GFP_KERNEL);
648	if (!drvdata)
649		return -ENOMEM;
650
651	hid_set_drvdata(hid_dev, drvdata);
652	dev_set_drvdata(&hid_dev->dev, drvdata);
653
654	drvdata->dev = &hid_dev->dev;
655	drvdata->hid_dev = hid_dev;
656	drvdata->is_wired = hid_id->driver_data == CORSAIR_VOID_WIRED;
657
658	drvdata->sidetone_max = CORSAIR_VOID_SIDETONE_MAX_WIRELESS;
659	if (drvdata->is_wired)
660		drvdata->sidetone_max = CORSAIR_VOID_SIDETONE_MAX_WIRED;
661
662	/* Set initial values for no wireless headset attached */
663	/* If a headset is attached, it'll be prompted later */
664	corsair_void_set_unknown_wireless_data(drvdata);
665	corsair_void_set_unknown_batt(drvdata);
666
667	/* Receiver version won't be reset after init */
668	/* Headset version already set via set_unknown_wireless_data */
669	drvdata->fw_receiver_major = 0;
670	drvdata->fw_receiver_minor = 0;
671
672	ret = hid_parse(hid_dev);
673	if (ret) {
674		hid_err(hid_dev, "parse failed (reason: %d)\n", ret);
675		return ret;
676	}
677
678	name = devm_kasprintf(drvdata->dev, GFP_KERNEL,
679			      "corsair-void-%d-battery", hid_dev->id);
680	if (!name)
681		return -ENOMEM;
682
683	drvdata->battery_desc.name = name;
684	drvdata->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
685	drvdata->battery_desc.properties = corsair_void_battery_props;
686	drvdata->battery_desc.num_properties = ARRAY_SIZE(corsair_void_battery_props);
687	drvdata->battery_desc.get_property = corsair_void_battery_get_property;
688
689	drvdata->battery = NULL;
690	INIT_WORK(&drvdata->battery_work, corsair_void_battery_work_handler);
691
692	ret = sysfs_create_group(&hid_dev->dev.kobj, &corsair_void_attr_group);
693	if (ret)
694		return ret;
695
696	/* Any failures after here will need to call hid_hw_stop */
697	ret = hid_hw_start(hid_dev, HID_CONNECT_DEFAULT);
698	if (ret) {
699		hid_err(hid_dev, "hid_hw_start failed (reason: %d)\n", ret);
700		goto failed_after_sysfs;
701	}
702
703	/* Refresh battery data, in case wireless headset is already connected */
704	INIT_DELAYED_WORK(&drvdata->delayed_status_work,
705			  corsair_void_status_work_handler);
706	schedule_delayed_work(&drvdata->delayed_status_work,
707			      msecs_to_jiffies(100));
708
709	/* Refresh firmware versions */
710	INIT_DELAYED_WORK(&drvdata->delayed_firmware_work,
711			  corsair_void_firmware_work_handler);
712	schedule_delayed_work(&drvdata->delayed_firmware_work,
713			      msecs_to_jiffies(100));
714
715	return 0;
716
717failed_after_sysfs:
718	sysfs_remove_group(&hid_dev->dev.kobj, &corsair_void_attr_group);
719	return ret;
720}
721
722static void corsair_void_remove(struct hid_device *hid_dev)
723{
724	struct corsair_void_drvdata *drvdata = hid_get_drvdata(hid_dev);
725
726	hid_hw_stop(hid_dev);
727	cancel_work_sync(&drvdata->battery_work);
728	if (drvdata->battery)
729		power_supply_unregister(drvdata->battery);
730
731	cancel_delayed_work_sync(&drvdata->delayed_status_work);
732	cancel_delayed_work_sync(&drvdata->delayed_firmware_work);
733	sysfs_remove_group(&hid_dev->dev.kobj, &corsair_void_attr_group);
734}
735
736static int corsair_void_raw_event(struct hid_device *hid_dev,
737				  struct hid_report *hid_report,
738				  u8 *data, int size)
739{
740	struct corsair_void_drvdata *drvdata = hid_get_drvdata(hid_dev);
741	bool was_connected = drvdata->connected;
742
743	/* Description of packets are documented at the top of this file */
744	if (hid_report->id == CORSAIR_VOID_STATUS_REPORT_ID) {
745		drvdata->mic_up = FIELD_GET(CORSAIR_VOID_MIC_MASK, data[2]);
746		drvdata->connected = (data[3] == CORSAIR_VOID_WIRELESS_CONNECTED) ||
747				     drvdata->is_wired;
748
749		corsair_void_process_receiver(drvdata,
750					      FIELD_GET(CORSAIR_VOID_CAPACITY_MASK, data[2]),
751					      data[3], data[4]);
752	} else if (hid_report->id == CORSAIR_VOID_FIRMWARE_REPORT_ID) {
753		drvdata->fw_receiver_major = data[1];
754		drvdata->fw_receiver_minor = data[2];
755		drvdata->fw_headset_major = data[3];
756		drvdata->fw_headset_minor = data[4];
757	}
758
759	/* Handle wireless headset connect / disconnect */
760	if ((was_connected != drvdata->connected) && !drvdata->is_wired) {
761		if (drvdata->connected)
762			corsair_void_headset_connected(drvdata);
763		else
764			corsair_void_headset_disconnected(drvdata);
765	}
766
767	return 0;
768}
769
770static const struct hid_device_id corsair_void_devices[] = {
771	/* Corsair Void Wireless */
772	CORSAIR_VOID_WIRELESS_DEVICE(0x0a0c),
773	CORSAIR_VOID_WIRELESS_DEVICE(0x0a2b),
774	CORSAIR_VOID_WIRELESS_DEVICE(0x1b23),
775	CORSAIR_VOID_WIRELESS_DEVICE(0x1b25),
776	CORSAIR_VOID_WIRELESS_DEVICE(0x1b27),
777
778	/* Corsair Void USB */
779	CORSAIR_VOID_WIRED_DEVICE(0x0a0f),
780	CORSAIR_VOID_WIRED_DEVICE(0x1b1c),
781	CORSAIR_VOID_WIRED_DEVICE(0x1b29),
782	CORSAIR_VOID_WIRED_DEVICE(0x1b2a),
783
784	/* Corsair Void Surround */
785	CORSAIR_VOID_WIRED_DEVICE(0x0a30),
786	CORSAIR_VOID_WIRED_DEVICE(0x0a31),
787
788	/* Corsair Void Pro Wireless */
789	CORSAIR_VOID_WIRELESS_DEVICE(0x0a14),
790	CORSAIR_VOID_WIRELESS_DEVICE(0x0a16),
791	CORSAIR_VOID_WIRELESS_DEVICE(0x0a1a),
792
793	/* Corsair Void Pro USB */
794	CORSAIR_VOID_WIRED_DEVICE(0x0a17),
795	CORSAIR_VOID_WIRED_DEVICE(0x0a1d),
796
797	/* Corsair Void Pro Surround */
798	CORSAIR_VOID_WIRED_DEVICE(0x0a18),
799	CORSAIR_VOID_WIRED_DEVICE(0x0a1e),
800	CORSAIR_VOID_WIRED_DEVICE(0x0a1f),
801
802	/* Corsair Void Elite Wireless */
803	CORSAIR_VOID_WIRELESS_DEVICE(0x0a51),
804	CORSAIR_VOID_WIRELESS_DEVICE(0x0a55),
805	CORSAIR_VOID_WIRELESS_DEVICE(0x0a75),
806
807	/* Corsair Void Elite USB */
808	CORSAIR_VOID_WIRED_DEVICE(0x0a52),
809	CORSAIR_VOID_WIRED_DEVICE(0x0a56),
810
811	/* Corsair Void Elite Surround */
812	CORSAIR_VOID_WIRED_DEVICE(0x0a53),
813	CORSAIR_VOID_WIRED_DEVICE(0x0a57),
814
815	{}
816};
817
818MODULE_DEVICE_TABLE(hid, corsair_void_devices);
819
820static struct hid_driver corsair_void_driver = {
821	.name = "hid-corsair-void",
822	.id_table = corsair_void_devices,
823	.probe = corsair_void_probe,
824	.remove = corsair_void_remove,
825	.raw_event = corsair_void_raw_event,
826};
827
828module_hid_driver(corsair_void_driver);
829
830MODULE_LICENSE("GPL");
831MODULE_AUTHOR("Stuart Hayhurst <stuart.a.hayhurst@gmail.com>");
832MODULE_DESCRIPTION("HID driver for Corsair Void headsets");
~/ Read-only file preview.