[PATCH 2/6] HID: hid-input: Add phone hook and mic mute buttons for headsets

From: Maxim Mikityanskiy
Date: Sat Jul 03 2021 - 18:04:37 EST


A lot of USBHID headsets available on the market have buttons to toggle
microphone mute and to answer the call/hang up.

According to the HID Usage Tables specification, these usages are on/off
controls, which may be presented by either two buttons, a single toggle
button or a mechanical switch. This commit adds a function called
hidinput_handle_onoff that handles all these cases in a compliant way.

Signed-off-by: Maxim Mikityanskiy <maxtram95@xxxxxxxxx>
---
drivers/hid/hid-input.c | 140 +++++++++++++++++++++++++
include/uapi/linux/input-event-codes.h | 8 ++
2 files changed, 148 insertions(+)

diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index 44b8243f9924..533a7f429a5f 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -579,6 +579,43 @@ static bool hidinput_field_in_collection(struct hid_device *device, struct hid_f
return collection->type == type && collection->usage == usage;
}

+/**
+ * hidinput_get_onoff_keycodes - Gets on and off keycodes for OOC usages.
+ * @usage: HID usage.
+ * @code_on: Output parameter for the on keycode.
+ * @code_off: Output parameter for the off keycode.
+ *
+ * Returns true if @usage is a supported on/off control (OOC), as defined by HID
+ * Usage Tables 1.21 (3.4.1.2).
+ *
+ * Depending on the OOC type, we need to send either a toggle keycode or
+ * separate on/off keycodes. This function detects whether @usage is an OOC. If
+ * yes, and if this OOC is supported, it returns the on and off keycodes
+ * corresponding to the toggle keycode stored in usage->code.
+ */
+static bool hidinput_get_onoff_keycodes(struct hid_usage *usage,
+ u16 *code_on, u16 *code_off)
+{
+ if (usage->type != EV_KEY)
+ return false;
+
+ if ((usage->hid & HID_USAGE_PAGE) != HID_UP_TELEPHONY)
+ return false;
+
+ switch (usage->code) {
+ case KEY_TOGGLE_PHONE:
+ *code_on = KEY_PICKUP_PHONE;
+ *code_off = KEY_HANGUP_PHONE;
+ return true;
+ case KEY_MICMUTE:
+ *code_on = KEY_MICMUTE_ON;
+ *code_off = KEY_MICMUTE_OFF;
+ return true;
+ }
+
+ return false;
+}
+
static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_field *field,
struct hid_usage *usage)
{
@@ -586,6 +623,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
struct hid_device *device = input_get_drvdata(input);
int max = 0, code;
unsigned long *bit = NULL;
+ u16 code_on, code_off;

field->hidinput = hidinput;

@@ -887,6 +925,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel

case HID_UP_TELEPHONY:
switch (usage->hid & HID_USAGE) {
+ case 0x20: map_key_clear(KEY_TOGGLE_PHONE); break;
case 0x2f: map_key_clear(KEY_MICMUTE); break;
case 0xb0: map_key_clear(KEY_NUMERIC_0); break;
case 0xb1: map_key_clear(KEY_NUMERIC_1); break;
@@ -1198,6 +1237,11 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel

set_bit(usage->type, input->evbit);

+ if (hidinput_get_onoff_keycodes(usage, &code_on, &code_off)) {
+ set_bit(code_on, bit);
+ set_bit(code_off, bit);
+ }
+
/*
* This part is *really* controversial:
* - HID aims at being generic so we should do our best to export
@@ -1314,6 +1358,92 @@ static void hidinput_handle_scroll(struct hid_usage *usage,
input_event(input, EV_REL, usage->code, hi_res);
}

+/**
+ * hidinput_handle_onoff - Handle on/off control (OOC).
+ * @field: HID field that corresponds to the event.
+ * @value: HID value that corresponds to the event.
+ * @code_toggle: Key code to send when toggling state of the on/off control.
+ * @code_on: Key code to send when turning on the on/off control.
+ * @code_off: Key code to send when turning off the on/off control.
+ *
+ * Returns true if the event was handled, false if the @field flags are invalid.
+ *
+ * Handles on/off control (OOC), as defined by HID Usage Tables 1.21 (3.4.1.2).
+ * Determines the type of the OOC by looking at @field and sends one of the key
+ * codes accordingly. Whenever it's possible to distinguish on and off states,
+ * different key strokes (@code_on, @code_off) are sent, otherwise @code_toggle
+ * is sent.
+ */
+static bool hidinput_handle_onoff(struct hid_field *field, __s32 value, unsigned int scan,
+ __u16 code_toggle, __u16 code_on, __u16 code_off)
+{
+ struct input_dev *input = field->hidinput->input;
+ __u16 code = 0;
+
+ /* Two buttons, on and off */
+ if ((field->flags & HID_MAIN_ITEM_RELATIVE) &&
+ (field->flags & HID_MAIN_ITEM_NO_PREFERRED) &&
+ (field->logical_minimum == -1) &&
+ (field->logical_maximum == 1)) {
+ if (value != 1 && value != -1)
+ return true;
+
+ code = value == 1 ? code_on : code_off;
+ }
+
+ /* A single button that toggles the on/off state each time it is pressed */
+ if ((field->flags & HID_MAIN_ITEM_RELATIVE) &&
+ !(field->flags & HID_MAIN_ITEM_NO_PREFERRED) &&
+ (field->logical_minimum == 0) &&
+ (field->logical_maximum == 1)) {
+ if (value != 1)
+ return true;
+
+ code = code_toggle;
+ }
+
+ /* A toggle switch that maintains the on/off state mechanically */
+ if (!(field->flags & HID_MAIN_ITEM_RELATIVE) &&
+ (field->flags & HID_MAIN_ITEM_NO_PREFERRED) &&
+ (field->logical_minimum == 0) &&
+ (field->logical_maximum == 1))
+ code = value ? code_on : code_off;
+
+ if (!code)
+ return false;
+
+ input_event(input, EV_MSC, MSC_SCAN, scan);
+ input_event(input, EV_KEY, code, 1);
+ input_sync(input);
+ input_event(input, EV_KEY, code, 0);
+
+ return true;
+}
+
+/**
+ * hidinput_handle_onoffs - Handles an OOC event if the HID usage type is OOC.
+ * @usage: HID usage to check.
+ * @field: HID field that corresponds to the event.
+ * @value: HID value that corresponds to the event.
+ *
+ * Returns: 1 if @usage is a supported on/off control (OOC), as defined by HID
+ * Usage Tables 1.21 (3.4.1.2).
+ * 0 if @usage is not a supported OOC.
+ * -EINVAL if @usage is not a valid OOC (@field is invalid).
+ */
+static int hidinput_handle_onoffs(struct hid_usage *usage, struct hid_field *field, __s32 value)
+{
+ u16 code_on, code_off;
+
+ if (!hidinput_get_onoff_keycodes(usage, &code_on, &code_off))
+ return 0;
+
+ if (!hidinput_handle_onoff(field, value, usage->hid, usage->code, code_on, code_off))
+ return -EINVAL;
+
+ return 1;
+}
+
void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value)
{
struct input_dev *input;
@@ -1438,6 +1568,16 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
value == field->value[usage->usage_index])
return;

+ switch (hidinput_handle_onoffs(usage, field, value)) {
+ case 1:
+ return;
+ case -EINVAL:
+ hid_warn_once(hid, "Invalid OOC usage: code %u, flags %#x, min %d, max %d\n",
+ usage->code, field->flags,
+ field->logical_minimum, field->logical_maximum);
+ return;
+ }
+
/* report the usage code as scancode if the key status has changed */
if (usage->type == EV_KEY &&
(!test_bit(usage->code, input->key)) == value)
diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h
index dd785a5b5076..d490de9ce7fe 100644
--- a/include/uapi/linux/input-event-codes.h
+++ b/include/uapi/linux/input-event-codes.h
@@ -518,6 +518,7 @@
#define KEY_NOTIFICATION_CENTER 0x1bc /* Show/hide the notification center */
#define KEY_PICKUP_PHONE 0x1bd /* Answer incoming call */
#define KEY_HANGUP_PHONE 0x1be /* Decline incoming call */
+#define KEY_TOGGLE_PHONE 0x1bf /* Toggle phone hook */

#define KEY_DEL_EOL 0x1c0
#define KEY_DEL_EOS 0x1c1
@@ -660,6 +661,13 @@
/* Select an area of screen to be copied */
#define KEY_SELECTIVE_SCREENSHOT 0x27a

+/*
+ * In contrast to KEY_MICMUTE (that toggles the mute state), these set specific
+ * (on/off) states.
+ */
+#define KEY_MICMUTE_ON 0x280
+#define KEY_MICMUTE_OFF 0x281
+
/*
* Some keyboards have keys which do not have a defined meaning, these keys
* are intended to be programmed / bound to macros by the user. For most
--
2.32.0