[PATCH] input: Add multi-touch protocol support to Synaptics

From: Takashi Iwai
Date: Mon Jun 14 2010 - 06:46:48 EST


This patch adds the experimental support of the multi-touch protocol
for recent Synaptics devices. Note that the protocol was analyzed just
by a pure guess work by watching device outputs, so it might be incorrect.
Also, the check of the multi-touch capability based on the 0x0c caps
might be also wrong.

In the multi-touch mode, the driver gives MT_POSITION_X, MT_POSITION_Y
and MT_PRESSURE abs events to follow the type A in
Documentation/input/multi-touch-protocol.txt.
The device supports up to two finger points, as far as I've tested.

As the driver now gives the MT_* events, this extension might result in
the incompatible event outputs. Thus make sure that the user-space side
(e.g. X11 synaptics driver) is also updated to support MT_* events.

Signed-off-by: Takashi Iwai <tiwai@xxxxxxx>

---
drivers/input/mouse/Kconfig | 11 ++++
drivers/input/mouse/synaptics.c | 113 +++++++++++++++++++++++++++++++++-----
drivers/input/mouse/synaptics.h | 1 +
3 files changed, 110 insertions(+), 15 deletions(-)

diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
index 91d3517..6dd0d29 100644
--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -76,6 +76,17 @@ config MOUSE_PS2_SYNAPTICS_LED
Say Y here if you have a Synaptics device with an embedded LED.
This will enable LED class driver to control the LED device.

+config MOUSE_PS2_SYNAPTICS_MULTI_TOUCH
+ bool "Support multi-touch protocol of Synaptics devices"
+ depends on MOUSE_PS2_SYNAPTICS
+ help
+ Say Y here for enabling the multi-touch protocol of recent
+ Syanptics devices. This may result in incompatible input
+ events, thus make sure that you update X11 synaptics driver
+ beforehand with the multi-protocol touch.
+
+ If unsure, say N.
+
config MOUSE_PS2_LIFEBOOK
bool "Fujitsu Lifebook PS/2 mouse protocol extension" if EMBEDDED
default y
diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
index 00799bc..79d9463 100644
--- a/drivers/input/mouse/synaptics.c
+++ b/drivers/input/mouse/synaptics.c
@@ -181,6 +181,12 @@ static int synaptics_capability(struct psmouse *psmouse)
}
}

+#ifdef CONFIG_MOUSE_PS2_SYNAPTICS_MULTI_TOUCH
+ /* FIXME: is this the right guess? */
+ if (priv->ext_cap_0c & (0x08 << 16))
+ priv->can_multi_touch = 1;
+#endif
+
return 0;
}

@@ -458,6 +464,32 @@ static void synaptics_free_led(struct psmouse *psmouse)
#define synaptics_sync_led(ps) do {} while (0)
#endif

+/* change to the multi-touch mode;
+ * this is done by sending SYN_QUE_MODEL cmd but setting a parameter
+ * by SETRATE instead of querying via GETINFO.
+ * 0xc8 seems to be the multi-touch mode.
+ */
+static int synaptics_init_multi_touch(struct psmouse *psmouse)
+{
+ unsigned char param[1];
+
+ if (psmouse_sliced_command(psmouse, SYN_QUE_MODEL))
+ return -1;
+ param[0] = 0xc8;
+ if (ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_SETRATE))
+ return -1;
+ return 0;
+}
+
+#ifdef CONFIG_MOUSE_PS2_SYNAPTICS_MULTI_TOUCH
+#define is_multi_touch(priv) (priv)->can_multi_touch
+#else
+#define is_multi_touch(priv) 0
+#endif
+/* the multi-touch packet contains w=2 (like pen) */
+#define is_multi_touch_packet(priv, hw) \
+ (is_multi_touch(priv) && (hw)->w == 2)
+
/*****************************************************************************
* Functions to interpret the absolute mode packets
****************************************************************************/
@@ -467,17 +499,27 @@ static void synaptics_parse_hw_state(unsigned char buf[], struct synaptics_data
memset(hw, 0, sizeof(struct synaptics_hw_state));

if (SYN_MODEL_NEWABS(priv->model_id)) {
- hw->x = (((buf[3] & 0x10) << 8) |
- ((buf[1] & 0x0f) << 8) |
- buf[4]);
- hw->y = (((buf[3] & 0x20) << 7) |
- ((buf[1] & 0xf0) << 4) |
- buf[5]);
-
- hw->z = buf[2];
hw->w = (((buf[0] & 0x30) >> 2) |
((buf[0] & 0x04) >> 1) |
((buf[3] & 0x04) >> 2));
+ if (is_multi_touch_packet(priv, hw)) {
+ /* a multi-touch packet is encoded differently;
+ * it appears to have half-resolutions. I might
+ * have missed the lowest bits, but it's hard
+ * to recognize.
+ */
+ hw->x = ((buf[4] & 0x0f) << 8 | buf[1]) << 1;
+ hw->y = ((buf[4] & 0xf0) << 4 | buf[2]) << 1;
+ hw->z = ((buf[3] & 0x30) | (buf[5] & 0x0f)) << 1;
+ } else {
+ hw->x = (((buf[3] & 0x10) << 8) |
+ ((buf[1] & 0x0f) << 8) |
+ buf[4]);
+ hw->y = (((buf[3] & 0x20) << 7) |
+ ((buf[1] & 0xf0) << 4) |
+ buf[5]);
+ hw->z = buf[2];
+ }

hw->left = (buf[0] & 0x01) ? 1 : 0;
hw->right = (buf[0] & 0x02) ? 1 : 0;
@@ -492,7 +534,7 @@ static void synaptics_parse_hw_state(unsigned char buf[], struct synaptics_data

} else if (SYN_CAP_MIDDLE_BUTTON(priv->capabilities)) {
hw->middle = ((buf[0] ^ buf[3]) & 0x01) ? 1 : 0;
- if (hw->w == 2)
+ if (!is_multi_touch(priv) && hw->w == 2)
hw->scroll = (signed char)(buf[1]);
}

@@ -550,6 +592,18 @@ static void synaptics_process_packet(struct psmouse *psmouse)

synaptics_parse_hw_state(psmouse->packet, priv, &hw);

+ if (is_multi_touch_packet(priv, &hw)) {
+ /* multi-touching */
+ if (hw.z > 0) {
+ input_report_abs(dev, ABS_MT_POSITION_X, hw.x);
+ input_report_abs(dev, ABS_MT_POSITION_Y,
+ YMAX_NOMINAL + YMIN_NOMINAL - hw.y);
+ }
+ input_report_abs(dev, ABS_MT_PRESSURE, hw.z);
+ input_mt_sync(dev);
+ return;
+ }
+
if (hw.scroll) {
priv->scroll += hw.scroll;

@@ -576,7 +630,8 @@ static void synaptics_process_packet(struct psmouse *psmouse)
if (SYN_CAP_EXTENDED(priv->capabilities)) {
switch (hw.w) {
case 0 ... 1:
- if (SYN_CAP_MULTIFINGER(priv->capabilities))
+ if (SYN_CAP_MULTIFINGER(priv->capabilities) ||
+ is_multi_touch(priv))
num_fingers = hw.w + 2;
break;
case 2:
@@ -602,10 +657,15 @@ static void synaptics_process_packet(struct psmouse *psmouse)
if (hw.z < 25) input_report_key(dev, BTN_TOUCH, 0);

if (hw.z > 0) {
- input_report_abs(dev, ABS_X, hw.x);
- input_report_abs(dev, ABS_Y, YMAX_NOMINAL + YMIN_NOMINAL - hw.y);
+ int key;
+ key = is_multi_touch(priv) ? ABS_MT_POSITION_X : ABS_X;
+ input_report_abs(dev, key, hw.x);
+ key = is_multi_touch(priv) ? ABS_MT_POSITION_Y : ABS_Y;
+ input_report_abs(dev, key, YMAX_NOMINAL + YMIN_NOMINAL - hw.y);
}
- input_report_abs(dev, ABS_PRESSURE, hw.z);
+ input_report_abs(dev,
+ is_multi_touch(priv) ? ABS_MT_PRESSURE : ABS_PRESSURE,
+ hw.z);

if (SYN_CAP_PALMDETECT(priv->capabilities))
input_report_abs(dev, ABS_TOOL_WIDTH, finger_width);
@@ -614,7 +674,7 @@ static void synaptics_process_packet(struct psmouse *psmouse)
input_report_key(dev, BTN_LEFT, hw.left);
input_report_key(dev, BTN_RIGHT, hw.right);

- if (SYN_CAP_MULTIFINGER(priv->capabilities)) {
+ if (SYN_CAP_MULTIFINGER(priv->capabilities) || is_multi_touch(priv)) {
input_report_key(dev, BTN_TOOL_DOUBLETAP, num_fingers == 2);
input_report_key(dev, BTN_TOOL_TRIPLETAP, num_fingers == 3);
}
@@ -630,6 +690,8 @@ static void synaptics_process_packet(struct psmouse *psmouse)
for (i = 0; i < SYN_CAP_MULTI_BUTTON_NO(priv->ext_cap); i++)
input_report_key(dev, BTN_0 + i, hw.ext_buttons & (1 << i));

+ if (is_multi_touch(priv))
+ input_mt_sync(dev);
input_sync(dev);
}

@@ -719,7 +781,7 @@ static void set_input_params(struct input_dev *dev, struct synaptics_data *priv)
__set_bit(BTN_LEFT, dev->keybit);
__set_bit(BTN_RIGHT, dev->keybit);

- if (SYN_CAP_MULTIFINGER(priv->capabilities)) {
+ if (SYN_CAP_MULTIFINGER(priv->capabilities) || is_multi_touch(priv)) {
__set_bit(BTN_TOOL_DOUBLETAP, dev->keybit);
__set_bit(BTN_TOOL_TRIPLETAP, dev->keybit);
}
@@ -748,6 +810,16 @@ static void set_input_params(struct input_dev *dev, struct synaptics_data *priv)
__clear_bit(BTN_RIGHT, dev->keybit);
__clear_bit(BTN_MIDDLE, dev->keybit);
}
+
+ if (is_multi_touch(priv)) {
+ input_set_abs_params(dev, ABS_MT_POSITION_X,
+ XMIN_NOMINAL, XMAX_NOMINAL, 0, 0);
+ input_set_abs_params(dev, ABS_MT_POSITION_Y,
+ YMIN_NOMINAL, YMAX_NOMINAL, 0, 0);
+ input_set_abs_params(dev, ABS_MT_PRESSURE, 0, 255, 0, 0);
+ input_abs_set_res(dev, ABS_MT_POSITION_X, priv->x_res);
+ input_abs_set_res(dev, ABS_MT_POSITION_Y, priv->y_res);
+ }
}

static void synaptics_disconnect(struct psmouse *psmouse)
@@ -785,6 +857,8 @@ static int synaptics_reconnect(struct psmouse *psmouse)
}

synaptics_sync_led(psmouse);
+ if (is_multi_touch(priv))
+ synaptics_init_multi_touch(psmouse);

return 0;
}
@@ -863,6 +937,15 @@ int synaptics_init(struct psmouse *psmouse)
if (synaptics_init_led(psmouse) < 0)
goto init_fail;

+ if (priv->can_multi_touch) {
+ if (synaptics_init_multi_touch(psmouse)) {
+ printk(KERN_WARNING "Synaptics: "
+ "unable to initialize multi-touch\n");
+ priv->can_multi_touch = 0;
+ } else
+ printk(KERN_INFO "Synaptics: multi-touch enabled\n");
+ }
+
set_input_params(psmouse->dev, priv);

/*
diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h
index e1a9033..b586087 100644
--- a/drivers/input/mouse/synaptics.h
+++ b/drivers/input/mouse/synaptics.h
@@ -111,6 +111,7 @@ struct synaptics_data {

unsigned char pkt_type; /* packet type - old, new, etc */
unsigned char mode; /* current mode byte */
+ unsigned char can_multi_touch; /* multi-touch support */
int scroll;
struct synaptics_led *led;
};
--
1.7.2.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/