diff mbox series

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

Message ID 20210703220202.5637-3-maxtram95@gmail.com
State New
Headers show
Series Add support for common USB HID headset features | expand

Commit Message

Maxim Mikityanskiy July 3, 2021, 10:01 p.m. UTC
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@gmail.com>
---
 drivers/hid/hid-input.c                | 140 +++++++++++++++++++++++++
 include/uapi/linux/input-event-codes.h |   8 ++
 2 files changed, 148 insertions(+)
diff mbox series

Patch

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