diff options
| -rw-r--r-- | MAINTAINERS | 6 | ||||
| -rw-r--r-- | drivers/hid/Kconfig | 13 | ||||
| -rw-r--r-- | drivers/hid/Makefile | 1 | ||||
| -rw-r--r-- | drivers/hid/hid-hyperx.c | 117 | ||||
| -rw-r--r-- | drivers/hid/hid-ids.h | 1 |
5 files changed, 138 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 8f2c582f77e6..f1d49249efb1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11530,6 +11530,12 @@ F: include/uapi/linux/hid* F: samples/hid/ F: tools/testing/selftests/hid/ +HID HYPERX DRIVER +M: Benjamin Blume <benjaminblume@posteo.de> +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-hyperx.c + HID LOGITECH DRIVERS R: Filipe LaĆns <lains@riseup.net> L: linux-input@vger.kernel.org diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 1193442d008a..7f0539393a7b 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -433,6 +433,19 @@ config HOLTEK_FF Say Y here if you have a Holtek On Line Grip based game controller and want to have force feedback support for it. +config HID_HYPERX + tristate "HyperX microphones" + depends on USB_HID + help + Support for the mute button of HyperX microphones. + + The button is handled in the device firmware and only reports the + resulting mute state through a vendor-defined collection, so without + this driver userspace never learns that the microphone was muted. + + Supported devices: + - HyperX QuadCast 2 + config HID_VIVALDI_COMMON tristate help diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 4a172bd27b11..6112b4604bb5 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_HID_HOLTEK) += hid-holtek-kbd.o obj-$(CONFIG_HID_HOLTEK) += hid-holtek-mouse.o obj-$(CONFIG_HID_HOLTEK) += hid-holtekff.o obj-$(CONFIG_HID_HYPERV_MOUSE) += hid-hyperv.o +obj-$(CONFIG_HID_HYPERX) += hid-hyperx.o obj-$(CONFIG_HID_ICADE) += hid-icade.o obj-$(CONFIG_HID_ITE) += hid-ite.o obj-$(CONFIG_HID_JABRA) += hid-jabra.o diff --git a/drivers/hid/hid-hyperx.c b/drivers/hid/hid-hyperx.c new file mode 100644 index 000000000000..18d2dc24cc76 --- /dev/null +++ b/drivers/hid/hid-hyperx.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for the HyperX QuadCast 2 microphone + * + * The tap-to-mute button is handled entirely in the device firmware: it gates + * the audio internally and does not send the Telephony "Phone Mute" usage its + * own report descriptor advertises. The resulting mute state is only reported + * through a vendor-defined collection, which hid-input cannot map, so the + * button is invisible to userspace. + * + * Copyright (c) 2026 Benjamin Blume <benjaminblume@posteo.de> + */ + +#include <linux/hid.h> +#include <linux/input.h> +#include <linux/module.h> + +#include "hid-ids.h" + +#define HYPERX_QC2_REPORT_ID 0x77 +#define HYPERX_QC2_EVENT_MUTE 0x06 +#define HYPERX_QC2_MUTE_LEN 3 + +struct hyperx_drvdata { + struct input_dev *input; + bool muted; + bool have_state; +}; + +static int hyperx_input_configured(struct hid_device *hdev, + struct hid_input *hi) +{ + struct hyperx_drvdata *drvdata = hid_get_drvdata(hdev); + + /* + * The device exposes several application collections. Prefer the + * telephony one, which already advertises KEY_MICMUTE, and fall back + * to the first collection otherwise. + */ + if (!drvdata->input || test_bit(KEY_MICMUTE, hi->input->keybit)) { + drvdata->input = hi->input; + input_set_capability(drvdata->input, EV_KEY, KEY_MICMUTE); + } + + return 0; +} + +static int hyperx_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct hyperx_drvdata *drvdata = hid_get_drvdata(hdev); + bool muted; + + if (!(hdev->claimed & HID_CLAIMED_INPUT) || !drvdata->input) + return 0; + + if (size < HYPERX_QC2_MUTE_LEN || data[0] != HYPERX_QC2_REPORT_ID || + data[1] != HYPERX_QC2_EVENT_MUTE) + return 0; + + muted = data[2]; + if (drvdata->have_state && muted == drvdata->muted) + return 0; + + drvdata->muted = muted; + drvdata->have_state = true; + + /* + * The device reports the resulting absolute state, while KEY_MICMUTE is + * a momentary key that userspace acts on as a toggle. Emit one + * keypress per state change. + */ + input_report_key(drvdata->input, KEY_MICMUTE, 1); + input_sync(drvdata->input); + input_report_key(drvdata->input, KEY_MICMUTE, 0); + input_sync(drvdata->input); + + return 0; +} + +static int hyperx_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct hyperx_drvdata *drvdata; + int ret; + + drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + hid_set_drvdata(hdev, drvdata); + + ret = hid_parse(hdev); + if (ret) + return ret; + + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); +} + +static const struct hid_device_id hyperx_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_HP, + USB_PRODUCT_ID_HP_HYPERX_QUADCAST_2) }, + { } +}; +MODULE_DEVICE_TABLE(hid, hyperx_devices); + +static struct hid_driver hyperx_driver = { + .name = "hyperx", + .id_table = hyperx_devices, + .probe = hyperx_probe, + .input_configured = hyperx_input_configured, + .raw_event = hyperx_raw_event, +}; +module_hid_driver(hyperx_driver); + +MODULE_DESCRIPTION("HID driver for the HyperX QuadCast 2 microphone"); +MODULE_AUTHOR("Benjamin Blume <benjaminblume@posteo.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 9732ba197101..d7a2a15cb91e 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -692,6 +692,7 @@ #define USB_DEVICE_ID_HORI_WIRELESS_SWITCH_PAD 0x00f6 #define USB_VENDOR_ID_HP 0x03f0 +#define USB_PRODUCT_ID_HP_HYPERX_QUADCAST_2 0x07b4 #define USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A 0x464a #define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a #define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A 0x0b4a |
