diff mbox

[11/12] Input: synaptics - process finger (<=3) transitions

Message ID 1309324042-22943-12-git-send-email-djkurtz@chromium.org (mailing list archive)
State New, archived
Headers show

Commit Message

Daniel Kurtz June 29, 2011, 5:07 a.m. UTC
From: Daniel Kurtz <djkurtz@chromium.org>

Synaptics T5R2 touchpads track 5 fingers, but only report 2.

This patch attempts to deal with some idiosyncrasies of the T5R2
protocol:

 * When there are 3 fingers, one finger is 'hidden', meaning its position is
   never sent to the host.
 * The number of fingers can change at any time, but is only reported in
   SGM packets, thus at a number-of-fingers change, it is not possible
   to tell whether the AGM finger is for the original or new number of
   fingers.
 * When the number of fingers changes from 2->3 it is not
   possible to tell which of the 2 fingers are now reported.
 * When number of fingers changes from 3->2 it is often not possible to
   tell which finger was removed, and which are now being reported.

Signed-off-by: Daniel Kurtz <djkurtz@chromium.org>
---
 drivers/input/mouse/synaptics.c |  197 +++++++++++++++++++++++++++++++++++++-
 drivers/input/mouse/synaptics.h |    2 +
 2 files changed, 193 insertions(+), 6 deletions(-)
diff mbox

Patch

diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
index 19a9b7f..8b38e08 100644
--- a/drivers/input/mouse/synaptics.c
+++ b/drivers/input/mouse/synaptics.c
@@ -448,6 +448,8 @@  static int synaptics_parse_hw_state(const unsigned char buf[],
 				&& hw->w == 2) {
 			int type; /* Packet type */
 
+			priv->agm_pending = true;
+
 			type = (buf[5] & 0x30) >> 4;
 
 			switch (type) {
@@ -573,13 +575,20 @@  static void synaptics_report_mt_slot(struct input_dev *dev, int slot, int state,
 		input_report_abs(dev, ABS_MT_POSITION_Y, agm->y);
 		input_report_abs(dev, ABS_MT_PRESSURE, agm->z);
 		break;
+	case SYN_SLOT_HIDDEN:
+		input_mt_report_slot_state(dev, MT_TOOL_FINGER, true);
+		break;
 	}
 }
 
+/*
+ * Invariant: hidden_count = (count - 2);  hidden_count < (agm - sgm)
+ */
 static void synaptics_update_slots(struct synaptics_data *priv, int count,
 				   int sgm, int agm)
 {
 	int i;
+	int hidden_count;
 
 	/* First, clear previous slots. */
 	for (i = 0; i < SYN_TRACK_SLOT_COUNT; i++)
@@ -592,12 +601,45 @@  static void synaptics_update_slots(struct synaptics_data *priv, int count,
 	if (count < 2)
 		return;
 	priv->slot[agm] = SYN_SLOT_AGM;
+
+	/* Assign hidden slots between sgm and agm */
+	hidden_count = count - 2;
+	if (hidden_count < 1)
+		return;
+	if (hidden_count >= (agm-sgm))
+		hidden_count = agm-sgm-1;
+	for (i = 0; i < hidden_count; i++)
+		priv->slot[sgm + 1 + i] = SYN_SLOT_HIDDEN;
+}
+
+static int synaptics_find_sgm(struct synaptics_data *priv)
+{
+	int i;
+
+	for (i = 0; i < SYN_TRACK_SLOT_COUNT; i++)
+		if (priv->slot[i] == SYN_SLOT_SGM)
+			return i;
+	return -ENOENT;
+}
+
+static int synaptics_find_agm(struct synaptics_data *priv)
+{
+	int i;
+
+	for (i = 1; i < SYN_TRACK_SLOT_COUNT; i++)
+		if (priv->slot[i] == SYN_SLOT_AGM)
+			return i;
+	return -ENOENT;
 }
 
 static void synaptics_process_hw_state(struct synaptics_data *priv,
 				       struct synaptics_hw_state *sgm)
 {
+	struct synaptics_hw_state *agm = &priv->agm;
+	int slot_sgm;
+	int slot_agm;
 	int new_num_fingers;
+	int old_num_fingers = priv->num_fingers;
 
 	if (sgm->z == 0)
 		new_num_fingers = 0;
@@ -608,20 +650,163 @@  static void synaptics_process_hw_state(struct synaptics_data *priv,
 	else if (sgm->w == 1)
 		new_num_fingers = 3;
 
-	switch (new_num_fingers) {
-	case 0:
+	if (new_num_fingers == 0) {
 		synaptics_update_slots(priv, 0, 0, 0);
-		break;
-	case 1:
+		goto process_mt_data_done;
+	}
+
+	/*
+	 * If the last agm was (0,0,0), then SGM is in slot[0], and all other
+	 * fingers have been removed.
+	 */
+	if (priv->agm_pending && agm->z == 0) {
 		synaptics_update_slots(priv, 1, 0, 0);
+		goto process_mt_data_done;
+	}
+
+	/*
+	 * Update slots to in response to number of fingers changing from
+	 * old_num_fingers to new_num_fingers.
+	 */
+	switch (new_num_fingers) {
+	case 1:
+		switch (old_num_fingers) {
+		case 0:
+			synaptics_update_slots(priv, 1, 0, 0);
+			break;
+		case 1:
+			/*
+			 * If received AGM and previous SGM slot was 0, or
+			 * there was no SGM slot, then switch SGM slot to 1.
+			 *
+			 * The "SGM slot = 0" case happens with very rapid
+			 * "drum roll" gestures, where slot 0 finger is lifted
+			 * and a new slot 1 finger touches within one reporting
+			 * interval.
+			 * The "no SGM slot" case happens if initially two
+			 * or more fingers tap briefly, and all but one lift
+			 * before the end of the first reporting interval.
+			 * (In these cases, slot 0 becomes empty, only
+			 * slot 1, is reported with the SGM)
+			 *
+			 * Else if there was no previous SGM, use slot 0.
+			 *
+			 * Else, reuse the current SGM slot.
+			 */
+
+			/* Determine previous SGM slot, if there was one. */
+			slot_sgm = synaptics_find_sgm(priv);
+
+			if (priv->agm_pending && slot_sgm <= 0)
+				slot_sgm = 1;
+			else if (slot_sgm < 0)
+				slot_sgm = 0;
+
+			synaptics_update_slots(priv, 1, slot_sgm, 0);
+			break;
+		case 2:
+			/*
+			 * Since last AGM was not (0,0,0),
+			 * previous AGM is now the SGM.
+			 */
+			slot_agm = synaptics_find_agm(priv);
+			synaptics_update_slots(priv, 1, slot_agm, 0);
+			break;
+		case 3:
+			/*
+			 * Since last AGM was not (0,0,0), we don't know what
+			 * finger is left. So empty all slots.
+			 * The slot gets filled on a subsequent 1->1
+			 */
+			synaptics_update_slots(priv, 0, 0, 0);
+			break;
+		}
 		break;
+
 	case 2:
-	case 3: /* Fall-through case */
-		synaptics_update_slots(priv, 2, 0, 1);
+		switch (old_num_fingers) {
+		case 0:
+			synaptics_update_slots(priv, 2, 0, 1);
+			break;
+		case 1:
+			/*
+			 * If previous slot 0 had SGM,
+			 * the new finger, slot 1, is in AGM.
+			 * Otherwise, new finger, slot 0, is in SGM.
+			 */
+			slot_sgm = synaptics_find_sgm(priv);
+			if (slot_sgm < 1)
+				slot_sgm = 1;
+			synaptics_update_slots(priv, 2, 0, slot_sgm);
+			break;
+		case 2:
+			/* Assuming no change in fingers... */
+			slot_sgm = synaptics_find_sgm(priv);
+			slot_agm = synaptics_find_agm(priv);
+			if (slot_sgm < 0)
+				slot_sgm = 0;
+			if (slot_agm < slot_sgm + 1)
+				slot_agm = slot_sgm + 1;
+			synaptics_update_slots(priv, 2, slot_sgm, slot_agm);
+			break;
+		case 3:
+			/*
+			 * 3->2 transitions have two unsolvable problems:
+			 *  1) no indication is given which finger was removed
+			 *  2) no way to tell if agm packet was for finger 3
+			 *     before 3->2, or finger 2 after 3->2.
+			 *
+			 * So, empty all slots.
+			 * The slots get filled on a subsequent 2->2
+			 */
+			synaptics_update_slots(priv, 0, 0, 0);
+			break;
+		}
+		break;
+
+	case 3:
+		switch (old_num_fingers) {
+		case 0:
+			synaptics_update_slots(priv, 3, 0, 2);
+			break;
+		case 1:
+			/*
+			 * If old SGM was slot 2 or higher, that slot is now
+			 * AGM, with one of the new fingers in slot 0 as SGM.
+			 * Otherwise, the 3 used slots are 0,1,2.
+			 */
+			slot_sgm = synaptics_find_sgm(priv);
+			if (slot_sgm < 2)
+				slot_sgm = 2;
+			synaptics_update_slots(priv, 3, 0, slot_sgm);
+			break;
+		case 2:
+			/* On 2->3 transitions, we are given no indication
+			 * which finger was added.
+			 * We don't even know what finger the current AGM packet
+			 * contained.
+			 * So, empty all slots.
+			 * The slots get filled on a subsequent 3->3
+			 */
+			synaptics_update_slots(priv, 0, 0, 0);
+			break;
+		case 3:
+			/* Assuming no change in fingers... */
+			slot_sgm = synaptics_find_sgm(priv);
+			slot_agm = synaptics_find_agm(priv);
+			if (slot_sgm < 0)
+				slot_sgm = 0;
+			if (slot_agm < slot_sgm + 2)
+				slot_agm = slot_sgm + 2;
+			synaptics_update_slots(priv, 3, slot_sgm, slot_agm);
+			break;
+		}
 		break;
 	}
 
+process_mt_data_done:
 	priv->num_fingers = new_num_fingers;
+	priv->agm_pending = false;
 }
 
 static void synaptics_report_mt_data(struct psmouse *psmouse,
diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h
index 2214af6..cc193b6 100644
--- a/drivers/input/mouse/synaptics.h
+++ b/drivers/input/mouse/synaptics.h
@@ -120,6 +120,7 @@ 
 #define SYN_SLOT_EMPTY			0
 #define SYN_SLOT_SGM			1
 #define SYN_SLOT_AGM			2
+#define SYN_SLOT_HIDDEN			3
 
 /* number of tracking slots for Image Sensor firmware */
 #define SYN_TRACK_SLOT_COUNT		5
@@ -166,6 +167,7 @@  struct synaptics_data {
 	struct synaptics_hw_state agm;		/* last AGM packet */
 	int num_fingers;			/* current finger count */
 	int slot[SYN_TRACK_SLOT_COUNT];		/* finger slot state */
+	bool agm_pending;			/* new AGM packet received */
 };
 
 void synaptics_module_init(void);