On Sun, Oct 28, 2012 at 03:47:14PM +0100, Stefan Sperling wrote:
> 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?
> 

ok shadchin@, no regress with synaptics touchapd

Add this patch (for wsconsctl)

Index: util.c
===================================================================
RCS file: /cvs/src/sbin/wsconsctl/util.c,v
retrieving revision 1.58
diff -u -p -r1.58 util.c
--- util.c      14 Jul 2012 08:25:12 -0000      1.58
+++ util.c      28 Oct 2012 16:01:54 -0000
@@ -91,7 +91,8 @@ static const struct nameint mstype_tab[]
        { WSMOUSE_TYPE_SUN,     "sun" },
        { WSMOUSE_TYPE_SYNAPTICS, "synaptics" },
        { WSMOUSE_TYPE_ALPS,    "alps" },
-       { WSMOUSE_TYPE_SGI,     "sgi" }
+       { WSMOUSE_TYPE_SGI,     "sgi" },
+       { WSMOUSE_TYPE_ELANTECH, "elantech" }
 };
 
 static const struct nameint dpytype_tab[] = {

Also few comments below.

> 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;  
                    ^^^^^^^^
                  trailing tab

> +     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)
                      ^^^
                   should be 0

> +             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 */
                              ^^^
                         should be 0

> +                     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); 
                                                      ^^^^^^
                                                     trailing space

> +             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 */
                ^^^^^
                spaces

> +                     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;
>      }
>  }

-- 
Alexandr Shadchin

Reply via email to