From: Daniel Kurtz <djkurtz@xxxxxxxxxxxx> Synaptics image sensor touchpads track 5 fingers, but only report 2. This patch attempts to deal with some idiosyncrasies of these touchpads: * When there are 3 or more fingers, only two are reported. * 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. When 2 or more packets are present on the touchpad, the kernel reports exactly two MT-B slots containing the position data for the two fingers reported by the touchpad. In addition, it reports the total number of fingers using one of the EV_KEY/BTN_TOOL_*TAP events. Thus, this is a hybrid singletouch/MT-B scheme. Userspace can detect this condition by noting that the driver supports more EV_KEY/BTN_TOOL_*TAP events than its maximum EV_ABS/ABS_MT_SLOT. Signed-off-by: Daniel Kurtz <djkurtz@xxxxxxxxxxxx> --- drivers/input/mouse/synaptics.c | 229 ++++++++++++++++++++++++++++++++++++--- drivers/input/mouse/synaptics.h | 3 + 2 files changed, 216 insertions(+), 16 deletions(-) diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c index ff8c839..b626b98 100644 --- a/drivers/input/mouse/synaptics.c +++ b/drivers/input/mouse/synaptics.c @@ -453,6 +453,9 @@ static void synaptics_parse_agm(const unsigned char buf[], default: break; } + + /* Record that at least one AGM has been received since last SGM */ + priv->agm_pending = true; } static int synaptics_parse_hw_state(const unsigned char buf[], @@ -600,26 +603,44 @@ static void synaptics_report_slot_agm(struct input_dev *dev, int slot, } static void synaptics_report_mt(struct psmouse *psmouse, - int count, + struct synaptics_mt_state *mt_state, const struct synaptics_hw_state *sgm) { struct input_dev *dev = psmouse->dev; struct synaptics_data *priv = psmouse->private; struct synaptics_hw_state *agm = &priv->agm; + struct synaptics_mt_state *old = &priv->mt_state; - switch (count) { + switch (mt_state->count) { case 0: synaptics_report_slot_empty(dev, 0); synaptics_report_slot_empty(dev, 1); break; case 1: - synaptics_report_slot_sgm(dev, 0, sgm); - synaptics_report_slot_empty(dev, 1); + if (mt_state->sgm == 0) { + synaptics_report_slot_sgm(dev, 0, sgm); + synaptics_report_slot_empty(dev, 1); + } else { + synaptics_report_slot_empty(dev, 0); + synaptics_report_slot_sgm(dev, 1, sgm); + } break; - case 2: - case 3: /* Fall-through case */ - synaptics_report_slot_sgm(dev, 0, sgm); - synaptics_report_slot_agm(dev, 1, agm); + default: + /* + * For all other finger counts, report sgm in 0 and agm in 1, + * but only if the sgm/agm is reporting the same finger. + * If either reported finger has changed, invalidate its slot's + * old tracking id, instead. + */ + if ((old->sgm == -1) || (old->sgm == mt_state->sgm)) + synaptics_report_slot_sgm(dev, 0, sgm); + else + synaptics_report_slot_empty(dev, 0); + + if ((old->agm == -1) || (old->agm == mt_state->agm)) + synaptics_report_slot_agm(dev, 1, agm); + else + synaptics_report_slot_empty(dev, 1); break; } @@ -627,28 +648,204 @@ static void synaptics_report_mt(struct psmouse *psmouse, input_mt_report_pointer_emulation(dev, false); /* Send the number of fingers reported by touchpad itself. */ - input_mt_report_finger_count(dev, count); + input_mt_report_finger_count(dev, mt_state->count); input_report_key(dev, BTN_LEFT, sgm->left); input_sync(dev); } +/* Handle case where mt_state->count = 0 */ +static void synaptics_image_sensor_0f(struct synaptics_data *priv, + struct synaptics_mt_state *mt_state) +{ + synaptics_mt_state_set(mt_state, 0, -1, -1); +} + +/* Handle case where mt_state->count = 1 */ +static void synaptics_image_sensor_1f(struct synaptics_data *priv, + struct synaptics_mt_state *mt_state) +{ + struct synaptics_hw_state *agm = &priv->agm; + struct synaptics_mt_state *old = &priv->mt_state; + + /* + * If the last AGM was (0,0,0), and there is only one finger left, + * then SGM contains slot 0, and all other fingers have been removed. + */ + if (priv->agm_pending && agm->z == 0) { + synaptics_mt_state_set(mt_state, 1, 0, -1); + return; + } + + switch (old->count) { + case 0: + synaptics_mt_state_set(mt_state, 1, 0, -1); + break; + case 1: + /* + * If pending AGM and either: + * (a) the previous SGM slot contains slot 0, or + * (b) there was no SGM slot + * then SGM now contains slot 1 + * + * (a) The "SGM contains 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. + * + * (b) 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 both these cases, slot 0 will become empty, and SGM + * contains a new finger in slot 1) + * + * Else, if there was no previous SGM, it now contains slot 0. + * + * Otherwise, SGM still contains the same slot. + */ + + if (priv->agm_pending && old->sgm <= 0) + synaptics_mt_state_set(mt_state, 1, 1, -1); + else if (old->sgm == -1) + synaptics_mt_state_set(mt_state, 1, 0, -1); + break; + case 2: + /* + * Since the last AGM was NOT (0,0,0), it was the finger in + * slot 0 that has been removed. + * So, SGM now contains previous AGM's slot, and AGM is now + * empty. + */ + synaptics_mt_state_set(mt_state, 1, old->agm, -1); + break; + case 3: + /* + * Since last AGM was not (0,0,0), we don't know which finger + * is left. + * + * So, empty all slots. We will guess slot 0 on subsequent 1->1 + */ + synaptics_mt_state_set(mt_state, 0, -1, -1); + break; + } +} + +/* Handle case where mt_state->count = 2 */ +static void synaptics_image_sensor_2f(struct synaptics_data *priv, + struct synaptics_mt_state *mt_state) +{ + struct synaptics_mt_state *old = &priv->mt_state; + + switch (old->count) { + case 0: + synaptics_mt_state_set(mt_state, 2, 0, 1); + break; + case 1: + /* + * If previous SGM contained slot 1 or higher, SGM now contains + * slot 0 (the newly touching finger) and AGM contains SGM's + * previous slot. + * + * Otherwise, SGM still contains slot 0 and AGM now contains + * slot 1. + */ + if (old->sgm >= 1) + synaptics_mt_state_set(mt_state, 2, 0, old->sgm); + else + synaptics_mt_state_set(mt_state, 2, 0, 1); + break; + case 2: + /* + * mt_state either hasn't changed, or was updated by a recently + * received AGM-CONTACT packet. + */ + 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. We will guess slots [0,1] on + * subsequent 2->2 + */ + synaptics_mt_state_set(mt_state, 0, -1, -1); + break; + } +} + +/* Handle case where mt_state->count = 3 */ +static void synaptics_image_sensor_3f(struct synaptics_data *priv, + struct synaptics_mt_state *mt_state) +{ + struct synaptics_mt_state *old = &priv->mt_state; + + switch (old->count) { + case 0: + synaptics_mt_state_set(mt_state, 3, 0, 2); + break; + case 1: + /* + * If previous SGM contained slot 2 or higher, SGM now contains + * slot 0 (one of the newly touching fingers) and AGM contains + * SGM's previous slot. + * + * Otherwise, SGM now contains slot 0 and AGM contains slot 2. + */ + if (old->sgm >= 2) + synaptics_mt_state_set(mt_state, 3, 0, old->sgm); + else + synaptics_mt_state_set(mt_state, 3, 0, 2); + 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. They get filled on a subsequent 3->3 + */ + synaptics_mt_state_set(mt_state, 0, -1, -1); + break; + case 3: + /* + * mt_state either hasn't changed, or was updated by a recently + * received AGM-CONTACT packet. + */ + break; + } +} + static void synaptics_image_sensor_process(struct psmouse *psmouse, struct synaptics_hw_state *sgm) { - int count; + struct synaptics_data *priv = psmouse->private; + struct synaptics_hw_state *agm = &priv->agm; + struct synaptics_mt_state mt_state; + + /* Initialize using current mt_state (as updated by last agm) */ + mt_state = agm->mt_state; + /* + * Update mt_state using the new finger count and current mt_state. + */ if (sgm->z == 0) - count = 0; + synaptics_image_sensor_0f(priv, &mt_state); else if (sgm->w >= 4) - count = 1; + synaptics_image_sensor_1f(priv, &mt_state); else if (sgm->w == 0) - count = 2; - else - count = 3; + synaptics_image_sensor_2f(priv, &mt_state); + else if (sgm->w == 1) + synaptics_image_sensor_3f(priv, &mt_state); /* Send resulting input events to user space */ - synaptics_report_mt(psmouse, count, sgm); + synaptics_report_mt(psmouse, &mt_state, sgm); + + /* Store updated mt_state */ + priv->mt_state = agm->mt_state = mt_state; + priv->agm_pending = false; } /* diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h index 0c63357..87be1fe 100644 --- a/drivers/input/mouse/synaptics.h +++ b/drivers/input/mouse/synaptics.h @@ -161,11 +161,14 @@ struct synaptics_data { struct serio *pt_port; /* Pass-through serio port */ + struct synaptics_mt_state mt_state; /* Current mt finger state */ + /* * Last received Advanced Gesture Mode (AGM) packet. An AGM packet * contains position data for a second contact, at half resolution. */ struct synaptics_hw_state agm; + bool agm_pending; /* new AGM packet received */ }; void synaptics_module_init(void); -- 1.7.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html