diff options
author | Stefan Sperling <stsp@cvs.openbsd.org> | 2012-10-29 11:54:46 +0000 |
---|---|---|
committer | Stefan Sperling <stsp@cvs.openbsd.org> | 2012-10-29 11:54:46 +0000 |
commit | 6ee155187d52e27d3ee505be2e652bce6895f30b (patch) | |
tree | 58ed059f12284941c62fecfeb0755d246279a1c4 /sys/dev/pckbc/pms.c | |
parent | f504ff727b7ca45077effbdfa6bc84378eeb4b11 (diff) |
Add support for Elantech touchpads to pms(4). This allows the X synaptics(4)
input driver to configure these touchpads properly, e.g. toggle tap-to-click,
two-finger scrolling, edge-scrolling, etc. So far these pads were working
only in PS/2 mouse compatibility mode.
Support for up to hardware version 3 has been implemented. But only version 3
has been tested so versions 1 and 2 remain disabled via #ifdef notyet until
someone tests (bugs in this code could seriously mess up the mouse and I
don't want to end up shipping such bugs in 5.3).
help and ok mpi@ shadchin@
Diffstat (limited to 'sys/dev/pckbc/pms.c')
-rw-r--r-- | sys/dev/pckbc/pms.c | 752 |
1 files changed, 751 insertions, 1 deletions
diff --git a/sys/dev/pckbc/pms.c b/sys/dev/pckbc/pms.c index 8d7105f1af7..297d338033b 100644 --- a/sys/dev/pckbc/pms.c +++ b/sys/dev/pckbc/pms.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pms.c,v 1.31 2012/07/22 18:28:36 shadchin Exp $ */ +/* $OpenBSD: pms.c,v 1.32 2012/10/29 11:54:45 stsp Exp $ */ /* $NetBSD: psm.c,v 1.11 2000/06/05 22:20:57 sommerfeld Exp $ */ /*- @@ -57,6 +57,9 @@ struct pms_protocol { #define PMS_INTELLI 1 #define PMS_SYNAPTICS 2 #define PMS_ALPS 3 +#define PMS_ELANTECH_V1 4 +#define PMS_ELANTECH_V2 5 +#define PMS_ELANTECH_V3 6 u_int packetsize; int (*enable)(struct pms_softc *); int (*ioctl)(struct pms_softc *, u_long, caddr_t, int, struct proc *); @@ -108,6 +111,25 @@ struct alps_softc { #define ALPS_PRESSURE 40 }; +struct elantech_softc { + int flags; +#define ELANTECH_F_REPORTS_PRESSURE 0x01 +#define ELANTECH_F_HAS_ROCKER 0x02 +#define ELANTECH_F_2FINGER_PACKET 0x04 +#define ELANTECH_F_HW_V1_OLD 0x08 + + int min_x, min_y; + int max_x, max_y; + + u_char parity[256]; + u_char p1, p2, p3; + + /* Compat mode */ + int wsmode; + int old_x, old_y; + u_int old_buttons; +}; + struct pms_softc { /* driver status information */ struct device sc_dev; @@ -129,6 +151,7 @@ struct pms_softc { /* driver status information */ const struct pms_protocol *protocol; struct synaptics_softc *synaptics; struct alps_softc *alps; + struct elantech_softc *elantech; u_char packet[8]; @@ -227,6 +250,18 @@ int pms_ioctl_alps(struct pms_softc *, u_long, caddr_t, int, struct proc *); int pms_sync_alps(struct pms_softc *, int); void pms_proc_alps(struct pms_softc *); +int pms_enable_elantech_v1(struct pms_softc *); +int pms_enable_elantech_v2(struct pms_softc *); +int pms_enable_elantech_v3(struct pms_softc *); +int pms_ioctl_elantech(struct pms_softc *, u_long, caddr_t, int, + struct proc *); +int pms_sync_elantech_v1(struct pms_softc *, int); +int pms_sync_elantech_v2(struct pms_softc *, int); +int pms_sync_elantech_v3(struct pms_softc *, int); +void pms_proc_elantech_v1(struct pms_softc *); +void pms_proc_elantech_v2(struct pms_softc *); +void pms_proc_elantech_v3(struct pms_softc *); + int synaptics_set_mode(struct pms_softc *, int); int synaptics_query(struct pms_softc *, int, int *); int synaptics_get_hwinfo(struct pms_softc *); @@ -235,6 +270,17 @@ void synaptics_sec_proc(struct pms_softc *); int alps_sec_proc(struct pms_softc *); int alps_get_hwinfo(struct pms_softc *); +int elantech_knock(struct pms_softc *); +void elantech_send_input(struct pms_softc *, u_int, int, int, int, int); +int elantech_get_hwinfo_v1(struct pms_softc *); +int elantech_get_hwinfo_v2(struct pms_softc *); +int elantech_get_hwinfo_v3(struct pms_softc *); +int elantech_ps2_cmd(struct pms_softc *, u_char); +int elantech_set_absolute_mode_v1(struct pms_softc *); +int elantech_set_absolute_mode_v2(struct pms_softc *); +int elantech_set_absolute_mode_v3(struct pms_softc *); + + struct cfattach pms_ca = { sizeof(struct pms_softc), pmsprobe, pmsattach, NULL, pmsactivate @@ -293,6 +339,35 @@ const struct pms_protocol pms_protocols[] = { pms_proc_alps, NULL }, +#ifdef notyet + /* Elantech touchpad (hardware version 1) */ + { + PMS_ELANTECH_V1, 4, + pms_enable_elantech_v1, + pms_ioctl_elantech, + pms_sync_elantech_v1, + pms_proc_elantech_v1, + NULL + }, + /* Elantech touchpad (hardware version 2) */ + { + PMS_ELANTECH_V2, 6, + pms_enable_elantech_v2, + pms_ioctl_elantech, + pms_sync_elantech_v2, + pms_proc_elantech_v2, + NULL + }, +#endif + /* Elantech touchpad (hardware version 3) */ + { + PMS_ELANTECH_V3, 6, + pms_enable_elantech_v3, + pms_ioctl_elantech, + pms_sync_elantech_v3, + pms_proc_elantech_v3, + NULL + }, }; int @@ -1360,3 +1435,678 @@ pms_proc_alps(struct pms_softc *sc) alps->old_buttons = buttons; } } + +int +elantech_set_absolute_mode_v1(struct pms_softc *sc) +{ + int i; + u_char resp[3]; + + /* Enable absolute mode. Magic numbers from Linux driver. */ + if (pms_spec_cmd(sc, ELANTECH_CMD_WRITE_REG) || + pms_spec_cmd(sc, 0x10) || + pms_spec_cmd(sc, 0x16) || + pms_set_scaling(sc, 1) || + pms_spec_cmd(sc, ELANTECH_CMD_WRITE_REG) || + pms_spec_cmd(sc, 0x11) || + pms_spec_cmd(sc, 0x8f) || + pms_set_scaling(sc, 1)) + return (-1); + + /* Read back reg 0x10 to ensure hardware is ready. */ + for (i = 0; i < 5; i++) { + if (pms_spec_cmd(sc, ELANTECH_CMD_READ_REG) || + pms_spec_cmd(sc, 0x10) || + pms_get_status(sc, resp) == 0) + break; + delay(2000); + } + if (i == 5) + return (-1); + + if ((resp[0] & ELANTECH_ABSOLUTE_MODE) == 0) + return (-1); + + return (0); +} + +int +elantech_set_absolute_mode_v2(struct pms_softc *sc) +{ + int i; + u_char resp[3]; + + /* Enable absolute mode. Magic numbers from Linux driver. */ + if (elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, ELANTECH_CMD_WRITE_REG) || + elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, 0x10) || + elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, 0x54) || + pms_set_scaling(sc, 1) || + elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, ELANTECH_CMD_WRITE_REG) || + elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, 0x11) || + elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, 0x88) || + pms_set_scaling(sc, 1) || + elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, ELANTECH_CMD_WRITE_REG) || + elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, 0x21) || + elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, 0x88) || + pms_set_scaling(sc, 1)) + return (-1); + + /* Read back reg 0x10 to ensure hardware is ready. */ + for (i = 0; i < 5; i++) { + if (elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, ELANTECH_CMD_READ_REG) || + elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, 0x10) || + pms_get_status(sc, resp) == 0) + break; + delay(2000); + } + if (i == 5) + return (-1); + + return (0); +} + +int +elantech_set_absolute_mode_v3(struct pms_softc *sc) +{ + int i; + u_char resp[3]; + + /* Enable absolute mode. Magic numbers from Linux driver. */ + if (elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, ELANTECH_CMD_READ_WRITE_REG) || + elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, 0x10) || + elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, 0x0b) || + pms_set_scaling(sc, 1)) + return (-1); + + /* Read back reg 0x10 to ensure hardware is ready. */ + for (i = 0; i < 5; i++) { + if (elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, ELANTECH_CMD_READ_WRITE_REG) || + elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) || + elantech_ps2_cmd(sc, 0x10) || + pms_get_status(sc, resp) == 0) + break; + delay(2000); + } + if (i == 5) + return (-1); + + return (0); +} + +int +elantech_get_hwinfo_v1(struct pms_softc *sc) +{ + struct elantech_softc *elantech = sc->elantech; + int fw_version; + u_char capabilities[3]; + + if (synaptics_query(sc, ELANTECH_QUE_FW_VER, &fw_version)) + return (-1); + + if (fw_version < 0x20030 || fw_version == 0x20600) { + if (fw_version < 0x20000) + elantech->flags |= ELANTECH_F_HW_V1_OLD; + } else + return (-1); + + if (pms_spec_cmd(sc, ELANTECH_QUE_CAPABILITIES) || + pms_get_status(sc, capabilities)) + return (-1); + + if (capabilities[0] & ELANTECH_CAP_HAS_ROCKER) + elantech->flags |= ELANTECH_F_HAS_ROCKER; + + if (elantech_set_absolute_mode_v1(sc)) + return (-1); + + elantech->min_x = ELANTECH_V1_X_MIN; + elantech->max_x = ELANTECH_V1_X_MAX; + elantech->min_y = ELANTECH_V1_Y_MIN; + elantech->max_y = ELANTECH_V1_Y_MAX; + + return (0); +} + +int +elantech_get_hwinfo_v2(struct pms_softc *sc) +{ + struct elantech_softc *elantech = sc->elantech; + int fw_version, ic_ver; + u_char capabilities[3]; + int i, fixed_dpi; + u_char resp[3]; + + if (synaptics_query(sc, ELANTECH_QUE_FW_VER, &fw_version)) + return (-1); + + ic_ver = (fw_version & 0x0f0000) >> 16; + if (ic_ver != 2 && ic_ver != 4) + return (-1); + + if (fw_version >= 0x20800) + elantech->flags |= ELANTECH_F_REPORTS_PRESSURE; + + if (pms_spec_cmd(sc, ELANTECH_QUE_CAPABILITIES) || + pms_get_status(sc, capabilities)) + return (-1); + + if (elantech_set_absolute_mode_v2(sc)) + return (-1); + + if (fw_version == 0x20800 || fw_version == 0x20b00 || + fw_version == 0x20030) { + elantech->max_x = ELANTECH_V2_X_MAX; + elantech->max_y = ELANTECH_V2_Y_MAX; + } else { + if (pms_spec_cmd(sc, ELANTECH_QUE_FW_ID) || + pms_get_status(sc, resp)) + return (-1); + fixed_dpi = resp[1] & 0x10; + i = (fw_version > 0x20800 && fw_version < 0x20900) ? 1 : 2; + if ((fw_version >> 16) == 0x14 && fixed_dpi) { + if (pms_spec_cmd(sc, ELANTECH_QUE_SAMPLE) || + pms_get_status(sc, resp)) + return (-1); + elantech->max_x = (capabilities[1] - i) * resp[1] / 2; + elantech->max_y = (capabilities[2] - i) * resp[2] / 2; + } else if (fw_version == 0x040216) { + elantech->max_x = 819; + elantech->max_y = 405; + } else if (fw_version == 0x040219 || fw_version == 0x040215) { + elantech->max_x = 900; + elantech->max_y = 500; + } else { + elantech->max_x = (capabilities[1] - i) * 64; + elantech->max_y = (capabilities[2] - i) * 64; + } + } + + return (0); +} + +int +elantech_get_hwinfo_v3(struct pms_softc *sc) +{ + struct elantech_softc *elantech = sc->elantech; + int fw_version; + u_char resp[3]; + + if (synaptics_query(sc, ELANTECH_QUE_FW_VER, &fw_version)) + return (-1); + + if (((fw_version & 0x0f0000) >> 16) != 5) + return (-1); + + elantech->flags |= ELANTECH_F_REPORTS_PRESSURE; + + if (elantech_set_absolute_mode_v3(sc)) + return (-1); + + if (pms_spec_cmd(sc, ELANTECH_QUE_FW_ID) || + pms_get_status(sc, resp)) + return (-1); + + elantech->max_x = (resp[0] & 0x0f) << 8 | resp[1]; + elantech->max_y = (resp[0] & 0xf0) << 4 | resp[2]; + + return (0); +} + +int +elantech_ps2_cmd(struct pms_softc *sc, u_char command) +{ + u_char cmd[1]; + + cmd[0] = command; + return (pms_cmd(sc, cmd, 1, NULL, 0)); +} + +int +elantech_knock(struct pms_softc *sc) +{ + u_char resp[3]; + + if (pms_dev_disable(sc) || + pms_set_scaling(sc, 1) || + pms_set_scaling(sc, 1) || + pms_set_scaling(sc, 1) || + pms_get_status(sc, resp) || + resp[0] != PMS_ELANTECH_MAGIC1 || + resp[1] != PMS_ELANTECH_MAGIC2 || + (resp[2] != PMS_ELANTECH_MAGIC3_1 && + resp[2] != PMS_ELANTECH_MAGIC3_2)) + return (-1); + + return (0); +} + +int +pms_enable_elantech_v1(struct pms_softc *sc) +{ + struct elantech_softc *elantech = sc->elantech; + int i; + + if (elantech_knock(sc)) + return (0); + + if (sc->elantech == NULL) { + sc->elantech = elantech = malloc(sizeof(struct elantech_softc), + M_DEVBUF, M_WAITOK | M_ZERO); + if (elantech == NULL) { + printf("%s: elantech: not enough memory\n", + DEVNAME(sc)); + goto err; + } + + if (elantech_get_hwinfo_v1(sc)) { + free(sc->elantech, M_DEVBUF); + sc->elantech = NULL; + goto err; + } + + printf("%s: Elantech Touchpad, version %d\n", DEVNAME(sc), 1); + } else if (elantech_set_absolute_mode_v1(sc)) { + free(sc->elantech, M_DEVBUF); + sc->elantech = NULL; + goto err; + } + + for (i = 0; i < nitems(sc->elantech->parity); i++) + sc->elantech->parity[i] = sc->elantech->parity[i & (i - 1)] ^ 1; + + return (1); + +err: + pms_reset(sc); + + return (0); +} + +int +pms_enable_elantech_v2(struct pms_softc *sc) +{ + struct elantech_softc *elantech = sc->elantech; + + if (elantech_knock(sc)) + return (0); + + if (sc->elantech == NULL) { + sc->elantech = elantech = malloc(sizeof(struct elantech_softc), + M_DEVBUF, M_WAITOK | M_ZERO); + if (elantech == NULL) { + printf("%s: elantech: not enough memory\n", + DEVNAME(sc)); + goto err; + } + + if (elantech_get_hwinfo_v2(sc)) { + free(sc->elantech, M_DEVBUF); + sc->elantech = NULL; + goto err; + } + + printf("%s: Elantech Touchpad, version %d\n", DEVNAME(sc), 2); + } else if (elantech_set_absolute_mode_v2(sc)) { + free(sc->elantech, M_DEVBUF); + sc->elantech = NULL; + goto err; + } + + return (1); + +err: + pms_reset(sc); + + return (0); +} + +int +pms_enable_elantech_v3(struct pms_softc *sc) +{ + struct elantech_softc *elantech = sc->elantech; + + if (elantech_knock(sc)) + return (0); + + if (sc->elantech == NULL) { + sc->elantech = elantech = malloc(sizeof(struct elantech_softc), + M_DEVBUF, M_WAITOK | M_ZERO); + if (elantech == NULL) { + printf("%s: elantech: not enough memory\n", + DEVNAME(sc)); + goto err; + } + + if (elantech_get_hwinfo_v3(sc)) { + free(sc->elantech, M_DEVBUF); + sc->elantech = NULL; + goto err; + } + + printf("%s: Elantech Touchpad, version %d\n", DEVNAME(sc), 3); + } else if (elantech_set_absolute_mode_v3(sc)) { + free(sc->elantech, M_DEVBUF); + sc->elantech = NULL; + goto err; + } + + return (1); + +err: + pms_reset(sc); + + return (0); +} + +int +pms_ioctl_elantech(struct pms_softc *sc, u_long cmd, caddr_t data, int flag, + struct proc *p) +{ + struct elantech_softc *elantech = sc->elantech; + struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data; + int wsmode; + + switch (cmd) { + case WSMOUSEIO_GTYPE: + *(u_int *)data = WSMOUSE_TYPE_ELANTECH; + break; + case WSMOUSEIO_GCALIBCOORDS: + wsmc->minx = elantech->min_x; + wsmc->maxx = elantech->max_x; + wsmc->miny = elantech->min_y; + wsmc->maxy = elantech->max_y; + wsmc->swapxy = 0; + wsmc->resx = 0; + wsmc->resy = 0; + break; + case WSMOUSEIO_SETMODE: + wsmode = *(u_int *)data; + if (wsmode != WSMOUSE_COMPAT && wsmode != WSMOUSE_NATIVE) + return (EINVAL); + elantech->wsmode = wsmode; + break; + default: + return (-1); + } + return (0); +} + +int +pms_sync_elantech_v1(struct pms_softc *sc, int data) +{ + struct elantech_softc *elantech = sc->elantech; + u_char p; + + switch (sc->inputstate) { + case 0: + if (elantech->flags & ELANTECH_F_HW_V1_OLD) { + elantech->p1 = (data & 0x20) >> 5; + elantech->p2 = (data & 0x10) >> 4; + } else { + elantech->p1 = (data & 0x10) >> 4; + elantech->p2 = (data & 0x20) >> 5; + } + elantech->p3 = (data & 0x04) >> 2; + return (0); + case 1: + p = elantech->p1; + break; + case 2: + p = elantech->p2; + break; + case 3: + p = elantech->p3; + break; + default: + return (-1); + } + + if (data < 0 || data >= nitems(elantech->parity) || + elantech->parity[data] != p) + return (-1); + + return (0); +} + +int +pms_sync_elantech_v2(struct pms_softc *sc, int data) +{ + struct elantech_softc *elantech = sc->elantech; + + /* Variants reporting pressure always have the same constant bits. */ + if (elantech->flags & ELANTECH_F_REPORTS_PRESSURE) { + if (sc->inputstate == 0 && (data & 0x0c) != 0x04) + return (-1); + if (sc->inputstate == 3 && (data & 0x0f) != 0x02) + return (-1); + return (0); + } + + /* For variants not reporting pressure, 1 and 3 finger touch packets + * have different constant bits than 2 finger touch pakets. */ + switch (sc->inputstate) { + case 0: + if ((data & 0xc0) == 0x80) { + if ((data & 0x0c) != 0x0c) + return (-1); + elantech->flags |= ELANTECH_F_2FINGER_PACKET; + } else { + if ((data & 0x3c) != 0x3c) + return (-1); + elantech->flags &= ~ELANTECH_F_2FINGER_PACKET; + } + break; + case 1: + case 4: + if (elantech->flags & ELANTECH_F_2FINGER_PACKET) + break; + if ((data & 0xf0) != 0x00) + return (-1); + break; + case 3: + if (elantech->flags & ELANTECH_F_2FINGER_PACKET) { + if ((data & 0x0e) != 0x08) + return (-1); + } else { + if ((data & 0x3e) != 0x38) + return (-1); + } + break; + default: + break; + } + + return (0); +} + +int +pms_sync_elantech_v3(struct pms_softc *sc, int data) +{ + switch (sc->inputstate) { + case 0: + if ((data & 0x0c) != 0x04 && (data & 0x0c) != 0x0c) + return (-1); + break; + case 3: + if ((data & 0xcf) != 0x02 && (data & 0xce) != 0x0c) + return (-1); + break; + } + + return (0); +} + +void +pms_proc_elantech_v1(struct pms_softc *sc) +{ + struct elantech_softc *elantech = sc->elantech; + u_int buttons; + int x, y, w, z; + + if (elantech->flags & ELANTECH_F_HW_V1_OLD) + w = ((sc->packet[1] & 0x80) >> 7) + + ((sc->packet[1] & 0x30) >> 4); + else + w = (sc->packet[0] & 0xc0) >> 6; + + /* Hardware version 1 doesn't report pressure. */ + if (w) { + x = ((sc->packet[1] & 0x0c) << 6) | sc->packet[2]; + y = ((sc->packet[1] & 0x03) << 8) | sc->packet[3]; + z = SYNAPTICS_PRESSURE; + } else { + x = elantech->old_x; + y = elantech->old_y; + z = 0; + } + + if (sc->packet[0] & 0x01) + buttons |= WSMOUSE_BUTTON(1); + if (sc->packet[0] & 0x02) + buttons |= WSMOUSE_BUTTON(3); + if (elantech->flags & ELANTECH_F_HAS_ROCKER) { + if (sc->packet[0] & 0x40) /* up */ + buttons |= WSMOUSE_BUTTON(4); + if (sc->packet[0] & 0x80) /* down */ + buttons |= WSMOUSE_BUTTON(5); + } + + elantech_send_input(sc, buttons, x, y, z, w); +} + +void +pms_proc_elantech_v2(struct pms_softc *sc) +{ + const u_char debounce_pkt[] = { 0x84, 0xff, 0xff, 0x02, 0xff, 0xff }; + struct elantech_softc *elantech = sc->elantech; + u_int buttons; + int x, y, w, z; + + /* + * The hardware sends this packet when in debounce state. + * The packet should be ignored. + */ + if (!memcmp(sc->packet, debounce_pkt, sizeof(debounce_pkt))) + return; + + w = (sc->packet[0] & 0xc0) >> 6; + if (w == 1 || w == 3) { + x = ((sc->packet[1] & 0x0f) << 8) | sc->packet[2]; + y = ((sc->packet[4] & 0x0f) << 8) | sc->packet[5]; + if (elantech->flags & ELANTECH_F_REPORTS_PRESSURE) + z = ((sc->packet[1] & 0xf0) | + (sc->packet[4] & 0xf0) >> 4); + else + z = SYNAPTICS_PRESSURE; + } else if (w == 2) { + x = (((sc->packet[0] & 0x10) << 4) | sc->packet[1]) << 2; + y = (((sc->packet[0] & 0x20) << 3) | sc->packet[2]) << 2; + z = SYNAPTICS_PRESSURE; + } else { + x = elantech->old_x; + y = elantech->old_y; + z = 0; + } + + buttons = ((sc->packet[0] & 0x01 ? WSMOUSE_BUTTON(1) : 0) | + ((sc->packet[0] & 0x02) ? WSMOUSE_BUTTON(3): 0)); + + elantech_send_input(sc, buttons, x, y, z, w); +} + +void +pms_proc_elantech_v3(struct pms_softc *sc) +{ + const u_char debounce_pkt[] = { 0xc4, 0xff, 0xff, 0x02, 0xff, 0xff }; + struct elantech_softc *elantech = sc->elantech; + u_int buttons; + int x, y, w, z; + + /* The hardware sends this packet when in debounce state. + * The packet should be ignored. */ + if (!memcmp(sc->packet, debounce_pkt, sizeof(debounce_pkt))) + return; + + buttons = ((sc->packet[0] & 0x01 ? WSMOUSE_BUTTON(1) : 0) | + ((sc->packet[0] & 0x02) ? WSMOUSE_BUTTON(3): 0)); + x = ((sc->packet[1] & 0x0f) << 8 | sc->packet[2]); + y = ((sc->packet[4] & 0x0f) << 8 | sc->packet[5]); + z = 0; + w = (sc->packet[0] & 0xc0) >> 6; + if (w == 2) { + /* + * Two-finger touch causes two packets -- a head packet + * and a tail packet. We report a single event and ignore + * the tail packet. + */ + if ((sc->packet[0] & 0x0c) != 0x04 && + (sc->packet[3] & 0xfc) != 0x02) { + /* not the head packet -- ignore */ + return; + } + } + + /* Prevent juming cursor if pad isn't touched or reports garbage. */ + if (w == 0 || + ((x == 0 || y == 0 || x == elantech->max_x || y == elantech->max_y) + && (x != elantech->old_x || y != elantech->old_y))) { + x = elantech->old_x; + y = elantech->old_y; + } + + if (elantech->flags & ELANTECH_F_REPORTS_PRESSURE) + z = (sc->packet[1] & 0xf0) | ((sc->packet[4] & 0xf0) >> 4); + else if (w) + z = SYNAPTICS_PRESSURE; + + elantech_send_input(sc, buttons, x, y, z, w); +} + +void +elantech_send_input(struct pms_softc *sc, u_int buttons, int x, int y, int z, + int w) + { + struct elantech_softc *elantech = sc->elantech; + int dx, dy; + + if (elantech->wsmode == WSMOUSE_NATIVE) { + wsmouse_input(sc->sc_wsmousedev, buttons, x, y, z, w, + WSMOUSE_INPUT_ABSOLUTE_X | + WSMOUSE_INPUT_ABSOLUTE_Y | + WSMOUSE_INPUT_ABSOLUTE_Z | + WSMOUSE_INPUT_ABSOLUTE_W | + WSMOUSE_INPUT_SYNC); + } else { + dx = dy = 0; + + if ((elantech->flags & ELANTECH_F_REPORTS_PRESSURE) && + z > SYNAPTICS_PRESSURE) { + dx = x - elantech->old_x; + dy = y - elantech->old_y; + dx /= SYNAPTICS_SCALE; + dy /= SYNAPTICS_SCALE; + } + if (dx || dy || buttons != elantech->old_buttons) + wsmouse_input(sc->sc_wsmousedev, buttons, dx, dy, 0, 0, + WSMOUSE_INPUT_DELTA); + elantech->old_buttons = buttons; + } + + elantech->old_x = x; + elantech->old_y = y; +} |