This diff adds support for Elantech touchpads to pms(4), so that
synpatics(4) will attach and allow configuration of edge-scrolling,
2-finger scrolling, toggle tap-to-click on/off, etc.
Currently, such pads only work in compat mode, which means they are
recognized as regular PS/2 mice and cannot be properly configured.
This patch adds support for hardware version 1, 2, and 3.
Linux also supports version 4 but this driver does not yet.
I have hardware version 3 which seems to work well, but code for
the other versions is entirely untested. If you have a machine with
a touchpad please try this diff. If it's an Elantech 1, 2, or 3,
it should be detected as such automatically. (If you're looking
for an easy way to configure the pad I'd suggest installing Xfce
and going to the Mouse/Touchpad settings window.)
If I don't get test reports for versions 1 and 2, my plan is to
commit the support code for versions 1 and 2 but only attach to
version 3. Bugs in touchpad support code can be rather irritating
so I'd prefer to keep versions 1 and 2 in PS/2 compat mode until
somebody can confirm that the code actually works. Thanks!
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 4 Oct 2012 12:56:24 -0000
@@ -57,6 +57,8 @@ struct pms_protocol {
#define PMS_INTELLI 1
#define PMS_SYNAPTICS 2
#define PMS_ALPS 3
+#define PMS_ELANTECH_V1 4
+#define PMS_ELANTECH 5
u_int packetsize;
int (*enable)(struct pms_softc *);
int (*ioctl)(struct pms_softc *, u_long, caddr_t, int, struct proc *);
@@ -108,6 +110,27 @@ struct alps_softc {
#define ALPS_PRESSURE 40
};
+struct elantech_softc {
+ int hw_version;
+ int flags;
+#define ELANTECH_F_REPORTS_PRESSURE 0x01
+#define ELANTECH_F_HAS_ROCKER 0x02
+#define ELANTECH_F_PARITY_REVERSED 0x03
+#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 +152,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 +251,17 @@ 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(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(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 *);
+void pms_proc_elantech(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,15 @@ void synaptics_sec_proc(struct pms_softc
int alps_sec_proc(struct pms_softc *);
int alps_get_hwinfo(struct pms_softc *);
+void elantech_send_input(struct pms_softc *, u_int, int, int, int, int);
+int elantech_id_query(struct pms_softc *, int, u_char *);
+int elantech_get_hwinfo(struct pms_softc *);
+int elantech_ps2_cmd(struct pms_softc *, u_char);
+int elantech_read_reg(struct pms_softc *, u_char, u_char *);
+int elantech_write_reg(struct pms_softc *, u_char, u_char);
+int elantech_set_absolute_mode(struct pms_softc *);
+
+
struct cfattach pms_ca = {
sizeof(struct pms_softc), pmsprobe, pmsattach, NULL,
pmsactivate
@@ -293,6 +337,24 @@ const struct pms_protocol pms_protocols[
pms_proc_alps,
NULL
},
+ /* Elantech touchpad (hardware version 1) */
+ {
+ PMS_ELANTECH_V1, 4,
+ pms_enable_elantech,
+ pms_ioctl_elantech,
+ pms_sync_elantech_v1,
+ pms_proc_elantech_v1,
+ NULL
+ },
+ /* Elantech touchpad */
+ {
+ PMS_ELANTECH, 6,
+ pms_enable_elantech,
+ pms_ioctl_elantech,
+ pms_sync_elantech,
+ pms_proc_elantech,
+ NULL
+ },
};
int
@@ -1358,5 +1420,643 @@ pms_proc_alps(struct pms_softc *sc)
alps->old_x = x;
alps->old_y = y;
alps->old_buttons = buttons;
+ }
+}
+
+int
+elantech_set_absolute_mode(struct pms_softc *sc)
+{
+ struct elantech_softc *elantech = sc->elantech;
+ int i;
+ u_char val;
+
+ /* Enable absolute mode. Magic numbers from Linux driver. */
+ switch (elantech->hw_version) {
+ case 1:
+ if (elantech_write_reg(sc, 0x10, 0x16) ||
+ elantech_write_reg(sc, 0x11, 0x8f))
+ return (-1);
+ break;
+ case 2:
+ if (elantech_write_reg(sc, 0x10, 0x54) ||
+ elantech_write_reg(sc, 0x11, 0x88) ||
+ elantech_write_reg(sc, 0x21, 0x88))
+ return (-1);
+ break;
+ case 3:
+ if (elantech_write_reg(sc, 0x10, 0x0b))
+ return (-1);
+ break;
+ default:
+ return (-1);
+ }
+
+ /* Read back reg 0x10 to ensure hardware is ready. */
+ for (i = 0; i < 5; i++) {
+ if (elantech_read_reg(sc, 0x10, &val) == 0)
+ break;
+ delay(2000);
+ }
+ if (i == 5)
+ return (-1);
+
+ if (elantech->hw_version == 1 &&
+ (val & ELANTECH_ABSOLUTE_MODE) == 0)
+ return (-1);
+
+ return (0);
+}
+
+int
+elantech_id_query(struct pms_softc *sc, int fw_version, u_char *capabilities)
+{
+ struct elantech_softc *elantech = sc->elantech;
+ int i, fixed_dpi;
+ u_char resp[3];
+
+ switch (elantech->hw_version) {
+ case 2:
+ 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;
+ }
+ break;
+ case 3:
+ 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];
+ break;
+ default:
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+elantech_get_hwinfo(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) {
+ elantech->hw_version = 1;
+ if (fw_version < 0x20000)
+ elantech->flags |= ELANTECH_F_HW_V1_OLD;
+ } else {
+ switch ((fw_version & 0x0f0000) >> 16) {
+ case 2:
+ case 4:
+ elantech->hw_version = 2;
+ break;
+ case 5:
+ elantech->hw_version = 3;
+ break;
+ default:
+ return (-1);
+ }
+ }
+
+ if (fw_version < 0x20000)
+ elantech->flags |= ELANTECH_F_PARITY_REVERSED;
+
+ 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->flags & ELANTECH_F_HW_V1_OLD) &&
+ capabilities[0] & ELANTECH_CAP_HAS_ROCKER)
+ elantech->flags |= ELANTECH_F_HAS_ROCKER;
+
+ if (elantech_set_absolute_mode(sc))
+ return (-1);
+
+ switch (elantech->hw_version) {
+ case 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;
+ break;
+ case 2:
+ 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 (elantech_id_query(sc, fw_version, capabilities))
+ return (-1);
+ break;
+ case 3:
+ if (elantech_id_query(sc, fw_version, capabilities))
+ return (-1);
+ break;
+ default:
+ return (-1);
+ }
+
+ 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_read_reg(struct pms_softc *sc, u_char reg, u_char *data)
+{
+ struct elantech_softc *elantech = sc->elantech;
+ u_char resp[3];
+
+ switch (elantech->hw_version) {
+ case 1:
+ if (pms_spec_cmd(sc, ELANTECH_CMD_READ_REG) ||
+ pms_spec_cmd(sc, reg) ||
+ pms_get_status(sc, resp))
+ return (-1);
+ break;
+ case 2:
+ 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, reg) ||
+ pms_get_status(sc, resp))
+ return (-1);
+ break;
+ case 3:
+ 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, reg) ||
+ pms_get_status(sc, resp))
+ return (-1);
+ break;
+ default:
+ return (-1);
+
+ }
+
+ *data = resp[0];
+
+ return (0);
+}
+
+int
+elantech_write_reg(struct pms_softc *sc, u_char reg, u_char data)
+{
+ struct elantech_softc *elantech = sc->elantech;
+
+ switch (elantech->hw_version) {
+ case 1:
+ if (pms_spec_cmd(sc, ELANTECH_CMD_WRITE_REG) ||
+ pms_spec_cmd(sc, reg) ||
+ pms_spec_cmd(sc, data) ||
+ pms_set_scaling(sc, 1))
+ return (-1);
+ break;
+ case 2:
+ 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, reg) ||
+ elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
+ elantech_ps2_cmd(sc, data) ||
+ pms_set_scaling(sc, 1))
+ return (-1);
+ break;
+ case 3:
+ 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, reg) ||
+ elantech_ps2_cmd(sc, ELANTECH_PS2_CUSTOM_COMMAND) ||
+ elantech_ps2_cmd(sc, data) ||
+ pms_set_scaling(sc, 1))
+ return (-1);
+ break;
+ default:
+ return (-1);
+
+ }
+
+ return (0);
+}
+
+int
+pms_enable_elantech(struct pms_softc *sc)
+{
+ struct elantech_softc *elantech = sc->elantech;
+ u_char resp[3];
+ int i;
+
+ 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 (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(sc)) {
+ free(sc->elantech, M_DEVBUF);
+ sc->elantech = NULL;
+ goto err;
+ }
+
+ /* Hardware version 1 uses packet size 4, others use 6. */
+ if (elantech->hw_version > 1 && sc->protocol->packetsize == 4) {
+ free(sc->elantech, M_DEVBUF);
+ sc->elantech = NULL;
+ goto err;
+ }
+
+ for (i = 0; i < nitems(elantech->parity); i++)
+ elantech->parity[i] = elantech->parity[i & (i - 1)] ^ 1;
+
+ printf("%s: Elantech Touchpad, version %d\n",
+ DEVNAME(sc), elantech->hw_version);
+ } else if (elantech_set_absolute_mode(sc))
+ return (-1);
+
+ 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_PARITY_REVERSED) {
+ 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(struct pms_softc *sc, int data)
+{
+ struct elantech_softc *elantech = sc->elantech;
+
+ switch (elantech->hw_version) {
+ case 2:
+ return pms_sync_elantech_v2(sc, data);
+ case 3:
+ 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;
+ }
+ 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;
+}
+
+void
+pms_proc_elantech(struct pms_softc *sc)
+{
+ struct elantech_softc *elantech = sc->elantech;
+
+ switch (elantech->hw_version) {
+ case 2:
+ pms_proc_elantech_v2(sc);
+ break;
+ case 3:
+ pms_proc_elantech_v3(sc);
+ break;
}
}
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 */