[v2] HID: multitouch: Add quirks for flipped axes

Message ID 20221208165145.1.I69657e84c0606b2e5ccfa9fedbf42b7676a1e129@changeid
State New
Headers
Series [v2] HID: multitouch: Add quirks for flipped axes |

Commit Message

Allen Ballway Dec. 8, 2022, 4:58 p.m. UTC
  Certain touchscreen devices, such as the ELAN9034, are oriented
incorrectly and report touches on opposite points on the X and Y axes.
For example, a 100x200 screen touched at (10,20) would report (90, 180)
and vice versa.

This is fixed by adding device quirks to transform the touch points
into the correct spaces, from X -> MAX(X) - X, and Y -> MAX(Y) - Y.

Signed-off-by: Allen Ballway <ballway@chromium.org>
---
V1 -> V2: Address review comments, change to use DMI match. Confirmed
MT_TOOL_X/Y require transformation and update orientation based on
flipped axes.

 drivers/hid/hid-multitouch.c | 72 +++++++++++++++++++++++++++++++++---
 1 file changed, 67 insertions(+), 5 deletions(-)

--
2.39.0.rc1.256.g54fd8350bd-goog
  

Comments

Dmitry Torokhov Dec. 12, 2022, 6:46 p.m. UTC | #1
On Thu, Dec 08, 2022 at 04:58:36PM +0000, Allen Ballway wrote:
> Certain touchscreen devices, such as the ELAN9034, are oriented
> incorrectly and report touches on opposite points on the X and Y axes.
> For example, a 100x200 screen touched at (10,20) would report (90, 180)
> and vice versa.
> 
> This is fixed by adding device quirks to transform the touch points
> into the correct spaces, from X -> MAX(X) - X, and Y -> MAX(Y) - Y.
> 
> Signed-off-by: Allen Ballway <ballway@chromium.org>
> ---
> V1 -> V2: Address review comments, change to use DMI match. Confirmed
> MT_TOOL_X/Y require transformation and update orientation based on
> flipped axes.

I think you might need to push the DMI match into i2c-hid driver, and
bubble the quirks up here, otherwise if you connect a 2nd USB
touchscreen to such a system will also result in the quirk being applied
to it as far as I understand. In i2c-hid you can make a fine-grained
match on vendor/product and system to limit the quirk to hopefully the
right peripheral.

Thanks.
  

Patch

diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index 91a4d3fc30e08..8a4f77e534a75 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -39,6 +39,7 @@ 
 #include <linux/jiffies.h>
 #include <linux/string.h>
 #include <linux/timer.h>
+#include <linux/dmi.h>


 MODULE_AUTHOR("Stephane Chatty <chatty@enac.fr>");
@@ -71,6 +72,8 @@  MODULE_LICENSE("GPL");
 #define MT_QUIRK_SEPARATE_APP_REPORT	BIT(19)
 #define MT_QUIRK_FORCE_MULTI_INPUT	BIT(20)
 #define MT_QUIRK_DISABLE_WAKEUP		BIT(21)
+#define MT_QUIRK_FLIP_X			BIT(22)
+#define MT_QUIRK_FLIP_Y			BIT(23)

 #define MT_INPUTMODE_TOUCHSCREEN	0x02
 #define MT_INPUTMODE_TOUCHPAD		0x03
@@ -212,6 +215,7 @@  static void mt_post_parse(struct mt_device *td, struct mt_application *app);
 #define MT_CLS_GOOGLE				0x0111
 #define MT_CLS_RAZER_BLADE_STEALTH		0x0112
 #define MT_CLS_SMART_TECH			0x0113
+#define MT_CLS_ELAN_FLIPPED			0x0114

 #define MT_DEFAULT_MAXCONTACT	10
 #define MT_MAX_MAXCONTACT	250
@@ -396,9 +400,36 @@  static const struct mt_class mt_classes[] = {
 			MT_QUIRK_CONTACT_CNT_ACCURATE |
 			MT_QUIRK_SEPARATE_APP_REPORT,
 	},
+	{ .name = MT_CLS_ELAN_FLIPPED,
+		.quirks = MT_QUIRK_ALWAYS_VALID |
+			MT_QUIRK_IGNORE_DUPLICATES |
+			MT_QUIRK_HOVERING |
+			MT_QUIRK_CONTACT_CNT_ACCURATE |
+			MT_QUIRK_STICKY_FINGERS |
+			MT_QUIRK_WIN8_PTP_BUTTONS |
+			MT_QUIRK_FLIP_X |
+			MT_QUIRK_FLIP_Y,
+		.export_all_inputs = true },
+
 	{ }
 };

+/*
+ * This list contains devices which have specific issues based on the system
+ * they're on and not just the device itself.
+ */
+static const struct dmi_system_id mt_devices_dmi_override_table[] = {
+	{
+		.ident = "DynaBook K50/FR",
+		.matches = {
+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dynabook Inc."),
+			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "dynabook K50/FR"),
+		},
+		.driver_data = (void *)MT_CLS_ELAN_FLIPPED,
+	},
+	{ }	/* Terminate list */
+};
+
 static ssize_t mt_show_quirks(struct device *dev,
 			   struct device_attribute *attr,
 			   char *buf)
@@ -1086,6 +1117,10 @@  static int mt_process_slot(struct mt_device *td, struct input_dev *input,
 		int orientation = wide;
 		int max_azimuth;
 		int azimuth;
+		int x;
+		int y;
+		int cx;
+		int cy;

 		if (slot->a != DEFAULT_ZERO) {
 			/*
@@ -1104,6 +1139,16 @@  static int mt_process_slot(struct mt_device *td, struct input_dev *input,
 			if (azimuth > max_azimuth * 2)
 				azimuth -= max_azimuth * 4;
 			orientation = -azimuth;
+
+			/* Orientation is inverted if the X or Y axes are
+			 * flipped, but normalized if both are inverted.
+			 */
+			if (quirks & (MT_QUIRK_FLIP_X | MT_QUIRK_FLIP_Y) &&
+			    !((quirks & MT_QUIRK_FLIP_X)
+			      && (quirks & MT_QUIRK_FLIP_Y))) {
+				orientation = -orientation;
+			}
+
 		}

 		if (quirks & MT_QUIRK_TOUCH_SIZE_SCALING) {
@@ -1115,10 +1160,23 @@  static int mt_process_slot(struct mt_device *td, struct input_dev *input,
 			minor = minor >> 1;
 		}

-		input_event(input, EV_ABS, ABS_MT_POSITION_X, *slot->x);
-		input_event(input, EV_ABS, ABS_MT_POSITION_Y, *slot->y);
-		input_event(input, EV_ABS, ABS_MT_TOOL_X, *slot->cx);
-		input_event(input, EV_ABS, ABS_MT_TOOL_Y, *slot->cy);
+		x = quirks & MT_QUIRK_FLIP_X ?
+			input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x :
+			*slot->x;
+		y = quirks & MT_QUIRK_FLIP_Y ?
+			input_abs_get_max(input, ABS_MT_POSITION_Y) - *slot->y :
+			*slot->y;
+		cx = quirks & MT_QUIRK_FLIP_X ?
+			input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->cx :
+			*slot->cx;
+		cy = quirks & MT_QUIRK_FLIP_Y ?
+			input_abs_get_max(input, ABS_MT_POSITION_Y) - *slot->cy :
+			*slot->cy;
+
+		input_event(input, EV_ABS, ABS_MT_POSITION_X, x);
+		input_event(input, EV_ABS, ABS_MT_POSITION_Y, y);
+		input_event(input, EV_ABS, ABS_MT_TOOL_X, cx);
+		input_event(input, EV_ABS, ABS_MT_TOOL_Y, cy);
 		input_event(input, EV_ABS, ABS_MT_DISTANCE, !*slot->tip_state);
 		input_event(input, EV_ABS, ABS_MT_ORIENTATION, orientation);
 		input_event(input, EV_ABS, ABS_MT_PRESSURE, *slot->p);
@@ -1711,9 +1769,13 @@  static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	int ret, i;
 	struct mt_device *td;
 	const struct mt_class *mtclass = mt_classes; /* MT_CLS_DEFAULT */
+	const struct dmi_system_id *system_id
+			= dmi_first_match(mt_devices_dmi_override_table);
+	const long driver_data_name = system_id ?
+			(long)(system_id->driver_data) : id->driver_data;

 	for (i = 0; mt_classes[i].name ; i++) {
-		if (id->driver_data == mt_classes[i].name) {
+		if (driver_data_name == mt_classes[i].name) {
 			mtclass = &(mt_classes[i]);
 			break;
 		}