mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	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>
This commit is contained in:
		
							parent
							
								
									f23aa4c076
								
							
						
					
					
						commit
						6ea2a6fd38
					
				
					 4 changed files with 871 additions and 1 deletions
				
			
		
							
								
								
									
										38
									
								
								Documentation/ABI/testing/sysfs-driver-hid-corsair-void
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								Documentation/ABI/testing/sysfs-driver-hid-corsair-void
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,38 @@
 | 
				
			||||||
 | 
					What:		/sys/bus/hid/drivers/hid-corsair-void/<dev>/fw_version_headset
 | 
				
			||||||
 | 
					Date:		January 2024
 | 
				
			||||||
 | 
					KernelVersion:	6.13
 | 
				
			||||||
 | 
					Contact:	Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
 | 
				
			||||||
 | 
					Description:	(R) The firmware version of the headset
 | 
				
			||||||
 | 
								* Returns -ENODATA if no version was reported
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/hid/drivers/hid-corsair-void/<dev>/fw_version_receiver
 | 
				
			||||||
 | 
					Date:		January 2024
 | 
				
			||||||
 | 
					KernelVersion:	6.13
 | 
				
			||||||
 | 
					Contact:	Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
 | 
				
			||||||
 | 
					Description:	(R) The firmware version of the receiver
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/hid/drivers/hid-corsair-void/<dev>/microphone_up
 | 
				
			||||||
 | 
					Date:		July 2023
 | 
				
			||||||
 | 
					KernelVersion:	6.13
 | 
				
			||||||
 | 
					Contact:	Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
 | 
				
			||||||
 | 
					Description:	(R) Get the physical position of the microphone
 | 
				
			||||||
 | 
								* 1 -> Microphone up
 | 
				
			||||||
 | 
								* 0 -> Microphone down
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/hid/drivers/hid-corsair-void/<dev>/send_alert
 | 
				
			||||||
 | 
					Date:		July 2023
 | 
				
			||||||
 | 
					KernelVersion:	6.13
 | 
				
			||||||
 | 
					Contact:	Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
 | 
				
			||||||
 | 
					Description:	(W) Play a built-in notification from the headset (0 / 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/hid/drivers/hid-corsair-void/<dev>/set_sidetone
 | 
				
			||||||
 | 
					Date:		December 2023
 | 
				
			||||||
 | 
					KernelVersion:	6.13
 | 
				
			||||||
 | 
					Contact:	Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
 | 
				
			||||||
 | 
					Description:	(W) Set the sidetone volume (0 - sidetone_max)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What:		/sys/bus/hid/drivers/hid-corsair-void/<dev>/sidetone_max
 | 
				
			||||||
 | 
					Date:		July 2024
 | 
				
			||||||
 | 
					KernelVersion:	6.13
 | 
				
			||||||
 | 
					Contact:	Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
 | 
				
			||||||
 | 
					Description:	(R) Report the maximum sidetone volume
 | 
				
			||||||
| 
						 | 
					@ -213,13 +213,16 @@ config HID_CHICONY
 | 
				
			||||||
config HID_CORSAIR
 | 
					config HID_CORSAIR
 | 
				
			||||||
	tristate "Corsair devices"
 | 
						tristate "Corsair devices"
 | 
				
			||||||
	depends on USB_HID && LEDS_CLASS
 | 
						depends on USB_HID && LEDS_CLASS
 | 
				
			||||||
 | 
						select POWER_SUPPLY
 | 
				
			||||||
	help
 | 
						help
 | 
				
			||||||
	Support for Corsair devices that are not fully compliant with the
 | 
						Support for Corsair devices that are not fully compliant with the
 | 
				
			||||||
	HID standard.
 | 
						HID standard.
 | 
				
			||||||
 | 
						Support for Corsair Void headsets.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Supported devices:
 | 
						Supported devices:
 | 
				
			||||||
	- Vengeance K90
 | 
						- Vengeance K90
 | 
				
			||||||
	- Scimitar PRO RGB
 | 
						- Scimitar PRO RGB
 | 
				
			||||||
 | 
						- Corsair Void headsets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config HID_COUGAR
 | 
					config HID_COUGAR
 | 
				
			||||||
	tristate "Cougar devices"
 | 
						tristate "Cougar devices"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,7 +38,7 @@ obj-$(CONFIG_HID_BIGBEN_FF)	+= hid-bigbenff.o
 | 
				
			||||||
obj-$(CONFIG_HID_CHERRY)	+= hid-cherry.o
 | 
					obj-$(CONFIG_HID_CHERRY)	+= hid-cherry.o
 | 
				
			||||||
obj-$(CONFIG_HID_CHICONY)	+= hid-chicony.o
 | 
					obj-$(CONFIG_HID_CHICONY)	+= hid-chicony.o
 | 
				
			||||||
obj-$(CONFIG_HID_CMEDIA)	+= hid-cmedia.o
 | 
					obj-$(CONFIG_HID_CMEDIA)	+= hid-cmedia.o
 | 
				
			||||||
obj-$(CONFIG_HID_CORSAIR)	+= hid-corsair.o
 | 
					obj-$(CONFIG_HID_CORSAIR)	+= hid-corsair.o hid-corsair-void.o
 | 
				
			||||||
obj-$(CONFIG_HID_COUGAR)	+= hid-cougar.o
 | 
					obj-$(CONFIG_HID_COUGAR)	+= hid-cougar.o
 | 
				
			||||||
obj-$(CONFIG_HID_CP2112)	+= hid-cp2112.o
 | 
					obj-$(CONFIG_HID_CP2112)	+= hid-cp2112.o
 | 
				
			||||||
obj-$(CONFIG_HID_CYPRESS)	+= hid-cypress.o
 | 
					obj-$(CONFIG_HID_CYPRESS)	+= hid-cypress.o
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										829
									
								
								drivers/hid/hid-corsair-void.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										829
									
								
								drivers/hid/hid-corsair-void.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,829 @@
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: GPL-2.0-or-later
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  HID driver for Corsair Void headsets
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *  Copyright (C) 2023-2024 Stuart Hayhurst
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -------------------------------------------------------------------------- */
 | 
				
			||||||
 | 
					/* Receiver report information: (ID 100)                                      */
 | 
				
			||||||
 | 
					/* -------------------------------------------------------------------------- */
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * When queried, the receiver reponds with 5 bytes to describe the battery
 | 
				
			||||||
 | 
					 *   The power button, mute button and moving the mic also trigger this report
 | 
				
			||||||
 | 
					 * This includes power button + mic + connection + battery status and capacity
 | 
				
			||||||
 | 
					 * The information below may not be perfect, it's been gathered through guesses
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 0: REPORT ID
 | 
				
			||||||
 | 
					 *  100 for the battery packet
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 1: POWER BUTTON + (?)
 | 
				
			||||||
 | 
					 *  Largest bit is 1 when power button pressed
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 2: BATTERY CAPACITY + MIC STATUS
 | 
				
			||||||
 | 
					 *  Battery capacity:
 | 
				
			||||||
 | 
					 *    Seems to report ~54 higher than reality when charging
 | 
				
			||||||
 | 
					 *    Capped at 100, charging or not
 | 
				
			||||||
 | 
					 *  Microphone status:
 | 
				
			||||||
 | 
					 *    Largest bit is set to 1 when the mic is physically up
 | 
				
			||||||
 | 
					 *    No bits change when the mic is muted, only when physically moved
 | 
				
			||||||
 | 
					 *    This report is sent every time the mic is moved, no polling required
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 3: CONNECTION STATUS
 | 
				
			||||||
 | 
					 *  16: Wired headset
 | 
				
			||||||
 | 
					 *  38: Initialising
 | 
				
			||||||
 | 
					 *  49: Lost connection
 | 
				
			||||||
 | 
					 *  51: Disconnected, searching
 | 
				
			||||||
 | 
					 *  52: Disconnected, not searching
 | 
				
			||||||
 | 
					 *  177: Normal
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 4: BATTERY STATUS
 | 
				
			||||||
 | 
					 *  0: Disconnected
 | 
				
			||||||
 | 
					 *  1: Normal
 | 
				
			||||||
 | 
					 *  2: Low
 | 
				
			||||||
 | 
					 *  3: Critical - sent during shutdown
 | 
				
			||||||
 | 
					 *  4: Fully charged
 | 
				
			||||||
 | 
					 *  5: Charging
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					/* -------------------------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -------------------------------------------------------------------------- */
 | 
				
			||||||
 | 
					/* Receiver report information: (ID 102)                                      */
 | 
				
			||||||
 | 
					/* -------------------------------------------------------------------------- */
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * When queried, the recevier responds with 4 bytes to describe the firmware
 | 
				
			||||||
 | 
					 * The first 2 bytes are for the receiver, the second 2 are the headset
 | 
				
			||||||
 | 
					 * The headset firmware version will be 0 if no headset is connected
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 0: Recevier firmware major version
 | 
				
			||||||
 | 
					 *  Major version of the receiver's firmware
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 1: Recevier firmware minor version
 | 
				
			||||||
 | 
					 *  Minor version of the receiver's firmware
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 2: Headset firmware major version
 | 
				
			||||||
 | 
					 *  Major version of the headset's firmware
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 3: Headset firmware minor version
 | 
				
			||||||
 | 
					 *  Minor version of the headset's firmware
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					/* -------------------------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <linux/bitfield.h>
 | 
				
			||||||
 | 
					#include <linux/bitops.h>
 | 
				
			||||||
 | 
					#include <linux/cleanup.h>
 | 
				
			||||||
 | 
					#include <linux/device.h>
 | 
				
			||||||
 | 
					#include <linux/hid.h>
 | 
				
			||||||
 | 
					#include <linux/module.h>
 | 
				
			||||||
 | 
					#include <linux/mutex.h>
 | 
				
			||||||
 | 
					#include <linux/power_supply.h>
 | 
				
			||||||
 | 
					#include <linux/usb.h>
 | 
				
			||||||
 | 
					#include <linux/workqueue.h>
 | 
				
			||||||
 | 
					#include <asm/byteorder.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "hid-ids.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_DEVICE(id, type)		{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, (id)), \
 | 
				
			||||||
 | 
											.driver_data = (type) }
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_WIRELESS_DEVICE(id)	CORSAIR_VOID_DEVICE((id), CORSAIR_VOID_WIRELESS)
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_WIRED_DEVICE(id)		CORSAIR_VOID_DEVICE((id), CORSAIR_VOID_WIRED)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_STATUS_REQUEST_ID		0xC9
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_NOTIF_REQUEST_ID		0xCA
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_SIDETONE_REQUEST_ID	0xFF
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_STATUS_REPORT_ID		0x64
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_FIRMWARE_REPORT_ID		0x66
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_USB_SIDETONE_REQUEST	0x1
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_USB_SIDETONE_REQUEST_TYPE	0x21
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_USB_SIDETONE_VALUE		0x200
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_USB_SIDETONE_INDEX		0xB00
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_MIC_MASK			GENMASK(7, 7)
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_CAPACITY_MASK		GENMASK(6, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_WIRELESS_CONNECTED		177
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_SIDETONE_MAX_WIRELESS	55
 | 
				
			||||||
 | 
					#define CORSAIR_VOID_SIDETONE_MAX_WIRED		4096
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum {
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRELESS,
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum {
 | 
				
			||||||
 | 
						CORSAIR_VOID_BATTERY_NORMAL	= 1,
 | 
				
			||||||
 | 
						CORSAIR_VOID_BATTERY_LOW	= 2,
 | 
				
			||||||
 | 
						CORSAIR_VOID_BATTERY_CRITICAL	= 3,
 | 
				
			||||||
 | 
						CORSAIR_VOID_BATTERY_CHARGED	= 4,
 | 
				
			||||||
 | 
						CORSAIR_VOID_BATTERY_CHARGING	= 5,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static enum power_supply_property corsair_void_battery_props[] = {
 | 
				
			||||||
 | 
						POWER_SUPPLY_PROP_STATUS,
 | 
				
			||||||
 | 
						POWER_SUPPLY_PROP_PRESENT,
 | 
				
			||||||
 | 
						POWER_SUPPLY_PROP_CAPACITY,
 | 
				
			||||||
 | 
						POWER_SUPPLY_PROP_CAPACITY_LEVEL,
 | 
				
			||||||
 | 
						POWER_SUPPLY_PROP_SCOPE,
 | 
				
			||||||
 | 
						POWER_SUPPLY_PROP_MODEL_NAME,
 | 
				
			||||||
 | 
						POWER_SUPPLY_PROP_MANUFACTURER,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct corsair_void_battery_data {
 | 
				
			||||||
 | 
						int status;
 | 
				
			||||||
 | 
						bool present;
 | 
				
			||||||
 | 
						int capacity;
 | 
				
			||||||
 | 
						int capacity_level;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct corsair_void_drvdata {
 | 
				
			||||||
 | 
						struct hid_device *hid_dev;
 | 
				
			||||||
 | 
						struct device *dev;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						char *name;
 | 
				
			||||||
 | 
						bool is_wired;
 | 
				
			||||||
 | 
						unsigned int sidetone_max;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct corsair_void_battery_data battery_data;
 | 
				
			||||||
 | 
						bool mic_up;
 | 
				
			||||||
 | 
						bool connected;
 | 
				
			||||||
 | 
						int fw_receiver_major;
 | 
				
			||||||
 | 
						int fw_receiver_minor;
 | 
				
			||||||
 | 
						int fw_headset_major;
 | 
				
			||||||
 | 
						int fw_headset_minor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct power_supply *battery;
 | 
				
			||||||
 | 
						struct power_supply_desc battery_desc;
 | 
				
			||||||
 | 
						struct mutex battery_mutex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct delayed_work delayed_status_work;
 | 
				
			||||||
 | 
						struct delayed_work delayed_firmware_work;
 | 
				
			||||||
 | 
						struct work_struct battery_remove_work;
 | 
				
			||||||
 | 
						struct work_struct battery_add_work;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Functions to process receiver data
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void corsair_void_set_wireless_status(struct corsair_void_drvdata *drvdata)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct usb_interface *usb_if = to_usb_interface(drvdata->dev->parent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (drvdata->is_wired)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						usb_set_wireless_status(usb_if, drvdata->connected ?
 | 
				
			||||||
 | 
										USB_WIRELESS_STATUS_CONNECTED :
 | 
				
			||||||
 | 
										USB_WIRELESS_STATUS_DISCONNECTED);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void corsair_void_set_unknown_batt(struct corsair_void_drvdata *drvdata)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_battery_data *battery_data = &drvdata->battery_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						battery_data->status = POWER_SUPPLY_STATUS_UNKNOWN;
 | 
				
			||||||
 | 
						battery_data->present = false;
 | 
				
			||||||
 | 
						battery_data->capacity = 0;
 | 
				
			||||||
 | 
						battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Reset data that may change between wireless connections */
 | 
				
			||||||
 | 
					static void corsair_void_set_unknown_wireless_data(struct corsair_void_drvdata *drvdata)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						/* Only 0 out headset, receiver is always known if relevant */
 | 
				
			||||||
 | 
						drvdata->fw_headset_major = 0;
 | 
				
			||||||
 | 
						drvdata->fw_headset_minor = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata->connected = false;
 | 
				
			||||||
 | 
						drvdata->mic_up = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						corsair_void_set_wireless_status(drvdata);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void corsair_void_process_receiver(struct corsair_void_drvdata *drvdata,
 | 
				
			||||||
 | 
										  int raw_battery_capacity,
 | 
				
			||||||
 | 
										  int raw_connection_status,
 | 
				
			||||||
 | 
										  int raw_battery_status)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_battery_data *battery_data = &drvdata->battery_data;
 | 
				
			||||||
 | 
						struct corsair_void_battery_data orig_battery_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Save initial battery data, to compare later */
 | 
				
			||||||
 | 
						orig_battery_data = *battery_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Headset not connected, or it's wired */
 | 
				
			||||||
 | 
						if (raw_connection_status != CORSAIR_VOID_WIRELESS_CONNECTED)
 | 
				
			||||||
 | 
							goto unknown_battery;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Battery information unavailable */
 | 
				
			||||||
 | 
						if (raw_battery_status == 0)
 | 
				
			||||||
 | 
							goto unknown_battery;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Battery must be connected then */
 | 
				
			||||||
 | 
						battery_data->present = true;
 | 
				
			||||||
 | 
						battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Set battery status */
 | 
				
			||||||
 | 
						switch (raw_battery_status) {
 | 
				
			||||||
 | 
						case CORSAIR_VOID_BATTERY_NORMAL:
 | 
				
			||||||
 | 
						case CORSAIR_VOID_BATTERY_LOW:
 | 
				
			||||||
 | 
						case CORSAIR_VOID_BATTERY_CRITICAL:
 | 
				
			||||||
 | 
							battery_data->status = POWER_SUPPLY_STATUS_DISCHARGING;
 | 
				
			||||||
 | 
							if (raw_battery_status == CORSAIR_VOID_BATTERY_LOW)
 | 
				
			||||||
 | 
								battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
 | 
				
			||||||
 | 
							else if (raw_battery_status == CORSAIR_VOID_BATTERY_CRITICAL)
 | 
				
			||||||
 | 
								battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case CORSAIR_VOID_BATTERY_CHARGED:
 | 
				
			||||||
 | 
							battery_data->status = POWER_SUPPLY_STATUS_FULL;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case CORSAIR_VOID_BATTERY_CHARGING:
 | 
				
			||||||
 | 
							battery_data->status = POWER_SUPPLY_STATUS_CHARGING;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							hid_warn(drvdata->hid_dev, "unknown battery status '%d'",
 | 
				
			||||||
 | 
								 raw_battery_status);
 | 
				
			||||||
 | 
							goto unknown_battery;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						battery_data->capacity = raw_battery_capacity;
 | 
				
			||||||
 | 
						corsair_void_set_wireless_status(drvdata);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						goto success;
 | 
				
			||||||
 | 
					unknown_battery:
 | 
				
			||||||
 | 
						corsair_void_set_unknown_batt(drvdata);
 | 
				
			||||||
 | 
					success:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Inform power supply if battery values changed */
 | 
				
			||||||
 | 
						if (memcmp(&orig_battery_data, battery_data, sizeof(*battery_data))) {
 | 
				
			||||||
 | 
							scoped_guard(mutex, &drvdata->battery_mutex) {
 | 
				
			||||||
 | 
								if (drvdata->battery) {
 | 
				
			||||||
 | 
									power_supply_changed(drvdata->battery);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Functions to report stored data
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int corsair_void_battery_get_property(struct power_supply *psy,
 | 
				
			||||||
 | 
										     enum power_supply_property prop,
 | 
				
			||||||
 | 
										     union power_supply_propval *val)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata = power_supply_get_drvdata(psy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (prop) {
 | 
				
			||||||
 | 
							case POWER_SUPPLY_PROP_SCOPE:
 | 
				
			||||||
 | 
								val->intval = POWER_SUPPLY_SCOPE_DEVICE;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case POWER_SUPPLY_PROP_MODEL_NAME:
 | 
				
			||||||
 | 
								if (!strncmp(drvdata->hid_dev->name, "Corsair ", 8))
 | 
				
			||||||
 | 
									val->strval = drvdata->hid_dev->name + 8;
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									val->strval = drvdata->hid_dev->name;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case POWER_SUPPLY_PROP_MANUFACTURER:
 | 
				
			||||||
 | 
								val->strval = "Corsair";
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case POWER_SUPPLY_PROP_STATUS:
 | 
				
			||||||
 | 
								val->intval = drvdata->battery_data.status;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case POWER_SUPPLY_PROP_PRESENT:
 | 
				
			||||||
 | 
								val->intval = drvdata->battery_data.present;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case POWER_SUPPLY_PROP_CAPACITY:
 | 
				
			||||||
 | 
								val->intval = drvdata->battery_data.capacity;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
 | 
				
			||||||
 | 
								val->intval = drvdata->battery_data.capacity_level;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								return -EINVAL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t microphone_up_show(struct device *dev,
 | 
				
			||||||
 | 
									  struct device_attribute *attr, char *buf)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!drvdata->connected)
 | 
				
			||||||
 | 
							return -ENODEV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sysfs_emit(buf, "%d\n", drvdata->mic_up);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t fw_version_receiver_show(struct device *dev,
 | 
				
			||||||
 | 
										struct device_attribute *attr,
 | 
				
			||||||
 | 
										char *buf)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (drvdata->fw_receiver_major == 0 && drvdata->fw_receiver_minor == 0)
 | 
				
			||||||
 | 
							return -ENODATA;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sysfs_emit(buf, "%d.%02d\n", drvdata->fw_receiver_major,
 | 
				
			||||||
 | 
								  drvdata->fw_receiver_minor);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t fw_version_headset_show(struct device *dev,
 | 
				
			||||||
 | 
									       struct device_attribute *attr,
 | 
				
			||||||
 | 
									       char *buf)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (drvdata->fw_headset_major == 0 && drvdata->fw_headset_minor == 0)
 | 
				
			||||||
 | 
							return -ENODATA;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sysfs_emit(buf, "%d.%02d\n", drvdata->fw_headset_major,
 | 
				
			||||||
 | 
								  drvdata->fw_headset_minor);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t sidetone_max_show(struct device *dev,
 | 
				
			||||||
 | 
									 struct device_attribute *attr,
 | 
				
			||||||
 | 
									 char *buf)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sysfs_emit(buf, "%d\n", drvdata->sidetone_max);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Functions to send data to headset
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t send_alert_store(struct device *dev,
 | 
				
			||||||
 | 
									struct device_attribute *attr,
 | 
				
			||||||
 | 
									const char *buf, size_t count)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
						struct hid_device *hid_dev = drvdata->hid_dev;
 | 
				
			||||||
 | 
						unsigned char alert_id;
 | 
				
			||||||
 | 
						unsigned char *send_buf __free(kfree) = NULL;
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!drvdata->connected || drvdata->is_wired)
 | 
				
			||||||
 | 
							return -ENODEV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Only accept 0 or 1 for alert ID */
 | 
				
			||||||
 | 
						if (kstrtou8(buf, 10, &alert_id) || alert_id >= 2)
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						send_buf = kmalloc(3, GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!send_buf)
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Packet format to send alert with ID alert_id */
 | 
				
			||||||
 | 
						send_buf[0] = CORSAIR_VOID_NOTIF_REQUEST_ID;
 | 
				
			||||||
 | 
						send_buf[1] = 0x02;
 | 
				
			||||||
 | 
						send_buf[2] = alert_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = hid_hw_raw_request(hid_dev, CORSAIR_VOID_NOTIF_REQUEST_ID,
 | 
				
			||||||
 | 
									 send_buf, 3, HID_OUTPUT_REPORT,
 | 
				
			||||||
 | 
									 HID_REQ_SET_REPORT);
 | 
				
			||||||
 | 
						if (ret < 0)
 | 
				
			||||||
 | 
							hid_warn(hid_dev, "failed to send alert request (reason: %d)",
 | 
				
			||||||
 | 
								 ret);
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							ret = count;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int corsair_void_set_sidetone_wired(struct device *dev, const char *buf,
 | 
				
			||||||
 | 
										   unsigned int sidetone)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct usb_interface *usb_if = to_usb_interface(dev->parent);
 | 
				
			||||||
 | 
						struct usb_device *usb_dev = interface_to_usbdev(usb_if);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Packet format to set sidetone for wired headsets */
 | 
				
			||||||
 | 
						__le16 sidetone_le = cpu_to_le16(sidetone);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return usb_control_msg_send(usb_dev, 0,
 | 
				
			||||||
 | 
									   CORSAIR_VOID_USB_SIDETONE_REQUEST,
 | 
				
			||||||
 | 
									   CORSAIR_VOID_USB_SIDETONE_REQUEST_TYPE,
 | 
				
			||||||
 | 
									   CORSAIR_VOID_USB_SIDETONE_VALUE,
 | 
				
			||||||
 | 
									   CORSAIR_VOID_USB_SIDETONE_INDEX,
 | 
				
			||||||
 | 
									   &sidetone_le, 2, USB_CTRL_SET_TIMEOUT,
 | 
				
			||||||
 | 
									   GFP_KERNEL);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int corsair_void_set_sidetone_wireless(struct device *dev,
 | 
				
			||||||
 | 
										      const char *buf,
 | 
				
			||||||
 | 
										      unsigned char sidetone)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
						struct hid_device *hid_dev = drvdata->hid_dev;
 | 
				
			||||||
 | 
						unsigned char *send_buf __free(kfree) = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						send_buf = kmalloc(12, GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!send_buf)
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Packet format to set sidetone for wireless headsets */
 | 
				
			||||||
 | 
						send_buf[0] = CORSAIR_VOID_SIDETONE_REQUEST_ID;
 | 
				
			||||||
 | 
						send_buf[1] = 0x0B;
 | 
				
			||||||
 | 
						send_buf[2] = 0x00;
 | 
				
			||||||
 | 
						send_buf[3] = 0xFF;
 | 
				
			||||||
 | 
						send_buf[4] = 0x04;
 | 
				
			||||||
 | 
						send_buf[5] = 0x0E;
 | 
				
			||||||
 | 
						send_buf[6] = 0xFF;
 | 
				
			||||||
 | 
						send_buf[7] = 0x05;
 | 
				
			||||||
 | 
						send_buf[8] = 0x01;
 | 
				
			||||||
 | 
						send_buf[9] = 0x04;
 | 
				
			||||||
 | 
						send_buf[10] = 0x00;
 | 
				
			||||||
 | 
						send_buf[11] = sidetone + 200;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return hid_hw_raw_request(hid_dev, CORSAIR_VOID_SIDETONE_REQUEST_ID,
 | 
				
			||||||
 | 
									  send_buf, 12, HID_FEATURE_REPORT,
 | 
				
			||||||
 | 
									  HID_REQ_SET_REPORT);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ssize_t set_sidetone_store(struct device *dev,
 | 
				
			||||||
 | 
									  struct device_attribute *attr,
 | 
				
			||||||
 | 
									  const char *buf, size_t count)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
						struct hid_device *hid_dev = drvdata->hid_dev;
 | 
				
			||||||
 | 
						unsigned int sidetone;
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!drvdata->connected)
 | 
				
			||||||
 | 
							return -ENODEV;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* sidetone must be between 0 and drvdata->sidetone_max inclusive */
 | 
				
			||||||
 | 
						if (kstrtouint(buf, 10, &sidetone) || sidetone > drvdata->sidetone_max)
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (drvdata->is_wired)
 | 
				
			||||||
 | 
							ret = corsair_void_set_sidetone_wired(dev, buf, sidetone);
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							ret = corsair_void_set_sidetone_wireless(dev, buf, sidetone);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ret < 0)
 | 
				
			||||||
 | 
							hid_warn(hid_dev, "failed to send sidetone (reason: %d)", ret);
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							ret = count;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int corsair_void_request_status(struct hid_device *hid_dev, int id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						unsigned char *send_buf __free(kfree) = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						send_buf = kmalloc(2, GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!send_buf)
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Packet format to request data item (status / firmware) refresh */
 | 
				
			||||||
 | 
						send_buf[0] = CORSAIR_VOID_STATUS_REQUEST_ID;
 | 
				
			||||||
 | 
						send_buf[1] = id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Send request for data refresh */
 | 
				
			||||||
 | 
						return hid_hw_raw_request(hid_dev, CORSAIR_VOID_STATUS_REQUEST_ID,
 | 
				
			||||||
 | 
									  send_buf, 2, HID_OUTPUT_REPORT,
 | 
				
			||||||
 | 
									  HID_REQ_SET_REPORT);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Headset connect / disconnect handlers and work handlers
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void corsair_void_status_work_handler(struct work_struct *work)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata;
 | 
				
			||||||
 | 
						struct delayed_work *delayed_work;
 | 
				
			||||||
 | 
						int battery_ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						delayed_work = container_of(work, struct delayed_work, work);
 | 
				
			||||||
 | 
						drvdata = container_of(delayed_work, struct corsair_void_drvdata,
 | 
				
			||||||
 | 
								       delayed_status_work);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						battery_ret = corsair_void_request_status(drvdata->hid_dev,
 | 
				
			||||||
 | 
											  CORSAIR_VOID_STATUS_REPORT_ID);
 | 
				
			||||||
 | 
						if (battery_ret < 0) {
 | 
				
			||||||
 | 
							hid_warn(drvdata->hid_dev,
 | 
				
			||||||
 | 
								 "failed to request battery (reason: %d)", battery_ret);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void corsair_void_firmware_work_handler(struct work_struct *work)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata;
 | 
				
			||||||
 | 
						struct delayed_work *delayed_work;
 | 
				
			||||||
 | 
						int firmware_ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						delayed_work = container_of(work, struct delayed_work, work);
 | 
				
			||||||
 | 
						drvdata = container_of(delayed_work, struct corsair_void_drvdata,
 | 
				
			||||||
 | 
								       delayed_firmware_work);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						firmware_ret = corsair_void_request_status(drvdata->hid_dev,
 | 
				
			||||||
 | 
											   CORSAIR_VOID_FIRMWARE_REPORT_ID);
 | 
				
			||||||
 | 
						if (firmware_ret < 0) {
 | 
				
			||||||
 | 
							hid_warn(drvdata->hid_dev,
 | 
				
			||||||
 | 
								 "failed to request firmware (reason: %d)", firmware_ret);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void corsair_void_battery_remove_work_handler(struct work_struct *work)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata = container_of(work, struct corsair_void_drvdata,
 | 
				
			||||||
 | 
								       battery_remove_work);
 | 
				
			||||||
 | 
						scoped_guard(mutex, &drvdata->battery_mutex) {
 | 
				
			||||||
 | 
							if (drvdata->battery) {
 | 
				
			||||||
 | 
								power_supply_unregister(drvdata->battery);
 | 
				
			||||||
 | 
								drvdata->battery = NULL;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void corsair_void_battery_add_work_handler(struct work_struct *work)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata;
 | 
				
			||||||
 | 
						struct power_supply_config psy_cfg;
 | 
				
			||||||
 | 
						struct power_supply *new_supply;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata = container_of(work, struct corsair_void_drvdata,
 | 
				
			||||||
 | 
								       battery_add_work);
 | 
				
			||||||
 | 
						guard(mutex)(&drvdata->battery_mutex);
 | 
				
			||||||
 | 
						if (drvdata->battery)
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						psy_cfg.drv_data = drvdata;
 | 
				
			||||||
 | 
						new_supply = power_supply_register(drvdata->dev,
 | 
				
			||||||
 | 
										   &drvdata->battery_desc,
 | 
				
			||||||
 | 
										   &psy_cfg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (IS_ERR(new_supply)) {
 | 
				
			||||||
 | 
							hid_err(drvdata->hid_dev,
 | 
				
			||||||
 | 
								"failed to register battery '%s' (reason: %ld)\n",
 | 
				
			||||||
 | 
								drvdata->battery_desc.name,
 | 
				
			||||||
 | 
								PTR_ERR(new_supply));
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (power_supply_powers(new_supply, drvdata->dev)) {
 | 
				
			||||||
 | 
							power_supply_unregister(new_supply);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata->battery = new_supply;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void corsair_void_headset_connected(struct corsair_void_drvdata *drvdata)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						schedule_work(&drvdata->battery_add_work);
 | 
				
			||||||
 | 
						schedule_delayed_work(&drvdata->delayed_firmware_work,
 | 
				
			||||||
 | 
								      msecs_to_jiffies(100));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void corsair_void_headset_disconnected(struct corsair_void_drvdata *drvdata)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						schedule_work(&drvdata->battery_remove_work);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						corsair_void_set_unknown_wireless_data(drvdata);
 | 
				
			||||||
 | 
						corsair_void_set_unknown_batt(drvdata);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Driver setup, probing and HID event handling
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static DEVICE_ATTR_RO(fw_version_receiver);
 | 
				
			||||||
 | 
					static DEVICE_ATTR_RO(fw_version_headset);
 | 
				
			||||||
 | 
					static DEVICE_ATTR_RO(microphone_up);
 | 
				
			||||||
 | 
					static DEVICE_ATTR_RO(sidetone_max);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static DEVICE_ATTR_WO(send_alert);
 | 
				
			||||||
 | 
					static DEVICE_ATTR_WO(set_sidetone);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct attribute *corsair_void_attrs[] = {
 | 
				
			||||||
 | 
						&dev_attr_fw_version_receiver.attr,
 | 
				
			||||||
 | 
						&dev_attr_fw_version_headset.attr,
 | 
				
			||||||
 | 
						&dev_attr_microphone_up.attr,
 | 
				
			||||||
 | 
						&dev_attr_send_alert.attr,
 | 
				
			||||||
 | 
						&dev_attr_set_sidetone.attr,
 | 
				
			||||||
 | 
						&dev_attr_sidetone_max.attr,
 | 
				
			||||||
 | 
						NULL,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct attribute_group corsair_void_attr_group = {
 | 
				
			||||||
 | 
						.attrs = corsair_void_attrs,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int corsair_void_probe(struct hid_device *hid_dev,
 | 
				
			||||||
 | 
								      const struct hid_device_id *hid_id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata;
 | 
				
			||||||
 | 
						char *name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!hid_is_usb(hid_dev))
 | 
				
			||||||
 | 
							return -EINVAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata = devm_kzalloc(&hid_dev->dev, sizeof(*drvdata),
 | 
				
			||||||
 | 
								       GFP_KERNEL);
 | 
				
			||||||
 | 
						if (!drvdata)
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hid_set_drvdata(hid_dev, drvdata);
 | 
				
			||||||
 | 
						dev_set_drvdata(&hid_dev->dev, drvdata);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata->dev = &hid_dev->dev;
 | 
				
			||||||
 | 
						drvdata->hid_dev = hid_dev;
 | 
				
			||||||
 | 
						drvdata->is_wired = hid_id->driver_data == CORSAIR_VOID_WIRED;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata->sidetone_max = CORSAIR_VOID_SIDETONE_MAX_WIRELESS;
 | 
				
			||||||
 | 
						if (drvdata->is_wired)
 | 
				
			||||||
 | 
							drvdata->sidetone_max = CORSAIR_VOID_SIDETONE_MAX_WIRED;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Set initial values for no wireless headset attached */
 | 
				
			||||||
 | 
						/* If a headset is attached, it'll be prompted later */
 | 
				
			||||||
 | 
						corsair_void_set_unknown_wireless_data(drvdata);
 | 
				
			||||||
 | 
						corsair_void_set_unknown_batt(drvdata);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Receiver version won't be reset after init */
 | 
				
			||||||
 | 
						/* Headset version already set via set_unknown_wireless_data */
 | 
				
			||||||
 | 
						drvdata->fw_receiver_major = 0;
 | 
				
			||||||
 | 
						drvdata->fw_receiver_minor = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = hid_parse(hid_dev);
 | 
				
			||||||
 | 
						if (ret) {
 | 
				
			||||||
 | 
							hid_err(hid_dev, "parse failed (reason: %d)\n", ret);
 | 
				
			||||||
 | 
							return ret;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						name = devm_kasprintf(drvdata->dev, GFP_KERNEL,
 | 
				
			||||||
 | 
								      "corsair-void-%d-battery", hid_dev->id);
 | 
				
			||||||
 | 
						if (!name)
 | 
				
			||||||
 | 
							return -ENOMEM;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata->battery_desc.name = name;
 | 
				
			||||||
 | 
						drvdata->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
 | 
				
			||||||
 | 
						drvdata->battery_desc.properties = corsair_void_battery_props;
 | 
				
			||||||
 | 
						drvdata->battery_desc.num_properties = ARRAY_SIZE(corsair_void_battery_props);
 | 
				
			||||||
 | 
						drvdata->battery_desc.get_property = corsair_void_battery_get_property;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						drvdata->battery = NULL;
 | 
				
			||||||
 | 
						INIT_WORK(&drvdata->battery_remove_work,
 | 
				
			||||||
 | 
							  corsair_void_battery_remove_work_handler);
 | 
				
			||||||
 | 
						INIT_WORK(&drvdata->battery_add_work,
 | 
				
			||||||
 | 
							  corsair_void_battery_add_work_handler);
 | 
				
			||||||
 | 
						ret = devm_mutex_init(drvdata->dev, &drvdata->battery_mutex);
 | 
				
			||||||
 | 
						if (ret)
 | 
				
			||||||
 | 
							return ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = sysfs_create_group(&hid_dev->dev.kobj, &corsair_void_attr_group);
 | 
				
			||||||
 | 
						if (ret)
 | 
				
			||||||
 | 
							return ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Any failures after here will need to call hid_hw_stop */
 | 
				
			||||||
 | 
						ret = hid_hw_start(hid_dev, HID_CONNECT_DEFAULT);
 | 
				
			||||||
 | 
						if (ret) {
 | 
				
			||||||
 | 
							hid_err(hid_dev, "hid_hw_start failed (reason: %d)\n", ret);
 | 
				
			||||||
 | 
							goto failed_after_sysfs;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Refresh battery data, in case wireless headset is already connected */
 | 
				
			||||||
 | 
						INIT_DELAYED_WORK(&drvdata->delayed_status_work,
 | 
				
			||||||
 | 
								  corsair_void_status_work_handler);
 | 
				
			||||||
 | 
						schedule_delayed_work(&drvdata->delayed_status_work,
 | 
				
			||||||
 | 
								      msecs_to_jiffies(100));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Refresh firmware versions */
 | 
				
			||||||
 | 
						INIT_DELAYED_WORK(&drvdata->delayed_firmware_work,
 | 
				
			||||||
 | 
								  corsair_void_firmware_work_handler);
 | 
				
			||||||
 | 
						schedule_delayed_work(&drvdata->delayed_firmware_work,
 | 
				
			||||||
 | 
								      msecs_to_jiffies(100));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					failed_after_sysfs:
 | 
				
			||||||
 | 
						sysfs_remove_group(&hid_dev->dev.kobj, &corsair_void_attr_group);
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void corsair_void_remove(struct hid_device *hid_dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata = hid_get_drvdata(hid_dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hid_hw_stop(hid_dev);
 | 
				
			||||||
 | 
						cancel_work_sync(&drvdata->battery_remove_work);
 | 
				
			||||||
 | 
						cancel_work_sync(&drvdata->battery_add_work);
 | 
				
			||||||
 | 
						if (drvdata->battery)
 | 
				
			||||||
 | 
							power_supply_unregister(drvdata->battery);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cancel_delayed_work_sync(&drvdata->delayed_firmware_work);
 | 
				
			||||||
 | 
						sysfs_remove_group(&hid_dev->dev.kobj, &corsair_void_attr_group);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int corsair_void_raw_event(struct hid_device *hid_dev,
 | 
				
			||||||
 | 
									  struct hid_report *hid_report,
 | 
				
			||||||
 | 
									  u8 *data, int size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct corsair_void_drvdata *drvdata = hid_get_drvdata(hid_dev);
 | 
				
			||||||
 | 
						bool was_connected = drvdata->connected;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Description of packets are documented at the top of this file */
 | 
				
			||||||
 | 
						if (hid_report->id == CORSAIR_VOID_STATUS_REPORT_ID) {
 | 
				
			||||||
 | 
							drvdata->mic_up = FIELD_GET(CORSAIR_VOID_MIC_MASK, data[2]);
 | 
				
			||||||
 | 
							drvdata->connected = (data[3] == CORSAIR_VOID_WIRELESS_CONNECTED) ||
 | 
				
			||||||
 | 
									     drvdata->is_wired;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							corsair_void_process_receiver(drvdata,
 | 
				
			||||||
 | 
										      FIELD_GET(CORSAIR_VOID_CAPACITY_MASK, data[2]),
 | 
				
			||||||
 | 
										      data[3], data[4]);
 | 
				
			||||||
 | 
						} else if (hid_report->id == CORSAIR_VOID_FIRMWARE_REPORT_ID) {
 | 
				
			||||||
 | 
							drvdata->fw_receiver_major = data[1];
 | 
				
			||||||
 | 
							drvdata->fw_receiver_minor = data[2];
 | 
				
			||||||
 | 
							drvdata->fw_headset_major = data[3];
 | 
				
			||||||
 | 
							drvdata->fw_headset_minor = data[4];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Handle wireless headset connect / disconnect */
 | 
				
			||||||
 | 
						if ((was_connected != drvdata->connected) && !drvdata->is_wired) {
 | 
				
			||||||
 | 
							if (drvdata->connected)
 | 
				
			||||||
 | 
								corsair_void_headset_connected(drvdata);
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								corsair_void_headset_disconnected(drvdata);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct hid_device_id corsair_void_devices[] = {
 | 
				
			||||||
 | 
						/* Corsair Void Wireless */
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRELESS_DEVICE(0x0a0c),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRELESS_DEVICE(0x0a2b),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRELESS_DEVICE(0x1b23),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRELESS_DEVICE(0x1b25),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRELESS_DEVICE(0x1b27),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Corsair Void USB */
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x0a0f),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x1b1c),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x1b29),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x1b2a),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Corsair Void Surround */
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x0a30),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x0a31),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Corsair Void Pro Wireless */
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRELESS_DEVICE(0x0a14),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRELESS_DEVICE(0x0a16),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRELESS_DEVICE(0x0a1a),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Corsair Void Pro USB */
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x0a17),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x0a1d),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Corsair Void Pro Surround */
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x0a18),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x0a1e),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x0a1f),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Corsair Void Elite Wireless */
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRELESS_DEVICE(0x0a51),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRELESS_DEVICE(0x0a55),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRELESS_DEVICE(0x0a75),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Corsair Void Elite USB */
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x0a52),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x0a56),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Corsair Void Elite Surround */
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x0a53),
 | 
				
			||||||
 | 
						CORSAIR_VOID_WIRED_DEVICE(0x0a57),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MODULE_DEVICE_TABLE(hid, corsair_void_devices);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct hid_driver corsair_void_driver = {
 | 
				
			||||||
 | 
						.name = "hid-corsair-void",
 | 
				
			||||||
 | 
						.id_table = corsair_void_devices,
 | 
				
			||||||
 | 
						.probe = corsair_void_probe,
 | 
				
			||||||
 | 
						.remove = corsair_void_remove,
 | 
				
			||||||
 | 
						.raw_event = corsair_void_raw_event,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module_hid_driver(corsair_void_driver);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MODULE_LICENSE("GPL");
 | 
				
			||||||
 | 
					MODULE_AUTHOR("Stuart Hayhurst <stuart.a.hayhurst@gmail.com>");
 | 
				
			||||||
 | 
					MODULE_DESCRIPTION("HID driver for Corsair Void headsets");
 | 
				
			||||||
		Loading…
	
		Reference in a new issue