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