diff mbox

Alps dualpoint touchpads losing sync [buttons fixed too]

Message ID 1258314158.9416.16.camel@sardelle.necksus.de (mailing list archive)
State Superseded
Headers show

Commit Message

Sebastian Kapfer Nov. 15, 2009, 7:42 p.m. UTC
None
diff mbox

Patch

diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c
index f361106..aaab238 100644
--- a/drivers/input/mouse/alps.c
+++ b/drivers/input/mouse/alps.c
@@ -1,14 +1,20 @@ 
-/*
+/* vim:noet:sw=8:ts=8
  * ALPS touchpad PS/2 mouse driver
  *
  * Copyright (c) 2003 Neil Brown <neilb@cse.unsw.edu.au>
  * Copyright (c) 2003-2005 Peter Osterlund <petero2@telia.com>
  * Copyright (c) 2004 Dmitry Torokhov <dtor@mail.ru>
  * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz>
+ * Copyright (c) 2009 Sebastian Kapfer <sebastian_kapfer@gmx.net>
  *
  * ALPS detection, tap switching and status querying info is taken from
  * tpconfig utility (by C. Scott Ananian and Bruce Kall).
  *
+ * Inspiration for parts of the multitouch codepath from a patch by
+ * Matthew Chapman.  (See http://lkml.org/lkml/2008/12/8/182 )
+ * Additional research by David Kubicek and Erik Osterholm
+ * (https://bugs.launchpad.net/ubuntu/+source/linux/+bug/296610 )
+ *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 as published by
  * the Free Software Foundation.
@@ -35,6 +41,15 @@ 
 #define ALPS_OLDPROTO	0x10
 #define ALPS_PASS	0x20
 #define ALPS_FW_BK_2	0x40
+/* capable of sending 9-byte packets.  these packets are recognized by having
+ * LMR buttons set.  if there were a dualpoint device with three mouse buttons,
+ * we could misrecognize, so an additional flag.  if your dualpoint device
+ * often loses sync, try adding ALPS_DUALPOINT9.
+ * these devices also have the property that they don't keep button state
+ * separate for the touchpad and stick device. */
+#define ALPS_DUALPOINT9	0x80
+
+#define DELL_STYLE_DUALPOINT (ALPS_DUALPOINT|ALPS_DUALPOINT9|ALPS_PASS)
 
 static const struct alps_model_info alps_model_data[] = {
 	{ { 0x32, 0x02, 0x14 },	0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* Toshiba Salellite Pro M10 */
@@ -55,7 +70,8 @@  static const struct alps_model_info alps_model_data[] = {
 	{ { 0x20, 0x02, 0x0e },	0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* XXX */
 	{ { 0x22, 0x02, 0x0a },	0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT },
 	{ { 0x22, 0x02, 0x14 }, 0xff, 0xff, ALPS_PASS | ALPS_DUALPOINT }, /* Dell Latitude D600 */
-	{ { 0x62, 0x02, 0x14 }, 0xcf, 0xcf, ALPS_PASS | ALPS_DUALPOINT }, /* Dell Latitude E6500 */
+	/* Dell Latitude E6400, E6500, Precision M4400 */
+	{ { 0x62, 0x02, 0x14 }, 0xcf, 0xcf, DELL_STYLE_DUALPOINT },
 	{ { 0x73, 0x02, 0x50 }, 0xcf, 0xcf, ALPS_FW_BK_1 },		  /* Dell Vostro 1400 */
 };
 
@@ -65,8 +81,13 @@  static const struct alps_model_info alps_model_data[] = {
  * isn't valid per PS/2 spec.
  */
 
-/*
- * ALPS abolute Mode - new format
+/* PS/2 packet format
+ *
+ * byte 0: YOFL XOFL YSGN XSGN  1    M    R    L
+ * byte 1: X7   X6   X5   X4   X3   X2   X1   X0
+ * byte 2: Y7   Y6   Y5   Y4   Y3   Y2   Y1   Y0
+ *
+ * ALPS absolute Mode - new format
  *
  * byte 0:  1    ?    ?    ?    1    ?    ?    ?
  * byte 1:  0   x6   x5   x4   x3   x2   x1   x0
@@ -75,6 +96,20 @@  static const struct alps_model_info alps_model_data[] = {
  * byte 4:  0   y6   y5   y4   y3   y2   y1   y0
  * byte 5:  0   z6   z5   z4   z3   z2   z1   z0
  *
+ * Dualpoint device -- 9-byte packet format
+ *
+ * byte 0:    1    1    0    0    1    1    1    1
+ * byte 1:    0   x6   x5   x4   x3   x2   x1   x0
+ * byte 2:    0  x10   x9   x8   x7    0  fin  ges
+ * byte 3: YOFL XOFL YSGN XSGN    1    1    1    1
+ * byte 4:   X7   X6   X5   X4   X3   X2   X1   X0
+ * byte 5:   Y7   Y6   Y5   Y4   Y3   Y2   Y1   Y0
+ * byte 6:    0   y9   y8   y7    1    m    r    l
+ * byte 7:    0   y6   y5   y4   y3   y2   y1   y0
+ * byte 8:    0   z6   z5   z4   z3   z2   z1   z0
+ *
+ * CAPITALS = stick, miniscules = touchpad
+ *
  * ?'s can have different meanings on different models,
  * such as wheel rotation, extra buttons, stick buttons
  * on a dualpoint, etc.
@@ -98,9 +133,30 @@  static void alps_process_packet(struct psmouse *psmouse)
 		input_report_rel(dev2, REL_Y,
 			packet[2] ? ((packet[0] << 3) & 0x100) - packet[2] : 0);
 		input_sync(dev2);
+		if ((priv->i->flags & ALPS_DUALPOINT9) == 0)
+			return;
+		/* copy state to other input layer device, since
+		 * the hardware does not keep them separate. */
+		input_report_key(dev,  BTN_LEFT,   packet[0] & 1);
+		input_report_key(dev,  BTN_RIGHT,  packet[0] & 2);
+		input_report_key(dev,  BTN_MIDDLE, packet[0] & 4);
+		input_sync(dev);
 		return;
 	}
 
+	/* handle trackpoint part of a 9-byte packet,
+	   pass the rest on. */
+	if (priv->i->flags & ALPS_DUALPOINT9 && (packet[3] & 0xf) == 0xf) {
+		input_report_rel(dev2, REL_X,
+			packet[4] ? packet[4] - ((packet[3] << 4) & 0x100) : 0);
+		input_report_rel(dev2, REL_Y,
+			packet[5] ? ((packet[3] << 3) & 0x100) - packet[5] : 0);
+		/* touchpad data and buttons are handled below */
+		packet[3] = packet[6];
+		packet[4] = packet[7];
+		packet[5] = packet[8];
+	}
+
 	if (priv->i->flags & ALPS_OLDPROTO) {
 		left = packet[2] & 0x10;
 		right = packet[2] & 0x08;
@@ -148,6 +204,14 @@  static void alps_process_packet(struct psmouse *psmouse)
 	input_report_key(dev, BTN_LEFT, left);
 	input_report_key(dev, BTN_RIGHT, right);
 	input_report_key(dev, BTN_MIDDLE, middle);
+	/* copy state to other input layer device, since
+	 * the hardware does not keep them separate. */
+	if (priv->i->flags & ALPS_DUALPOINT9) {
+		input_report_key(dev2, BTN_LEFT,   left);
+		input_report_key(dev2, BTN_RIGHT,  right);
+		input_report_key(dev2, BTN_MIDDLE, middle);
+		input_sync(dev2);
+	}
 
 	/* Convert hardware tap to a reasonable Z value */
 	if (ges && !fin) z = 40;
@@ -191,6 +255,7 @@  static void alps_process_packet(struct psmouse *psmouse)
 static psmouse_ret_t alps_process_byte(struct psmouse *psmouse)
 {
 	struct alps_data *priv = psmouse->private;
+	int length_full_packet = 6;
 
 	if ((psmouse->packet[0] & 0xc8) == 0x08) { /* PS/2 packet */
 		if (psmouse->pktcnt == 3) {
@@ -200,15 +265,39 @@  static psmouse_ret_t alps_process_byte(struct psmouse *psmouse)
 		return PSMOUSE_GOOD_DATA;
 	}
 
-	if ((psmouse->packet[0] & priv->i->mask0) != priv->i->byte0)
+	/* must have been a non-PS/2 packet to even get here. */
+
+	if ((psmouse->packet[0] & priv->i->mask0) != priv->i->byte0) {
+		dbg("don't like packet[0] = %x (mask0 = %x, byte0 = %x\n",
+		    (int)psmouse->packet[0],
+		    (int)priv->i->mask0,
+		    (int)priv->i->byte0);
 		return PSMOUSE_BAD_DATA;
+	}
 
-	/* Bytes 2 - 6 should have 0 in the highest bit */
-	if (psmouse->pktcnt >= 2 && psmouse->pktcnt <= 6 &&
-	    (psmouse->packet[psmouse->pktcnt - 1] & 0x80))
+	/* 9-byte packet format by dualpoint units */
+	if (priv->i->flags & ALPS_DUALPOINT9) {
+		/* is marked by a packet with LMR set */
+		if ((psmouse->pktcnt >= 4)
+		    && ((psmouse->packet[3] & 0xf) == 0xf)) {
+			length_full_packet = 9;
+			/* wave stick bytes through */
+			if (psmouse->pktcnt <= 6)
+				return PSMOUSE_GOOD_DATA;
+		}
+	}
+
+	/* Bytes 2 - 6 should have 0 in the highest bit for 6-byte packet */
+	/* Bytes 2, 3, and 7 through 9 should have for 9-byte packet      */
+	if (psmouse->pktcnt >= 2 &&
+	    (psmouse->packet[psmouse->pktcnt - 1] & 0x80)) {
+		dbg("don't like packet[%i] = %x\n",
+		    psmouse->pktcnt - 1,
+		    (int)psmouse->packet[psmouse->pktcnt - 1]);
 		return PSMOUSE_BAD_DATA;
+	}
 
-	if (psmouse->pktcnt == 6) {
+	if (psmouse->pktcnt == length_full_packet) {
 		alps_process_packet(psmouse);
 		return PSMOUSE_FULL_PACKET;
 	}
diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h
index e053bdd..d4772fe 100644
--- a/drivers/input/mouse/psmouse.h
+++ b/drivers/input/mouse/psmouse.h
@@ -42,7 +42,7 @@  struct psmouse {
 	struct delayed_work resync_work;
 	char *vendor;
 	char *name;
-	unsigned char packet[8];
+	unsigned char packet[9];
 	unsigned char badbyte;
 	unsigned char pktcnt;
 	unsigned char pktsize;