Adds basic v3 hardware support for newer devices not currently supported by the driver. Thanks to Tom Lin <tom_lin@xxxxxxxxxx> and Mark A. Stratman <stratman@xxxxxxxxx>, whose work on a previous patch revealed many of the details of the v3 protocol. Signed-off-by: Seth Forshee <seth.forshee@xxxxxxxxxxxxx> --- drivers/input/mouse/elantech.c | 255 ++++++++++++++++++++++++++++++++++------ drivers/input/mouse/elantech.h | 4 + 2 files changed, 223 insertions(+), 36 deletions(-) diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c index 3250356..e708a75 100644 --- a/drivers/input/mouse/elantech.c +++ b/drivers/input/mouse/elantech.c @@ -22,6 +22,10 @@ #include "psmouse.h" #include "elantech.h" +#define FW_VERSION_MAJOR_MASK 0xff0000 +#define FW_VERSION_MINOR_MASK 0x00ff00 +#define FW_VERSION_MICRO_MASK 0x0000ff + #define elantech_debug(fmt, ...) \ do { \ if (etd->debug) \ @@ -82,6 +86,7 @@ static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg, { struct elantech_data *etd = psmouse->private; unsigned char param[3]; + unsigned char command; int rc = 0; if (reg < 0x10 || reg > 0x26) @@ -90,9 +95,11 @@ static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg, if (reg > 0x11 && reg < 0x20) return -1; + command = (etd->hw_version == 3) ? ETP_REGISTER_RW : ETP_REGISTER_READ; + switch (etd->hw_version) { case 1: - if (psmouse_sliced_command(psmouse, ETP_REGISTER_READ) || + if (psmouse_sliced_command(psmouse, command) || psmouse_sliced_command(psmouse, reg) || ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETINFO)) { rc = -1; @@ -100,8 +107,9 @@ static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg, break; case 2: + case 3: if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || - elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READ) || + elantech_ps2_command(psmouse, NULL, command) || elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, reg) || elantech_ps2_command(psmouse, param, PSMOUSE_CMD_GETINFO)) { @@ -125,6 +133,7 @@ static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg, unsigned char val) { struct elantech_data *etd = psmouse->private; + unsigned char command; int rc = 0; if (reg < 0x10 || reg > 0x26) @@ -133,9 +142,11 @@ static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg, if (reg > 0x11 && reg < 0x20) return -1; + command = (etd->hw_version == 3) ? ETP_REGISTER_RW : ETP_REGISTER_WRITE; + switch (etd->hw_version) { case 1: - if (psmouse_sliced_command(psmouse, ETP_REGISTER_WRITE) || + if (psmouse_sliced_command(psmouse, command) || psmouse_sliced_command(psmouse, reg) || psmouse_sliced_command(psmouse, val) || ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11)) { @@ -145,7 +156,7 @@ static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg, case 2: if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || - elantech_ps2_command(psmouse, NULL, ETP_REGISTER_WRITE) || + elantech_ps2_command(psmouse, NULL, command) || elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || elantech_ps2_command(psmouse, NULL, reg) || elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) || @@ -164,6 +175,41 @@ static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg, } /* + * Set the elantech touchpad mode byte via special commands + */ +static int elantech_mode_command(struct psmouse *psmouse, unsigned char reg, + unsigned char val) +{ + struct elantech_data *etd = psmouse->private; + unsigned char param[3]; + int write_cmd, read_cmd; + + if (etd->hw_version == 3) { + write_cmd = ETP_REGISTER_RW; + read_cmd = ETP_REGISTER_RW; + } else { + write_cmd = ETP_REGISTER_READ; + read_cmd = ETP_REGISTER_WRITE; + } + + if (psmouse_sliced_command(psmouse, write_cmd) || + psmouse_sliced_command(psmouse, reg) || + psmouse_sliced_command(psmouse, val) || + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11)) + return -1; + + if (psmouse_sliced_command(psmouse, read_cmd) || + psmouse_sliced_command(psmouse, reg) || + ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETINFO)) + return -1; + + if (val != param[0]) + return -1; + + return 0; +} + +/* * Dump a complete mouse movement packet to the syslog */ static void elantech_packet_dump(unsigned char *packet, int size) @@ -223,7 +269,7 @@ static void elantech_report_absolute_v1(struct psmouse *psmouse) input_report_abs(dev, ABS_X, ((packet[1] & 0x0c) << 6) | packet[2]); input_report_abs(dev, ABS_Y, - ETP_YMAX_V1 - (((packet[1] & 0x03) << 8) | packet[3])); + etd->y_max - (((packet[1] & 0x03) << 8) | packet[3])); } input_report_key(dev, BTN_TOOL_FINGER, fingers == 1); @@ -298,7 +344,7 @@ static void elantech_report_absolute_v2(struct psmouse *psmouse) * byte 4: . . . . . . y9 y8 * byte 5: y7 y6 y5 y4 y3 y2 y1 y0 */ - y1 = ETP_YMAX_V2 - (((packet[4] & 0x03) << 8) | packet[5]); + y1 = etd->y_max - (((packet[4] & 0x03) << 8) | packet[5]); input_report_abs(dev, ABS_X, x1); input_report_abs(dev, ABS_Y, y1); @@ -316,14 +362,14 @@ static void elantech_report_absolute_v2(struct psmouse *psmouse) */ x1 = ((packet[0] & 0x10) << 4) | packet[1]; /* byte 2: ay7 ay6 ay5 ay4 ay3 ay2 ay1 ay0 */ - y1 = ETP_2FT_YMAX - (((packet[0] & 0x20) << 3) | packet[2]); + y1 = etd->y_max_2ft - (((packet[0] & 0x20) << 3) | packet[2]); /* * byte 3: . . by8 bx8 . . . . * byte 4: bx7 bx6 bx5 bx4 bx3 bx2 bx1 bx0 */ x2 = ((packet[3] & 0x10) << 4) | packet[4]; /* byte 5: by7 by8 by5 by4 by3 by2 by1 by0 */ - y2 = ETP_2FT_YMAX - (((packet[3] & 0x20) << 3) | packet[5]); + y2 = etd->y_max_2ft - (((packet[3] & 0x20) << 3) | packet[5]); /* * For compatibility with the X Synaptics driver scale up * one coordinate and report as ordinary mouse movent @@ -352,6 +398,83 @@ static void elantech_report_absolute_v2(struct psmouse *psmouse) input_sync(dev); } +/* + * Interpret complete data packets and report absolute mode input events for + * hardware version 3. (6 byte packets) + */ +static psmouse_ret_t elantech_report_absolute_v3(struct psmouse *psmouse) +{ + struct elantech_data *etd = psmouse->private; + struct input_dev *dev = psmouse->dev; + unsigned char *packet1 = psmouse->packet; + unsigned char *packet2 = NULL; + unsigned int fingers, width = 0, pres = 0; + unsigned int x1, y1, x2 = 0, y2 = 0; + + /* byte 0: n1 n0 . . . . R L */ + fingers = (packet1[0] & 0xc0) >> 6; + + /* + * Need to receive two packets for 2-touch events. If this + * is the first packet, store it away and wait for the second. + * We don't support parity in v3, so use that storage. + */ + if (fingers == 2 && (packet1[0] & 0x0c) == 0x04) { + memcpy(etd->parity, packet1, psmouse->pktsize); + return PSMOUSE_FULL_PACKET; + } + + /* + * If this the second packet of a two-fingered touch event we + * need to process the previous packet stashed away in + * etd->parity + */ + if (fingers == 2) { + packet1 = etd->parity; + packet2 = psmouse->packet; + } + + x1 = ((packet1[1] & 0x0f) << 8) | packet1[2]; + y1 = etd->y_max - (((packet1[4] & 0x0f) << 8) | packet1[5]); + + if (packet2) { + x2 = ((packet2[1] & 0x0f) << 8) | packet2[2]; + y2 = etd->y_max - (((packet2[4] & 0x0f) << 8) | packet2[5]); + } + + /* + * Reject crazy values we sometimes get from the hardware + */ + if (fingers != 0 && (x1 > etd->x_max || y1 > etd->y_max)) + return PSMOUSE_FULL_PACKET; + + pres = (packet1[1] & 0xf0) | ((packet1[4] & 0xf0) >> 4); + width = ((packet1[0] & 0x30) >> 2) | ((packet1[3] & 0x30) >> 4); + + elantech_report_semi_mt_data(dev, fingers, x1, y1, x2, y2); + + input_report_key(dev, BTN_TOUCH, fingers != 0); + input_report_key(dev, BTN_TOOL_FINGER, fingers == 1); + input_report_key(dev, BTN_TOOL_DOUBLETAP, fingers == 2); + input_report_key(dev, BTN_TOOL_TRIPLETAP, fingers == 3); + + if (etd->reports_pressure) { + input_report_abs(dev, ABS_PRESSURE, pres); + input_report_abs(dev, ABS_TOOL_WIDTH, width); + } + + if (fingers != 0) { + input_report_abs(dev, ABS_X, x1); + input_report_abs(dev, ABS_Y, y1); + } + + input_report_key(dev, BTN_LEFT, packet1[0] & 0x01); + input_report_key(dev, BTN_RIGHT, packet1[0] & 0x02); + + input_sync(dev); + return PSMOUSE_FULL_PACKET; +} + static int elantech_check_parity_v1(struct psmouse *psmouse) { struct elantech_data *etd = psmouse->private; @@ -401,6 +524,9 @@ static psmouse_ret_t elantech_process_byte(struct psmouse *psmouse) /* We don't know how to check parity in protocol v2 */ elantech_report_absolute_v2(psmouse); break; + case 3: + /* We don't know how to check parity in protocol v3 */ + return elantech_report_absolute_v3(psmouse); } return PSMOUSE_FULL_PACKET; @@ -433,13 +559,16 @@ static int elantech_set_absolute_mode(struct psmouse *psmouse) etd->reg_21 = 0x60; /* 0x00 */ if (elantech_write_reg(psmouse, 0x10, etd->reg_10) || elantech_write_reg(psmouse, 0x11, etd->reg_11) || - elantech_write_reg(psmouse, 0x21, etd->reg_21)) { + elantech_write_reg(psmouse, 0x21, etd->reg_21)) rc = -1; - break; - } + break; + case 3: + etd->reg_10 = 0x0f; + rc = elantech_mode_command(psmouse, 0x10, etd->reg_10); + break; } - if (rc == 0) { + if (etd->hw_version != 3 && rc == 0) { /* * Read back reg 0x10. For hardware version 1 we must make * sure the absolute mode bit is set. For hardware version 2 @@ -473,10 +602,11 @@ static int elantech_set_absolute_mode(struct psmouse *psmouse) /* * Set the appropriate event bits for the input subsystem */ -static void elantech_set_input_params(struct psmouse *psmouse) +static int elantech_set_input_params(struct psmouse *psmouse) { struct input_dev *dev = psmouse->dev; struct elantech_data *etd = psmouse->private; + unsigned char param[3]; __set_bit(EV_KEY, dev->evbit); __set_bit(EV_ABS, dev->evbit); @@ -492,20 +622,41 @@ static void elantech_set_input_params(struct psmouse *psmouse) switch (etd->hw_version) { case 1: + etd->x_max = ETP_XMAX_V1; + etd->y_max = ETP_YMAX_V1; + /* Rocker button */ if (etd->fw_version < 0x020000 && (etd->capabilities & ETP_CAP_HAS_ROCKER)) { __set_bit(BTN_FORWARD, dev->keybit); __set_bit(BTN_BACK, dev->keybit); } - input_set_abs_params(dev, ABS_X, ETP_XMIN_V1, ETP_XMAX_V1, 0, 0); - input_set_abs_params(dev, ABS_Y, ETP_YMIN_V1, ETP_YMAX_V1, 0, 0); + input_set_abs_params(dev, ABS_X, ETP_XMIN_V1, etd->x_max, 0, 0); + input_set_abs_params(dev, ABS_Y, ETP_YMIN_V1, etd->y_max, 0, 0); break; case 2: + case 3: + if (etd->hw_version == 3) { + if (ps2_command(&psmouse->ps2dev, NULL, ETP_PS2_CUSTOM_COMMAND) || + ps2_command(&psmouse->ps2dev, NULL, 0x00) || + ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETINFO)) + return -1; + + etd->x_max = (param[0] & 0xf) << 8 | param[1]; + etd->y_max = (param[0] & 0xf0) << 4 | param[2]; + etd->y_max_2ft = etd->y_max; + elantech_debug("x_max = %d, y_max = %d\n", + etd->x_max, etd->y_max); + } else { + etd->x_max = ETP_XMAX_V2; + etd->y_max = ETP_YMAX_V2; + etd->y_max_2ft = ETP_2FT_YMAX; + } + __set_bit(BTN_TOOL_QUADTAP, dev->keybit); - input_set_abs_params(dev, ABS_X, ETP_XMIN_V2, ETP_XMAX_V2, 0, 0); - input_set_abs_params(dev, ABS_Y, ETP_YMIN_V2, ETP_YMAX_V2, 0, 0); + input_set_abs_params(dev, ABS_X, ETP_XMIN_V2, etd->x_max, 0, 0); + input_set_abs_params(dev, ABS_Y, ETP_YMIN_V2, etd->y_max, 0, 0); if (etd->reports_pressure) { input_set_abs_params(dev, ABS_PRESSURE, ETP_PMIN_V2, ETP_PMAX_V2, 0, 0); @@ -514,10 +665,12 @@ static void elantech_set_input_params(struct psmouse *psmouse) } __set_bit(INPUT_PROP_SEMI_MT, dev->propbit); input_mt_init_slots(dev, 2); - input_set_abs_params(dev, ABS_MT_POSITION_X, ETP_XMIN_V2, ETP_XMAX_V2, 0, 0); - input_set_abs_params(dev, ABS_MT_POSITION_Y, ETP_YMIN_V2, ETP_YMAX_V2, 0, 0); + input_set_abs_params(dev, ABS_MT_POSITION_X, ETP_XMIN_V2, etd->x_max, 0, 0); + input_set_abs_params(dev, ABS_MT_POSITION_Y, ETP_YMIN_V2, etd->y_max, 0, 0); break; } + + return 0; } struct elantech_attr_data { @@ -639,10 +792,17 @@ static bool elantech_is_signature_valid(const unsigned char *param) /* * Use magic knock to detect Elantech touchpad */ + +static const char elantech_magic_knocks[][3] = { + { '\x3c', '\x03', '\xc8' }, + { '\x3c', '\x03', '\x00' }, +}; + int elantech_detect(struct psmouse *psmouse, bool set_properties) { struct ps2dev *ps2dev = &psmouse->ps2dev; unsigned char param[3]; + int i; ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); @@ -655,11 +815,15 @@ int elantech_detect(struct psmouse *psmouse, bool set_properties) return -1; } - /* - * Report this in case there are Elantech models that use a different - * set of magic numbers - */ - if (param[0] != 0x3c || param[1] != 0x03 || param[2] != 0xc8) { + for (i = 0; i < ARRAY_SIZE(elantech_magic_knocks); i++) { + if (!memcmp(param, elantech_magic_knocks[i], sizeof(param))) + break; + } + if (i >= ARRAY_SIZE(elantech_magic_knocks)) { + /* + * Report this in case there are Elantech models that use a + * different set of magic numbers + */ pr_debug("unexpected magic knock result 0x%02x, 0x%02x, 0x%02x.\n", param[0], param[1], param[2]); return -1; @@ -749,23 +913,38 @@ int elantech_init(struct psmouse *psmouse) etd->fw_version = (param[0] << 16) | (param[1] << 8) | param[2]; - /* - * Assume every version greater than this is new EeePC style - * hardware with 6 byte packets - */ - if (etd->fw_version >= 0x020030) { + if (etd->fw_version < 0x020030) { + etd->hw_version = 1; + etd->paritycheck = 1; + } else if ((etd->fw_version & ~FW_VERSION_MICRO_MASK) <= 0x140000) { etd->hw_version = 2; /* For now show extra debug information */ etd->debug = 1; - /* Don't know how to do parity checking for version 2 */ - etd->paritycheck = 0; if (etd->fw_version >= 0x020800) etd->reports_pressure = true; - } else { - etd->hw_version = 1; - etd->paritycheck = 1; + unsigned char dummy[3]; + error = 0; + if (ps2_command(&psmouse->ps2dev, NULL, ETP_PS2_CUSTOM_COMMAND) || + ps2_command(&psmouse->ps2dev, NULL, 0x01) || + ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETINFO)) + error = -1; + else if ( + ps2_command(&psmouse->ps2dev, NULL, ETP_PS2_CUSTOM_COMMAND) || + ps2_command(&psmouse->ps2dev, NULL, 0x04) || + ps2_command(&psmouse->ps2dev, dummy, PSMOUSE_CMD_GETINFO)) + error = -1; + + if (!error && + (param[0] & 0x0f) >= 5 && (param[1] & 0x0f) >= 0x06) { + etd->hw_version = 3; + etd->debug = 1; + etd->reports_pressure = true; + } else { + pr_debug("did not detect elantech firmware version\n"); + goto init_fail; + } } pr_info("assuming hardware version %d, firmware version %d.%d.%d\n", @@ -794,7 +973,11 @@ int elantech_init(struct psmouse *psmouse) goto init_fail; } - elantech_set_input_params(psmouse); + error = elantech_set_input_params(psmouse); + if (error) { + pr_err("failed to set input parameters\n"); + goto init_fail; + } error = sysfs_create_group(&psmouse->ps2dev.serio->dev.kobj, &elantech_attr_group); @@ -806,7 +989,7 @@ int elantech_init(struct psmouse *psmouse) psmouse->protocol_handler = elantech_process_byte; psmouse->disconnect = elantech_disconnect; psmouse->reconnect = elantech_reconnect; - psmouse->pktsize = etd->hw_version == 2 ? 6 : 4; + psmouse->pktsize = etd->hw_version == 1 ? 4 : 6; return 0; diff --git a/drivers/input/mouse/elantech.h b/drivers/input/mouse/elantech.h index fabb2b9..b896cc8 100644 --- a/drivers/input/mouse/elantech.h +++ b/drivers/input/mouse/elantech.h @@ -22,6 +22,7 @@ /* * Command values for register reading or writing */ +#define ETP_REGISTER_RW 0x00 #define ETP_REGISTER_READ 0x10 #define ETP_REGISTER_WRITE 0x11 @@ -110,6 +111,9 @@ struct elantech_data { bool reports_pressure; unsigned char hw_version; unsigned int fw_version; + unsigned int x_max; + unsigned int y_max; + unsigned int y_max_2ft; unsigned int single_finger_reports; unsigned char parity[256]; }; -- 1.7.4.1 -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html