]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
Input: elantech - fix protocol errors for some trackpoints in SMBus mode
authorjingle.wu <jingle.wu@emc.com.tw>
Fri, 11 Dec 2020 07:49:16 +0000 (23:49 -0800)
committerDmitry Torokhov <dmitry.torokhov@gmail.com>
Fri, 11 Dec 2020 08:15:05 +0000 (00:15 -0800)
There are some version of Elan trackpads that send incorrect data when
in SMbus mode, unless they are switched to use 0x5f reports instead of
standard 0x5e. This patch implements querying device to retrieve chips
identifying data, and switching it, when needed to the alternative
report.

Signed-off-by: Jingle Wu <jingle.wu@emc.com.tw>
Link: https://lore.kernel.org/r/20201211071531.32413-1-jingle.wu@emc.com.tw
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
drivers/input/mouse/elantech.c
drivers/input/mouse/elantech.h

index 47cd0e7f79bd16a965a4e72423b45f95d58a6265..97381e2e03bae90df7b8f0a9367be68a8dc1363d 100644 (file)
@@ -89,6 +89,47 @@ static int elantech_ps2_command(struct psmouse *psmouse,
        return rc;
 }
 
+/*
+ * Send an Elantech style special command to read 3 bytes from a register
+ */
+static int elantech_read_reg_params(struct psmouse *psmouse, u8 reg, u8 *param)
+{
+       if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
+           elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READWRITE) ||
+           elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
+           elantech_ps2_command(psmouse, NULL, reg) ||
+           elantech_ps2_command(psmouse, param, PSMOUSE_CMD_GETINFO)) {
+               psmouse_err(psmouse,
+                           "failed to read register %#02x\n", reg);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+/*
+ * Send an Elantech style special command to write a register with a parameter
+ */
+static int elantech_write_reg_params(struct psmouse *psmouse, u8 reg, u8 *param)
+{
+       if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
+           elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READWRITE) ||
+           elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
+           elantech_ps2_command(psmouse, NULL, reg) ||
+           elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
+           elantech_ps2_command(psmouse, NULL, param[0]) ||
+           elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
+           elantech_ps2_command(psmouse, NULL, param[1]) ||
+           elantech_ps2_command(psmouse, NULL, PSMOUSE_CMD_SETSCALE11)) {
+               psmouse_err(psmouse,
+                           "failed to write register %#02x with value %#02x%#02x\n",
+                           reg, param[0], param[1]);
+               return -EIO;
+       }
+
+       return 0;
+}
+
 /*
  * Send an Elantech style special command to read a value from a register
  */
@@ -1529,19 +1570,35 @@ static const struct dmi_system_id no_hw_res_dmi_table[] = {
        { }
 };
 
+/*
+ * Change Report id 0x5E to 0x5F.
+ */
+static int elantech_change_report_id(struct psmouse *psmouse)
+{
+       unsigned char param[2] = { 0x10, 0x03 };
+
+       if (elantech_write_reg_params(psmouse, 0x7, param) ||
+           elantech_read_reg_params(psmouse, 0x7, param) ||
+           param[0] != 0x10 || param[1] != 0x03) {
+               psmouse_err(psmouse, "Unable to change report ID to 0x5f.\n");
+               return -EIO;
+       }
+
+       return 0;
+}
 /*
  * determine hardware version and set some properties according to it.
  */
 static int elantech_set_properties(struct elantech_device_info *info)
 {
        /* This represents the version of IC body. */
-       int ver = (info->fw_version & 0x0f0000) >> 16;
+       info->ic_version = (info->fw_version & 0x0f0000) >> 16;
 
        /* Early version of Elan touchpads doesn't obey the rule. */
        if (info->fw_version < 0x020030 || info->fw_version == 0x020600)
                info->hw_version = 1;
        else {
-               switch (ver) {
+               switch (info->ic_version) {
                case 2:
                case 4:
                        info->hw_version = 2;
@@ -1557,6 +1614,11 @@ static int elantech_set_properties(struct elantech_device_info *info)
                }
        }
 
+       /* Get information pattern for hw_version 4 */
+       info->pattern = 0x00;
+       if (info->ic_version == 0x0f && (info->fw_version & 0xff) <= 0x02)
+               info->pattern = info->fw_version & 0xff;
+
        /* decide which send_cmd we're gonna use early */
        info->send_cmd = info->hw_version >= 3 ? elantech_send_cmd :
                                                 synaptics_send_cmd;
@@ -1598,6 +1660,7 @@ static int elantech_query_info(struct psmouse *psmouse,
 {
        unsigned char param[3];
        unsigned char traces;
+       unsigned char ic_body[3];
 
        memset(info, 0, sizeof(*info));
 
@@ -1640,6 +1703,21 @@ static int elantech_query_info(struct psmouse *psmouse,
                             info->samples[2]);
        }
 
+       if (info->pattern > 0x00 && info->ic_version == 0xf) {
+               if (info->send_cmd(psmouse, ETP_ICBODY_QUERY, ic_body)) {
+                       psmouse_err(psmouse, "failed to query ic body\n");
+                       return -EINVAL;
+               }
+               info->ic_version = be16_to_cpup((__be16 *)ic_body);
+               psmouse_info(psmouse,
+                            "Elan ic body: %#04x, current fw version: %#02x\n",
+                            info->ic_version, ic_body[2]);
+       }
+
+       info->product_id = be16_to_cpup((__be16 *)info->samples);
+       if (info->pattern == 0x00)
+               info->product_id &= 0xff;
+
        if (info->samples[1] == 0x74 && info->hw_version == 0x03) {
                /*
                 * This module has a bug which makes absolute mode
@@ -1654,6 +1732,23 @@ static int elantech_query_info(struct psmouse *psmouse,
        /* The MSB indicates the presence of the trackpoint */
        info->has_trackpoint = (info->capabilities[0] & 0x80) == 0x80;
 
+       if (info->has_trackpoint && info->ic_version == 0x0011 &&
+           (info->product_id == 0x08 || info->product_id == 0x09 ||
+            info->product_id == 0x0d || info->product_id == 0x0e)) {
+               /*
+                * This module has a bug which makes trackpoint in SMBus
+                * mode return invalid data unless trackpoint is switched
+                * from using 0x5e reports to 0x5f. If we are not able to
+                * make the switch, let's abort initialization so we'll be
+                * using standard PS/2 protocol.
+                */
+               if (elantech_change_report_id(psmouse)) {
+                       psmouse_info(psmouse,
+                                    "Trackpoint report is broken, forcing standard PS/2 protocol\n");
+                       return -ENODEV;
+               }
+       }
+
        info->x_res = 31;
        info->y_res = 31;
        if (info->hw_version == 4) {
index e0a3e59d4f1bbd46426bc802218e3f9d9978ece6..571e6ca11d33b54fc6f784c8aa2c5f97b95c11c5 100644 (file)
@@ -18,6 +18,7 @@
 #define ETP_CAPABILITIES_QUERY         0x02
 #define ETP_SAMPLE_QUERY               0x03
 #define ETP_RESOLUTION_QUERY           0x04
+#define ETP_ICBODY_QUERY               0x05
 
 /*
  * Command values for register reading or writing
@@ -140,7 +141,10 @@ struct elantech_device_info {
        unsigned char samples[3];
        unsigned char debug;
        unsigned char hw_version;
+       unsigned char pattern;
        unsigned int fw_version;
+       unsigned int ic_version;
+       unsigned int product_id;
        unsigned int x_min;
        unsigned int y_min;
        unsigned int x_max;