[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#633595: elantech mouse v3 support



On Mon, Aug 29, 2011 at 04:22:36PM +0100, Ben Hutchings wrote:
> On Mon, 2011-08-29 at 16:06 +0200, Julien Valroff wrote:
> > Hi Ben,
> > 
> > Le lundi 29 août 2011 à 14:07:44 (+0200 CEST), Ben Hutchings a écrit :
> > > On Mon, 2011-08-29 at 00:35 +0200, Eloi COUTANT wrote:
> > > > Hi.
> > > > 
> > > > I confirm that Julien's patch is efficient on Samsung RC510.
> > > > I have no problems anymore : one, two and three fingers are working,
> > > > "wheels" too !
> > > [...]
> > > 
> > > Julien, please can you provide a list of *upstream* changes.  We will
> > > not apply a large patch with no such references.
> > 
> > I have not written that patch at all, just made available the "original"
> > DKMS tree so that people can test it easily.
> > 
> > The patch was written by Seth Forshee <seth.forshee@canonical.com> and is
> > available on LaunchPad bug #681904 [0].
> > 
> > All the patches, Ubuntu builts and dkms .deb are available at:
> > http://people.canonical.com/~sforshee/lp681904/
> 
> OK, so I see this patch:
> http://people.canonical.com/~sforshee/lp681904/linux-3.0.0-8.10~lp681904v201108052055/0001-Input-elantech-Add-v3-hardware-support.patch
> which looks like something that could go upstream.  But it hasn't, or at
> least it's not visible in linux-next.

That patch will not be going upstream. Elantech has submitted more
comprehensive updates to the driver that were developed in parallel (see
https://lkml.org/lkml/2011/8/29/63 for the most recent revision).

We are carrying an updated version of the above patch temporarily until
Elantech's solution makes it upstream. In practice that probably means
we'll use my patch for Ubuntu 11.10 and switch to Elantech's solution in
12.04. The advantage to the patch we're carrying now versus Elantech's
is that it doesn't functionally modify the v1 or v2 touchpad support,
and thus shouldn't carry any risk of regression for those devices.  I've
attached a copy of the updated patch.

Cheers,
Seth
>From bae4a3ddeb12cbaa31740d9d36dfaa281a02534c Mon Sep 17 00:00:00 2001
From: Seth Forshee <seth.forshee@canonical.com>
Date: Fri, 5 Aug 2011 15:54:54 -0500
Subject: [PATCH] UBUNTU: SAUCE: (no-up) Input: elantech - Add v3 hardware support

BugLink: https://bugs.launchpad.net/bugs/681904

Adds basic v3 hardware support for newer devices not currently
supported by the driver.

Thanks to JJ Ding <jj_ding@emc.com.tw>, Tom Lin <tom_lin@emc.com.tw>,
and Mark A. Stratman <stratman@gmail.com>, whose work on other
patcesh revealed the workings of the v3 protocol.

Signed-off-by: Seth Forshee <seth.forshee@canonical.com>
---
 drivers/input/mouse/elantech.c |  181 +++++++++++++++++++++++++++++++++-------
 drivers/input/mouse/elantech.h |    5 +
 2 files changed, 157 insertions(+), 29 deletions(-)

diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c
index 3250356..642cde0 100644
--- a/drivers/input/mouse/elantech.c
+++ b/drivers/input/mouse/elantech.c
@@ -82,6 +82,7 @@ static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg,
 {
 	struct elantech_data *etd = psmouse->private;
 	unsigned char param[3];
+	unsigned char command;
 	int rc = 0;
 
 	if (reg < 0x10 || reg > 0x26)
@@ -90,9 +91,11 @@ static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg,
 	if (reg > 0x11 && reg < 0x20)
 		return -1;
 
+	command = (etd->hw_version == 3) ? ETP_REGISTER_RW : ETP_REGISTER_READ;
+
 	switch (etd->hw_version) {
 	case 1:
-		if (psmouse_sliced_command(psmouse, ETP_REGISTER_READ) ||
+		if (psmouse_sliced_command(psmouse, command) ||
 		    psmouse_sliced_command(psmouse, reg) ||
 		    ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETINFO)) {
 			rc = -1;
@@ -100,8 +103,9 @@ static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg,
 		break;
 
 	case 2:
+	case 3:
 		if (elantech_ps2_command(psmouse,  NULL, ETP_PS2_CUSTOM_COMMAND) ||
-		    elantech_ps2_command(psmouse,  NULL, ETP_REGISTER_READ) ||
+		    elantech_ps2_command(psmouse,  NULL, command) ||
 		    elantech_ps2_command(psmouse,  NULL, ETP_PS2_CUSTOM_COMMAND) ||
 		    elantech_ps2_command(psmouse,  NULL, reg) ||
 		    elantech_ps2_command(psmouse, param, PSMOUSE_CMD_GETINFO)) {
@@ -125,6 +129,7 @@ static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg,
 				unsigned char val)
 {
 	struct elantech_data *etd = psmouse->private;
+	unsigned char command;
 	int rc = 0;
 
 	if (reg < 0x10 || reg > 0x26)
@@ -133,9 +138,11 @@ static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg,
 	if (reg > 0x11 && reg < 0x20)
 		return -1;
 
+	command = (etd->hw_version == 3) ? ETP_REGISTER_RW : ETP_REGISTER_WRITE;
+
 	switch (etd->hw_version) {
 	case 1:
-		if (psmouse_sliced_command(psmouse, ETP_REGISTER_WRITE) ||
+		if (psmouse_sliced_command(psmouse, command) ||
 		    psmouse_sliced_command(psmouse, reg) ||
 		    psmouse_sliced_command(psmouse, val) ||
 		    ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_SETSCALE11)) {
@@ -144,8 +151,9 @@ static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg,
 		break;
 
 	case 2:
+	case 3:
 		if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
-		    elantech_ps2_command(psmouse, NULL, ETP_REGISTER_WRITE) ||
+		    elantech_ps2_command(psmouse, NULL, command) ||
 		    elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
 		    elantech_ps2_command(psmouse, NULL, reg) ||
 		    elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
@@ -223,7 +231,7 @@ static void elantech_report_absolute_v1(struct psmouse *psmouse)
 		input_report_abs(dev, ABS_X,
 			((packet[1] & 0x0c) << 6) | packet[2]);
 		input_report_abs(dev, ABS_Y,
-			ETP_YMAX_V1 - (((packet[1] & 0x03) << 8) | packet[3]));
+			etd->y_max - (((packet[1] & 0x03) << 8) | packet[3]));
 	}
 
 	input_report_key(dev, BTN_TOOL_FINGER, fingers == 1);
@@ -298,7 +306,7 @@ static void elantech_report_absolute_v2(struct psmouse *psmouse)
 		 * byte 4:  .   .   .   .   .   .  y9  y8
 		 * byte 5: y7  y6  y5  y4  y3  y2  y1  y0
 		 */
-		y1 = ETP_YMAX_V2 - (((packet[4] & 0x03) << 8) | packet[5]);
+		y1 = etd->y_max - (((packet[4] & 0x03) << 8) | packet[5]);
 
 		input_report_abs(dev, ABS_X, x1);
 		input_report_abs(dev, ABS_Y, y1);
@@ -316,14 +324,14 @@ static void elantech_report_absolute_v2(struct psmouse *psmouse)
 		 */
 		x1 = ((packet[0] & 0x10) << 4) | packet[1];
 		/* byte 2: ay7 ay6 ay5 ay4 ay3 ay2 ay1 ay0 */
-		y1 = ETP_2FT_YMAX - (((packet[0] & 0x20) << 3) | packet[2]);
+		y1 = etd->y_max_2ft - (((packet[0] & 0x20) << 3) | packet[2]);
 		/*
 		 * byte 3:  .   .  by8 bx8  .   .   .   .
 		 * byte 4: bx7 bx6 bx5 bx4 bx3 bx2 bx1 bx0
 		 */
 		x2 = ((packet[3] & 0x10) << 4) | packet[4];
 		/* byte 5: by7 by8 by5 by4 by3 by2 by1 by0 */
-		y2 = ETP_2FT_YMAX - (((packet[3] & 0x20) << 3) | packet[5]);
+		y2 = etd->y_max_2ft - (((packet[3] & 0x20) << 3) | packet[5]);
 		/*
 		 * For compatibility with the X Synaptics driver scale up
 		 * one coordinate and report as ordinary mouse movent
@@ -352,6 +360,83 @@ static void elantech_report_absolute_v2(struct psmouse *psmouse)
 	input_sync(dev);
 }
 
+static int elantech_v3_is_noise(unsigned int x, unsigned int y)
+{
+	return x == 0xfff && y == 0xfff;
+}
+
+/*
+ * Interpret complete data packets and report absolute mode input events for
+ * hardware version 3. (6 byte packets)
+ */
+static void elantech_report_absolute_v3(struct psmouse *psmouse)
+{
+	struct elantech_data *etd = psmouse->private;
+	struct input_dev *dev = psmouse->dev;
+	unsigned char *packet1 = psmouse->packet;
+	unsigned char *packet2 = NULL;
+	unsigned int fingers, width = 0, pres = 0;
+	unsigned int x1, y1, x2 = 0, y2 = 0;
+
+	/* byte 0: n1  n0   .   .   .   .   R   L */
+	fingers = (packet1[0] & 0xc0) >> 6;
+
+	/*
+	 * Need to receive two packets for 2-touch events. If this
+	 * is the first packet, store it away and wait for the second.
+	 * We don't support parity in v3, so use that storage.
+	 */
+	if (fingers == 2 && (packet1[0] & 0x0c) == 0x04 &&
+	    (packet1[3] & 0xcf) == 0x2) {
+		memcpy(etd->parity, packet1, psmouse->pktsize);
+		return;
+	}
+
+	/*
+	 * If this the second packet of a two-fingered touch event we
+	 * need to process the previous packet stashed away in
+	 * etd->parity
+	 */
+	if (fingers == 2) {
+		packet1 = etd->parity;
+		packet2 = psmouse->packet;
+	}
+
+	x1 = ((packet1[1] & 0x0f) << 8) | packet1[2];
+	y1 = ((packet1[4] & 0x0f) << 8) | packet1[5];
+	if (elantech_v3_is_noise(x1, y1))
+		return;
+	y1 = etd->y_max - y1;
+
+	if (packet2) {
+		x2 = ((packet2[1] & 0x0f) << 8) | packet2[2];
+		y2 = etd->y_max - (((packet2[4] & 0x0f) << 8) | packet2[5]);
+	}
+
+	pres = (packet1[1] & 0xf0) | ((packet1[4] & 0xf0) >> 4);
+	width = ((packet1[0] & 0x30) >> 2) | ((packet1[3] & 0x30) >> 4);
+
+	elantech_report_semi_mt_data(dev, fingers, x1, y1, x2, y2);
+
+	input_report_key(dev, BTN_TOUCH, fingers != 0);
+	input_report_key(dev, BTN_TOOL_FINGER, fingers == 1);
+	input_report_key(dev, BTN_TOOL_DOUBLETAP, fingers == 2);
+	input_report_key(dev, BTN_TOOL_TRIPLETAP, fingers == 3);
+	input_report_abs(dev, ABS_PRESSURE, pres);
+	input_report_abs(dev, ABS_TOOL_WIDTH, width);
+
+	if (fingers != 0) {
+		input_report_abs(dev, ABS_X, x1);
+		input_report_abs(dev, ABS_Y, y1);
+	}
+
+	input_report_key(dev, BTN_LEFT, packet1[0] & 0x01);
+	input_report_key(dev, BTN_RIGHT, packet1[0] & 0x02);
+
+	input_sync(dev);
+	return;
+}
+
 static int elantech_check_parity_v1(struct psmouse *psmouse)
 {
 	struct elantech_data *etd = psmouse->private;
@@ -401,6 +486,10 @@ static psmouse_ret_t elantech_process_byte(struct psmouse *psmouse)
 		/* We don't know how to check parity in protocol v2 */
 		elantech_report_absolute_v2(psmouse);
 		break;
+	case 3:
+		/* We don't know how to check parity in protocol v3 */
+		elantech_report_absolute_v3(psmouse);
+		break;
 	}
 
 	return PSMOUSE_FULL_PACKET;
@@ -435,8 +524,13 @@ static int elantech_set_absolute_mode(struct psmouse *psmouse)
 		    elantech_write_reg(psmouse, 0x11, etd->reg_11) ||
 		    elantech_write_reg(psmouse, 0x21, etd->reg_21)) {
 			rc = -1;
-			break;
 		}
+		break;
+	case 3:
+		etd->reg_10 = 0x0b;
+		if (elantech_write_reg(psmouse, 0x10, etd->reg_10))
+			rc = -1;
+		break;
 	}
 
 	if (rc == 0) {
@@ -473,10 +567,11 @@ static int elantech_set_absolute_mode(struct psmouse *psmouse)
 /*
  * Set the appropriate event bits for the input subsystem
  */
-static void elantech_set_input_params(struct psmouse *psmouse)
+static int elantech_set_input_params(struct psmouse *psmouse)
 {
 	struct input_dev *dev = psmouse->dev;
 	struct elantech_data *etd = psmouse->private;
+	unsigned char param[3];
 
 	__set_bit(EV_KEY, dev->evbit);
 	__set_bit(EV_ABS, dev->evbit);
@@ -492,20 +587,40 @@ static void elantech_set_input_params(struct psmouse *psmouse)
 
 	switch (etd->hw_version) {
 	case 1:
+		etd->x_max = ETP_XMAX_V1;
+		etd->y_max = ETP_YMAX_V1;
+
 		/* Rocker button */
 		if (etd->fw_version < 0x020000 &&
 		    (etd->capabilities & ETP_CAP_HAS_ROCKER)) {
 			__set_bit(BTN_FORWARD, dev->keybit);
 			__set_bit(BTN_BACK, dev->keybit);
 		}
-		input_set_abs_params(dev, ABS_X, ETP_XMIN_V1, ETP_XMAX_V1, 0, 0);
-		input_set_abs_params(dev, ABS_Y, ETP_YMIN_V1, ETP_YMAX_V1, 0, 0);
+		input_set_abs_params(dev, ABS_X, ETP_XMIN_V1, etd->x_max, 0, 0);
+		input_set_abs_params(dev, ABS_Y, ETP_YMIN_V1, etd->y_max, 0, 0);
 		break;
 
 	case 2:
 		__set_bit(BTN_TOOL_QUADTAP, dev->keybit);
-		input_set_abs_params(dev, ABS_X, ETP_XMIN_V2, ETP_XMAX_V2, 0, 0);
-		input_set_abs_params(dev, ABS_Y, ETP_YMIN_V2, ETP_YMAX_V2, 0, 0);
+		/* fall through */
+	case 3:
+		if (etd->hw_version == 3) {
+			if (synaptics_send_cmd(psmouse, ETP_FW_ID_QUERY, param))
+				return -1;
+
+			etd->x_max = (param[0] & 0x0f) << 8 | param[1];
+			etd->y_max = (param[0] & 0xf0) << 4 | param[2];
+			etd->y_max_2ft = etd->y_max;
+			elantech_debug("x_max = %d, y_max = %d\n",
+				       etd->x_max, etd->y_max);
+		} else {
+			etd->x_max = ETP_XMAX_V2;
+			etd->y_max = ETP_YMAX_V2;
+			etd->y_max_2ft = ETP_2FT_YMAX;
+		}
+
+		input_set_abs_params(dev, ABS_X, ETP_XMIN_V2, etd->x_max, 0, 0);
+		input_set_abs_params(dev, ABS_Y, ETP_YMIN_V2, etd->y_max, 0, 0);
 		if (etd->reports_pressure) {
 			input_set_abs_params(dev, ABS_PRESSURE, ETP_PMIN_V2,
 					     ETP_PMAX_V2, 0, 0);
@@ -514,10 +629,12 @@ static void elantech_set_input_params(struct psmouse *psmouse)
 		}
 		__set_bit(INPUT_PROP_SEMI_MT, dev->propbit);
 		input_mt_init_slots(dev, 2);
-		input_set_abs_params(dev, ABS_MT_POSITION_X, ETP_XMIN_V2, ETP_XMAX_V2, 0, 0);
-		input_set_abs_params(dev, ABS_MT_POSITION_Y, ETP_YMIN_V2, ETP_YMAX_V2, 0, 0);
+		input_set_abs_params(dev, ABS_MT_POSITION_X, ETP_XMIN_V2, etd->x_max, 0, 0);
+		input_set_abs_params(dev, ABS_MT_POSITION_Y, ETP_YMIN_V2, etd->y_max, 0, 0);
 		break;
 	}
+
+	return 0;
 }
 
 struct elantech_attr_data {
@@ -659,7 +776,8 @@ int elantech_detect(struct psmouse *psmouse, bool set_properties)
 	 * Report this in case there are Elantech models that use a different
 	 * set of magic numbers
 	 */
-	if (param[0] != 0x3c || param[1] != 0x03 || param[2] != 0xc8) {
+	if (param[0] != 0x3c || param[1] != 0x03 ||
+	    (param[2] != 0xc8 && param[2] != 0x00)) {
 		pr_debug("unexpected magic knock result 0x%02x, 0x%02x, 0x%02x.\n",
 			 param[0], param[1], param[2]);
 		return -1;
@@ -749,23 +867,24 @@ int elantech_init(struct psmouse *psmouse)
 
 	etd->fw_version = (param[0] << 16) | (param[1] << 8) | param[2];
 
-	/*
-	 * Assume every version greater than this is new EeePC style
-	 * hardware with 6 byte packets
-	 */
-	if (etd->fw_version >= 0x020030) {
+	if (etd->fw_version < 0x020030) {
+		etd->hw_version = 1;
+		etd->paritycheck = 1;
+	} else if (etd->fw_version <= 0x150600) {
 		etd->hw_version = 2;
 		/* For now show extra debug information */
 		etd->debug = 1;
-		/* Don't know how to do parity checking for version 2 */
-		etd->paritycheck = 0;
 
 		if (etd->fw_version >= 0x020800)
 			etd->reports_pressure = true;
-
+	} else if ((etd->fw_version & 0x0f0000) == 0x050000) {
+		etd->hw_version = 3;
+		etd->debug = 1;
+		etd->reports_pressure = true;
 	} else {
-		etd->hw_version = 1;
-		etd->paritycheck = 1;
+		pr_debug("unrecognized firmware version 0x%06x\n",
+			 etd->fw_version);
+		goto init_fail;
 	}
 
 	pr_info("assuming hardware version %d, firmware version %d.%d.%d\n",
@@ -794,7 +913,11 @@ int elantech_init(struct psmouse *psmouse)
 		goto init_fail;
 	}
 
-	elantech_set_input_params(psmouse);
+	error = elantech_set_input_params(psmouse);
+	if (error) {
+		pr_err("failed to set input parameters\n");
+		goto init_fail;
+	}
 
 	error = sysfs_create_group(&psmouse->ps2dev.serio->dev.kobj,
 				   &elantech_attr_group);
@@ -806,7 +929,7 @@ int elantech_init(struct psmouse *psmouse)
 	psmouse->protocol_handler = elantech_process_byte;
 	psmouse->disconnect = elantech_disconnect;
 	psmouse->reconnect = elantech_reconnect;
-	psmouse->pktsize = etd->hw_version == 2 ? 6 : 4;
+	psmouse->pktsize = etd->hw_version == 1 ? 4 : 6;
 
 	return 0;
 
diff --git a/drivers/input/mouse/elantech.h b/drivers/input/mouse/elantech.h
index fabb2b9..40787d6 100644
--- a/drivers/input/mouse/elantech.h
+++ b/drivers/input/mouse/elantech.h
@@ -16,12 +16,14 @@
 /*
  * Command values for Synaptics style queries
  */
+#define ETP_FW_ID_QUERY			0x00
 #define ETP_FW_VERSION_QUERY		0x01
 #define ETP_CAPABILITIES_QUERY		0x02
 
 /*
  * Command values for register reading or writing
  */
+#define ETP_REGISTER_RW			0x00
 #define ETP_REGISTER_READ		0x10
 #define ETP_REGISTER_WRITE		0x11
 
@@ -110,6 +112,9 @@ struct elantech_data {
 	bool reports_pressure;
 	unsigned char hw_version;
 	unsigned int fw_version;
+	unsigned int x_max;
+	unsigned int y_max;
+	unsigned int y_max_2ft;
 	unsigned int single_finger_reports;
 	unsigned char parity[256];
 };
-- 
1.7.4.1


Reply to: