On Fri, Oct 05, 2012 at 01:52:08PM +0200, Martin Pieuchot wrote:
> My first impression at looking the code is that the v2 and v3 use
> totally different packet formats and I don't see a real benefit of
> having only one entry in the protocol table if you need to check
> for the hardware version in every function.
>
> I would suggest to use 3 different entries in the protocol table and a
> enable_elantech_common() function so you can get rid of the hw_version
> check and the protocol->packetsize hack for the v1.
>
> I got the same feeling when you set the absolute mode: every version
> use different magic ps2 commands with different magic values, I'm not
> sure adding generic read/write functions help to understand nor save
> some lines of code.
> 
> Other than that, it looks to me that the flags ELANTECH_F_HW_V1_OLD and
> ELANTECH_F_PARITY_REVERSED are redundant.

All good points. And the parity flag was even wrongly defined...

New version, hopefully better. Also fixes softc->elantech memory leak
in error paths (which synaptics and alps have, too, btw.)

Works for me with version 3, other versions still untested AFAIK.

Index: pms.c
===================================================================
RCS file: /cvs/src/sys/dev/pckbc/pms.c,v
retrieving revision 1.31
diff -u -p -r1.31 pms.c
--- pms.c       22 Jul 2012 18:28:36 -0000      1.31
+++ pms.c       5 Oct 2012 18:21:53 -0000
@@ -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 inf
        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
 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,33 @@ const struct pms_protocol pms_protocols[
                pms_proc_alps,
                NULL
        },
+       /* 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
+       },
+       /* 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
@@ -1359,4 +1432,692 @@ pms_proc_alps(struct pms_softc *sc)
                alps->old_y = y;
                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_SYNAPTICS;
+               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 fingers, x, y, w, z;
+
+       if (elantech->flags & ELANTECH_F_HW_V1_OLD)
+               fingers = ((sc->packet[1] & 0x80) >> 7) +
+                   ((sc->packet[1] & 0x30) >> 4);
+       else
+               fingers = (sc->packet[0] & 0xc0) >> 6;
+
+       /* Hardware version 1 doesn't report pressure. */
+       if (fingers) {
+               x = ((sc->packet[1] & 0x0c) << 6) | sc->packet[2];
+               y = ((sc->packet[1] & 0x03) << 8) | sc->packet[3];
+               z = SYNAPTICS_PRESSURE;
+               /* 4 means 1 finger, 0 means 2 fingers, see synaptic */
+               w = (fingers == 1 ? 4 : 0);
+       } else {
+               x = elantech->old_x;
+               y = elantech->old_y;
+               z = 0;
+               w = -1;
+       }
+
+       if (sc->packet[0] & 0x01)
+               buttons |= WSMOUSE_BUTTON(1);
+       if (sc->packet[1] & 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[1] & 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 fingers, 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;
+
+       fingers = (sc->packet[0] & 0xc0) >> 6;
+       if (fingers == 1 || fingers == 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;
+               /* 4 means one finger, 1 means 3 fingers in synaptics */
+               w = (fingers == 1 ? 4 : 1);
+       } else if (fingers == 2) {
+               x = (((sc->packet[0] & 0x10) << 4) | sc->packet[1]) << 2;
+               y = (((sc->packet[0] & 0x20) << 3) | sc->packet[2]) << 2;
+               z = SYNAPTICS_PRESSURE;
+               w = 0; /* means 2 fingers in synaptics */
+       } else {
+               x = elantech->old_x;
+               y = elantech->old_y;
+               z = 0;
+               w = -1;
+       }
+
+       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 fingers, 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 = y = z = 0;
+       w = -1; /* corresponds to no finger, see synaptics */
+       fingers = (sc->packet[0] & 0xc0) >> 6;
+       if (fingers == 2) {
+               /* Two-finger touch causes two packets -- a head packet
+                * and a tail packet. */
+               if ((sc->packet[0] & 0x0c) == 0x04 &&
+                   (sc->packet[3] & 0xfc) == 0x02) {
+                       /* head packet */
+                       x = ((sc->packet[1] & 0x0f) << 8 | sc->packet[2]);
+                       y = ((sc->packet[4] & 0x0f) << 8 | sc->packet[5]);
+               } else if ((sc->packet[0] & 0x0c) == 0x0c &&
+                   (sc->packet[3] & 0xce) == 0x0c) {
+                       /* tail packet */
+                       x = ((sc->packet[1] & 0x0f) << 8 | sc->packet[2]);
+                       y = ((sc->packet[4] & 0x0f) << 8 | sc->packet[5]);
+               }
+               w = 0; /* force 2 fingers in synaptics */
+       } else if (fingers == 1 || fingers == 3) {
+               x = (sc->packet[1] & 0x0f) << 8 | sc->packet[2];
+               y = ((sc->packet[4] & 0x0f) << 8) | sc->packet[5];
+               w = (fingers == 3 ? 1 : 4); /* values for synaptics */
+       }
+
+       /* Prevent juming cursor if pad isn't touched or reports garbage. */
+       if (fingers == 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 (fingers)
+               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) {
+               if ((x > 0 && y > 0) || buttons)
+                       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;
 }
Index: pmsreg.h
===================================================================
RCS file: /cvs/src/sys/dev/pckbc/pmsreg.h,v
retrieving revision 1.8
diff -u -p -r1.8 pmsreg.h
--- pmsreg.h    28 Apr 2012 09:43:24 -0000      1.8
+++ pmsreg.h    4 Oct 2012 01:10:19 -0000
@@ -43,6 +43,11 @@
 #define PMS_ALPS_MAGIC3_2      80
 #define PMS_ALPS_MAGIC3_3      100
 
+#define PMS_ELANTECH_MAGIC1    0x3c
+#define PMS_ELANTECH_MAGIC2    0x03
+#define PMS_ELANTECH_MAGIC3_1  0xc8
+#define PMS_ELANTECH_MAGIC3_2  0x00
+
 /*
  * Checking for almost-standard PS/2 packet
  * Note: ALPS devices never signal overflow condition
@@ -151,5 +156,37 @@
 #define ALPS_YSEC_BEZEL                                512
 
 #define ALPS_Z_MAGIC                           127
+
+/* Elantech queries */
+#define        ELANTECH_QUE_FW_ID                      0
+#define        ELANTECH_QUE_FW_VER                     1
+#define        ELANTECH_QUE_CAPABILITIES               2
+#define        ELANTECH_QUE_SAMPLE                     3
+#define        ELANTECH_QUE_RESOLUTION                 4
+
+/* Elantech capabilities */
+#define        ELANTECH_CAP_HAS_ROCKER                 4
+
+#define        ELANTECH_PS2_CUSTOM_COMMAND             0xf8
+
+#define        ELANTECH_CMD_READ_REG                   0x10
+#define        ELANTECH_CMD_WRITE_REG                  0x11
+#define        ELANTECH_CMD_READ_WRITE_REG             0x00
+
+#define        ELANTECH_ABSOLUTE_MODE                  0x04
+
+/* Hardware version 1 has hard-coded axis range values.
+ * X axis range is 0 to 576, Y axis range is 0 to 384.
+ * Edge offset accounts for bezel around the touchpad. */
+#define ELANTECH_V1_EDGE_OFFSET        32
+#define        ELANTECH_V1_X_MIN       (0 + ELANTECH_V1_EDGE_OFFSET)
+#define        ELANTECH_V1_X_MAX       (576 - ELANTECH_V1_EDGE_OFFSET)
+#define        ELANTECH_V1_Y_MIN       (0 + ELANTECH_V1_EDGE_OFFSET)
+#define        ELANTECH_V1_Y_MAX       (384 - ELANTECH_V1_EDGE_OFFSET)
+
+/* Older hardware version 2 variants lack ID query capability. */
+#define        ELANTECH_V2_X_MAX       1152
+#define        ELANTECH_V2_Y_MAX       768
+
 
 #endif /* SYS_DEV_PCKBC_PMSREG_H */

Reply via email to