On Mon, Oct 08, 2012 at 06:16:22PM +0200, Martin Pieuchot wrote:
> I don't have any Elantech hardware to test but I can tell you that this
> version doesn't introduce any regression with my ALPS touchpad. It could
> be nice to hear from other touchpad owners too ;)

I haven't received test reports from anyone else. I suspect many don't
realise that their laptop contains an elantech touchpad.

> > +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;
> 
> Here you may want to define a WSMOUSE_TYPE_ELANTECH, it is used in the
> synaptics(4) driver to describe which "features" are supported by this
> touchpad, see xenocara/driver/xf86-input-synaptics/src/wsconscomm.c
> 
> But if you do so, you'll obviously need to rebuild the X driver too ;)

Yes, I've done this now. The new kernel + X driver diff is below.
The synaptics driver already has some elantech-specific code which
gets activated with this change.

> > +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]);
> 
> Is it a typo or the 'x' and 'y' calculation are the same in both cases?

Yes. There are two packets, one per finger.

It turns out we can ignore the second packet. This makes two-finger scrolling
smoother than it was before. Reporting both events was causing occasional
jumps during two-finger scrolling.

> 
> > +           }
> > +           w = 0; /* force 2 fingers in synaptics */
> 
> This isn't related to your diff, but we should add defines for
> w = {-1, 0, 3, 4} because these values come from the synaptics devices.

Indeed. I've added a comment to wsconscomm.c 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 */
> > +   }
> 
> Same here for the 'x' and 'y', maybe you just need to convert the value
> you call 'fingers' to the corresponding 'w' (synaptics finger value).

By reporting the touchpad as an Elantech pad to synaptics we can get
rid of the 'fingers' variable and just assign the value to 'w' instead.

> > +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)
> 
> I think this condition is always true, or does the elantech touchpads
> generate packets without position nor buttons pressed? Plus in case of
> the v3 if x or y are null you explicitly force them to their previous
> know position.

Yes, this check is bogus. I've removed it.

> > +/* 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)
> 
> Some nitpicking, can you use just one space after "#define" to be
> coherent with the rest of the file ;)

Done.

> Apart from that, does your touchpad respond to the ELANTECH_QUE_FW_ID
> query before being in absolute mode? Because if it does you can remove
> the set_absolute_mode_vN() call from get_hwinfo_vN() and turn the latter
> into a version independent function.
> This would allow you to simplify the enable_elantech_vN() by sharing
> the get_hwinfo() code and merging the set_absolute_mode_vN() code in it.

Good idea but it doesn't work.
We need to switch the pad into absolute mode first. Else it reports
bogus width/height values which causes the mouse cursor to behave
erratically and breaks edge scrolling.

Thanks for the review! Is this good for commit now?

This diff doesn't attach to v1 and v2 hardware yet because these have
not been tested. If anyone wants to test v1 or v2 hardware just remove
the #ifdef notyet / #endif.

Kernel patch (X driver patch below):

Index: wscons/wsconsio.h
===================================================================
RCS file: /cvs/src/sys/dev/wscons/wsconsio.h,v
retrieving revision 1.66
diff -u -p -r1.66 wsconsio.h
--- wscons/wsconsio.h   22 Jul 2012 18:28:36 -0000      1.66
+++ wscons/wsconsio.h   28 Oct 2012 13:04:43 -0000
@@ -213,6 +213,7 @@ struct wskbd_map_data {
 #define                WSMOUSE_TYPE_SYNAPTICS  15      /* Synaptics touchpad */
 #define                WSMOUSE_TYPE_ALPS       16      /* ALPS touchpad */
 #define                WSMOUSE_TYPE_SGI        17      /* SGI serial mouse */
+#define                WSMOUSE_TYPE_ELANTECH   18      /* Elantech touchpad */
 
 /* Set resolution.  Not applicable to all mouse types. */
 #define        WSMOUSEIO_SRES          _IOW('W', 33, u_int)
Index: pckbc/pms.c
===================================================================
RCS file: /cvs/src/sys/dev/pckbc/pms.c,v
retrieving revision 1.31
diff -u -p -r1.31 pms.c
--- pckbc/pms.c 22 Jul 2012 18:28:36 -0000      1.31
+++ pckbc/pms.c 28 Oct 2012 14:31:19 -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,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
@@ -1359,4 +1434,675 @@ 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_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[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 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;
 }
Index: pckbc/pmsreg.h
===================================================================
RCS file: /cvs/src/sys/dev/pckbc/pmsreg.h,v
retrieving revision 1.8
diff -u -p -r1.8 pmsreg.h
--- pckbc/pmsreg.h      28 Apr 2012 09:43:24 -0000      1.8
+++ pckbc/pmsreg.h      28 Oct 2012 12:24:11 -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 */


X driver patch:

Index: xf86-input-synaptics/src/wsconscomm.c
===================================================================
RCS file: /cvs/xenocara/driver/xf86-input-synaptics/src/wsconscomm.c,v
retrieving revision 1.6
diff -u -p -r1.6 wsconscomm.c
--- xf86-input-synaptics/src/wsconscomm.c       22 Jul 2012 18:33:01 -0000      
1.6
+++ xf86-input-synaptics/src/wsconscomm.c       28 Oct 2012 14:07:37 -0000
@@ -61,7 +61,8 @@ WSConsIsTouchpad(InputInfoPtr pInfo, con
     }
 
     if (wsmouse_type == WSMOUSE_TYPE_SYNAPTICS ||
-        wsmouse_type == WSMOUSE_TYPE_ALPS)
+        wsmouse_type == WSMOUSE_TYPE_ALPS ||
+        wsmouse_type == WSMOUSE_TYPE_ELANTECH)
         rc = TRUE;
 
 out:
@@ -194,6 +195,13 @@ WSConsReadHwState(InputInfoPtr pInfo,
             hw->z = event.value;
             break;
         case WSCONS_EVENT_MOUSE_ABSOLUTE_W:
+           if (priv->model == MODEL_ELANTECH) {
+               /* Elantech touchpads report number of fingers directly. */
+               hw->fingerWidth = 5;
+               hw->numFingers = event.value;
+               break;
+           }
+           /* XXX magic number mapping which is mirrored in pms driver */
             switch (event.value) {
             case 0:
                 hw->fingerWidth = 5;
@@ -291,6 +299,12 @@ WSConsReadDevDimensions(InputInfoPtr pIn
         priv->has_width = FALSE;
         priv->has_double = FALSE;
         priv->has_triple = FALSE;
+        break;
+    case WSMOUSE_TYPE_ELANTECH:
+        priv->model = MODEL_ELANTECH;
+        priv->has_width = TRUE;
+        priv->has_double = TRUE;
+        priv->has_triple = TRUE;
         break;
     }
 }

Reply via email to