xref: /linux/drivers/hid/hid-hyperx.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
1*663870a8SBenjamin Blume // SPDX-License-Identifier: GPL-2.0-or-later
2*663870a8SBenjamin Blume /*
3*663870a8SBenjamin Blume  *  HID driver for the HyperX QuadCast 2 microphone
4*663870a8SBenjamin Blume  *
5*663870a8SBenjamin Blume  *  The tap-to-mute button is handled entirely in the device firmware: it gates
6*663870a8SBenjamin Blume  *  the audio internally and does not send the Telephony "Phone Mute" usage its
7*663870a8SBenjamin Blume  *  own report descriptor advertises.  The resulting mute state is only reported
8*663870a8SBenjamin Blume  *  through a vendor-defined collection, which hid-input cannot map, so the
9*663870a8SBenjamin Blume  *  button is invisible to userspace.
10*663870a8SBenjamin Blume  *
11*663870a8SBenjamin Blume  *  Copyright (c) 2026 Benjamin Blume <benjaminblume@posteo.de>
12*663870a8SBenjamin Blume  */
13*663870a8SBenjamin Blume 
14*663870a8SBenjamin Blume #include <linux/hid.h>
15*663870a8SBenjamin Blume #include <linux/input.h>
16*663870a8SBenjamin Blume #include <linux/module.h>
17*663870a8SBenjamin Blume 
18*663870a8SBenjamin Blume #include "hid-ids.h"
19*663870a8SBenjamin Blume 
20*663870a8SBenjamin Blume #define HYPERX_QC2_REPORT_ID	0x77
21*663870a8SBenjamin Blume #define HYPERX_QC2_EVENT_MUTE	0x06
22*663870a8SBenjamin Blume #define HYPERX_QC2_MUTE_LEN	3
23*663870a8SBenjamin Blume 
24*663870a8SBenjamin Blume struct hyperx_drvdata {
25*663870a8SBenjamin Blume 	struct input_dev *input;
26*663870a8SBenjamin Blume 	bool muted;
27*663870a8SBenjamin Blume 	bool have_state;
28*663870a8SBenjamin Blume };
29*663870a8SBenjamin Blume 
30*663870a8SBenjamin Blume static int hyperx_input_configured(struct hid_device *hdev,
31*663870a8SBenjamin Blume 				   struct hid_input *hi)
32*663870a8SBenjamin Blume {
33*663870a8SBenjamin Blume 	struct hyperx_drvdata *drvdata = hid_get_drvdata(hdev);
34*663870a8SBenjamin Blume 
35*663870a8SBenjamin Blume 	/*
36*663870a8SBenjamin Blume 	 * The device exposes several application collections.  Prefer the
37*663870a8SBenjamin Blume 	 * telephony one, which already advertises KEY_MICMUTE, and fall back
38*663870a8SBenjamin Blume 	 * to the first collection otherwise.
39*663870a8SBenjamin Blume 	 */
40*663870a8SBenjamin Blume 	if (!drvdata->input || test_bit(KEY_MICMUTE, hi->input->keybit)) {
41*663870a8SBenjamin Blume 		drvdata->input = hi->input;
42*663870a8SBenjamin Blume 		input_set_capability(drvdata->input, EV_KEY, KEY_MICMUTE);
43*663870a8SBenjamin Blume 	}
44*663870a8SBenjamin Blume 
45*663870a8SBenjamin Blume 	return 0;
46*663870a8SBenjamin Blume }
47*663870a8SBenjamin Blume 
48*663870a8SBenjamin Blume static int hyperx_raw_event(struct hid_device *hdev, struct hid_report *report,
49*663870a8SBenjamin Blume 			    u8 *data, int size)
50*663870a8SBenjamin Blume {
51*663870a8SBenjamin Blume 	struct hyperx_drvdata *drvdata = hid_get_drvdata(hdev);
52*663870a8SBenjamin Blume 	bool muted;
53*663870a8SBenjamin Blume 
54*663870a8SBenjamin Blume 	if (!(hdev->claimed & HID_CLAIMED_INPUT) || !drvdata->input)
55*663870a8SBenjamin Blume 		return 0;
56*663870a8SBenjamin Blume 
57*663870a8SBenjamin Blume 	if (size < HYPERX_QC2_MUTE_LEN || data[0] != HYPERX_QC2_REPORT_ID ||
58*663870a8SBenjamin Blume 	    data[1] != HYPERX_QC2_EVENT_MUTE)
59*663870a8SBenjamin Blume 		return 0;
60*663870a8SBenjamin Blume 
61*663870a8SBenjamin Blume 	muted = data[2];
62*663870a8SBenjamin Blume 	if (drvdata->have_state && muted == drvdata->muted)
63*663870a8SBenjamin Blume 		return 0;
64*663870a8SBenjamin Blume 
65*663870a8SBenjamin Blume 	drvdata->muted = muted;
66*663870a8SBenjamin Blume 	drvdata->have_state = true;
67*663870a8SBenjamin Blume 
68*663870a8SBenjamin Blume 	/*
69*663870a8SBenjamin Blume 	 * The device reports the resulting absolute state, while KEY_MICMUTE is
70*663870a8SBenjamin Blume 	 * a momentary key that userspace acts on as a toggle.  Emit one
71*663870a8SBenjamin Blume 	 * keypress per state change.
72*663870a8SBenjamin Blume 	 */
73*663870a8SBenjamin Blume 	input_report_key(drvdata->input, KEY_MICMUTE, 1);
74*663870a8SBenjamin Blume 	input_sync(drvdata->input);
75*663870a8SBenjamin Blume 	input_report_key(drvdata->input, KEY_MICMUTE, 0);
76*663870a8SBenjamin Blume 	input_sync(drvdata->input);
77*663870a8SBenjamin Blume 
78*663870a8SBenjamin Blume 	return 0;
79*663870a8SBenjamin Blume }
80*663870a8SBenjamin Blume 
81*663870a8SBenjamin Blume static int hyperx_probe(struct hid_device *hdev, const struct hid_device_id *id)
82*663870a8SBenjamin Blume {
83*663870a8SBenjamin Blume 	struct hyperx_drvdata *drvdata;
84*663870a8SBenjamin Blume 	int ret;
85*663870a8SBenjamin Blume 
86*663870a8SBenjamin Blume 	drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
87*663870a8SBenjamin Blume 	if (!drvdata)
88*663870a8SBenjamin Blume 		return -ENOMEM;
89*663870a8SBenjamin Blume 
90*663870a8SBenjamin Blume 	hid_set_drvdata(hdev, drvdata);
91*663870a8SBenjamin Blume 
92*663870a8SBenjamin Blume 	ret = hid_parse(hdev);
93*663870a8SBenjamin Blume 	if (ret)
94*663870a8SBenjamin Blume 		return ret;
95*663870a8SBenjamin Blume 
96*663870a8SBenjamin Blume 	return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
97*663870a8SBenjamin Blume }
98*663870a8SBenjamin Blume 
99*663870a8SBenjamin Blume static const struct hid_device_id hyperx_devices[] = {
100*663870a8SBenjamin Blume 	{ HID_USB_DEVICE(USB_VENDOR_ID_HP,
101*663870a8SBenjamin Blume 			 USB_PRODUCT_ID_HP_HYPERX_QUADCAST_2) },
102*663870a8SBenjamin Blume 	{ }
103*663870a8SBenjamin Blume };
104*663870a8SBenjamin Blume MODULE_DEVICE_TABLE(hid, hyperx_devices);
105*663870a8SBenjamin Blume 
106*663870a8SBenjamin Blume static struct hid_driver hyperx_driver = {
107*663870a8SBenjamin Blume 	.name			= "hyperx",
108*663870a8SBenjamin Blume 	.id_table		= hyperx_devices,
109*663870a8SBenjamin Blume 	.probe			= hyperx_probe,
110*663870a8SBenjamin Blume 	.input_configured	= hyperx_input_configured,
111*663870a8SBenjamin Blume 	.raw_event		= hyperx_raw_event,
112*663870a8SBenjamin Blume };
113*663870a8SBenjamin Blume module_hid_driver(hyperx_driver);
114*663870a8SBenjamin Blume 
115*663870a8SBenjamin Blume MODULE_DESCRIPTION("HID driver for the HyperX QuadCast 2 microphone");
116*663870a8SBenjamin Blume MODULE_AUTHOR("Benjamin Blume <benjaminblume@posteo.de>");
117*663870a8SBenjamin Blume MODULE_LICENSE("GPL");
118