diff mbox

[v4,8/8] Input: elantech - add v4 hardware support

Message ID 1314606539-24722-9-git-send-email-jj_ding@emc.com.tw (mailing list archive)
State New, archived
Headers show

Commit Message

JJ Ding Aug. 29, 2011, 8:28 a.m. UTC
v4 hardware is a true multitouch capable touchpad (up to 5 fingers).
The packet format is quite complex, please see protocol document for
reference.

Signed-off-by: JJ Ding <jj_ding@emc.com.tw>
---
 Documentation/input/elantech.txt |  170 ++++++++++++++++++++++++++
 drivers/input/mouse/elantech.c   |  247 ++++++++++++++++++++++++++++++++++----
 drivers/input/mouse/elantech.h   |   29 ++++-
 3 files changed, 420 insertions(+), 26 deletions(-)

Comments

Tom Lin Aug. 30, 2011, 1:50 p.m. UTC | #1
Hi JJ
On Mon, 2011-08-29 at 16:28 +0800, JJ Ding wrote:
> v4 hardware is a true multitouch capable touchpad (up to 5 fingers).
> The packet format is quite complex, please see protocol document for
> reference.
> 
> Signed-off-by: JJ Ding <jj_ding@emc.com.tw>
> ---
>  Documentation/input/elantech.txt |  170 ++++++++++++++++++++++++++
>  drivers/input/mouse/elantech.c   |  247 ++++++++++++++++++++++++++++++++++----
>  drivers/input/mouse/elantech.h   |   29 ++++-
>  3 files changed, 420 insertions(+), 26 deletions(-)
> 
> diff --git a/Documentation/input/elantech.txt b/Documentation/input/elantech.txt
> index cee08ee..f63115a 100644
> --- a/Documentation/input/elantech.txt
> +++ b/Documentation/input/elantech.txt
> @@ -32,6 +32,12 @@ Contents
>      6.2 Native absolute mode 6 byte packet format
>          6.2.1 One/Three finger touch
>          6.2.2 Two finger touch
> + 7. Hardware version 4
> +    7.1 Registers
> +    7.2 Native absolute mode 6 byte packet format
> +        7.2.1 Status packet
> +        7.2.2 Head packet
> +        7.2.3 Motion packet
>  
> 
> 
> @@ -573,3 +579,167 @@ The packet format is exactly the same for two finger touch, except the hardware
>  sends two 6 byte packets. The first packet contains data for the first finger,
>  the second packet has data for the second finger. So for two finger touch a
>  total of 12 bytes are sent.
> +
> +/////////////////////////////////////////////////////////////////////////////
> +
> +7. Hardware version 4
> +   ==================
> +
> +7.1 Registers
> +    ~~~~~~~~~
> +* reg_07
> +
> +   bit   7   6   5   4   3   2   1   0
> +         0   0   0   0   0   0   0   A
> +
> +         A: 1 = enable absolute tracking
> +
> +7.2 Native absolute mode 6 byte packet format
> +    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +v4 hardware is a true multitouch touchpad, capable of tracking up to 5 fingers.
> +Unfortunately, due to PS/2's limited bandwidth, its packet format is rather
> +complex.
> +
> +Whenever the numbers or identities of the fingers changes, the hardware sends a
> +status packet to indicate how many and which fingers is on touchpad, followed by
> +head packets or motion packets. A head packet contains data of finger id, finger
> +position (absolute x, y values), width, and presure. A motion packet contains
> +two fingers' position delta.
> +
> +For example, when status packet tells there are 2 fingers on touchpad, then we
> +can expect two following head packets. If the finger status doesn't change,
> +the following packets would be motion packets, only sending delta of finger
> +position, until we receive a status packet.
> +
> +One exception is one finger touch. when a status packet tells us there is only
> +one finger, the hardware would just send head packets afterwards.
> +
> +7.2.1 Status packet
> +      ~~~~~~~~~~~~~
> +
> +byte 0:
> +
> +   bit   7   6   5   4   3   2   1   0
> +         .   .   .   .   0   1   R   L
> +
> +         L, R = 1 when Left, Right mouse button pressed
> +
> +byte 1:
> +
> +   bit   7   6   5   4   3   2   1   0
> +         .   .   . ft4 ft3 ft2 ft1 ft0
> +
> +         ft4 ft3 ft2 ft1 ft0 ftn = 1 when finger n is on touchpad
> +
> +byte 2: not used
> +
> +byte 3:
> +
> +   bit   7   6   5   4   3   2   1   0
> +         .   .   .   1   0   0   0   0
> +
> +         constant bits
> +
> +byte 4:
> +
> +   bit   7   6   5   4   3   2   1   0
> +         p   .   .   .   .   .   .   .
> +
> +         p = 1 for palm
> +
> +byte 5: not used
> +
> +7.2.2 Head packet
> +      ~~~~~~~~~~~
> +
> +byte 0:
> +
> +   bit   7   6   5   4   3   2   1   0
> +        w3  w2  w1  w0   0   1   R   L
> +
> +        L, R = 1 when Left, Right mouse button pressed
> +        w3..w0 = finger width (spans how many trace lines)
> +
> +byte 1:
> +
> +   bit   7   6   5   4   3   2   1   0
> +        p7  p6  p5  p4 x11 x10  x9  x8
> +
> +byte 2:
> +
> +   bit   7   6   5   4   3   2   1   0
> +        x7  x6  x5  x4  x3  x2  x1  x0
> +
> +        x11..x0 = absolute x value (horizontal)
> +
> +byte 3:
> +
> +   bit   7   6   5   4   3   2   1   0
> +       id2 id1 id0   1   0   0   0   1
> +
> +       id2..id0 = finger id
> +
> +byte 4:
> +
> +   bit   7   6   5   4   3   2   1   0
> +        p3  p1  p2  p0  y11 y10 y9  y8
> +
> +        p7..p0 = pressure
> +
> +byte 5:
> +
> +   bit   7   6   5   4   3   2   1   0
> +        y7  y6  y5  y4  y3  y2  y1  y0
> +
> +        y11..y0 = absolute y value (vertical)
> +
> +7.2.3 Motion packet
> +      ~~~~~~~~~~~~~
> +
> +byte 0:
> +
> +   bit   7   6   5   4   3   2   1   0
> +       id2 id1 id0   w   0   1   R   L
> +
> +       L, R = 1 when Left, Right mouse button pressed
> +       id2..id0 = finger id
> +       w = 1 when delta overflows (> 127 or < -128), in this case
> +       firmware sends us (delta x / 5) and (delta y  / 5)
> +
> +byte 1:
> +
> +   bit   7   6   5   4   3   2   1   0
> +        x7  x6  x5  x4  x3  x2  x1  x0
> +
> +        x7..x0 = delta x (two's complement)
> +
> +byte 2:
> +
> +   bit   7   6   5   4   3   2   1   0
> +        y7  y6  y5  y4  y3  y2  y1  y0
> +
> +        y7..y0 = delta y (two's complement)
> +
> +byte 3:
> +
> +   bit   7   6   5   4   3   2   1   0
> +       id2 id1 id0   1   0   0   1   0
> +
> +       id2..id0 = finger id
> +
> +byte 4:
> +
> +   bit   7   6   5   4   3   2   1   0
> +        x7  x6  x5  x4  x3  x2  x1  x0
> +
> +        x7..x0 = delta x (two's complement)
> +
> +byte 5:
> +
> +   bit   7   6   5   4   3   2   1   0
> +        y7  y6  y5  y4  y3  y2  y1  y0
> +
> +        y7..y0 = delta y (two's complement)
> +
> +        byte 0 ~ 2 for one finger
> +        byte 3 ~ 5 for another
> diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c
> index c4ceefd..0d3936d 100644
> --- a/drivers/input/mouse/elantech.c
> +++ b/drivers/input/mouse/elantech.c
> @@ -84,12 +84,6 @@ static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg,
>  	unsigned char param[3];
>  	int rc = 0;
>  
> -	if (reg < 0x10 || reg > 0x26)
> -		return -1;
> -
> -	if (reg > 0x11 && reg < 0x20)
> -		return -1;
> -
>  	switch (etd->hw_version) {
>  	case 1:
>  		if (psmouse_sliced_command(psmouse, ETP_REGISTER_READ) ||
> @@ -109,7 +103,7 @@ static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg,
>  		}
>  		break;
>  
> -	case 3:
> +	case 3 ... 4:
>  		if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
>  		    elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READWRITE) ||
>  		    elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
> @@ -122,8 +116,10 @@ static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg,
>  
>  	if (rc)
>  		pr_err("failed to read register 0x%02x.\n", reg);
> -	else
> +	else if (etd->hw_version != 4)
>  		*val = param[0];
> +	else
> +		*val = param[1];
>  
>  	return rc;
>  }
> @@ -137,12 +133,6 @@ static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg,
>  	struct elantech_data *etd = psmouse->private;
>  	int rc = 0;
>  
> -	if (reg < 0x10 || reg > 0x26)
> -		return -1;
> -
> -	if (reg > 0x11 && reg < 0x20)
> -		return -1;
> -
>  	switch (etd->hw_version) {
>  	case 1:
>  		if (psmouse_sliced_command(psmouse, ETP_REGISTER_WRITE) ||
> @@ -176,6 +166,20 @@ static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg,
>  			rc = -1;
>  		}
>  		break;
> +
> +	case 4:
> +		if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
> +		    elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READWRITE) ||
> +		    elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
> +		    elantech_ps2_command(psmouse, NULL, reg) ||
> +		    elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
> +		    elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READWRITE) ||
> +		    elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
> +		    elantech_ps2_command(psmouse, NULL, val) ||
> +		    elantech_ps2_command(psmouse, NULL, PSMOUSE_CMD_SETSCALE11)) {
> +			rc = -1;
> +		}
> +		break;
>  	}
>  
>  	if (rc)
> @@ -409,12 +413,12 @@ static void elantech_report_absolute_v3(struct psmouse *psmouse,
>  			 * byte 1:   .    .    .    .  ax11 ax10 ax9  ax8
>  			 * byte 2: ax7  ax6  ax5  ax4  ax3  ax2  ax1  ax0
>  			 */
> -			etd->prev_x = ((packet[1] & 0x0f) << 8) | packet[2];
> +			etd->mt[0].x = ((packet[1] & 0x0f) << 8) | packet[2];
>  			/*
>  			 * byte 4:   .    .    .    .  ay11 ay10 ay9  ay8
>  			 * byte 5: ay7  ay6  ay5  ay4  ay3  ay2  ay1  ay0
>  			 */
> -			etd->prev_y = etd->y_max -
> +			etd->mt[0].y = etd->y_max -
>  				(((packet[4] & 0x0f) << 8) | packet[5]);
>  			/*
>  			 * wait for next packet
> @@ -423,8 +427,8 @@ static void elantech_report_absolute_v3(struct psmouse *psmouse,
>  		}
>  
>  		/* packet_type == PACKET_V3_TAIL */
> -		x1 = etd->prev_x;
> -		y1 = etd->prev_y;
> +		x1 = etd->mt[0].x;
> +		y1 = etd->mt[0].y;
>  		x2 = ((packet[1] & 0x0f) << 8) | packet[2];
>  		y2 = etd->y_max - (((packet[4] & 0x0f) << 8) | packet[5]);
>  		break;
> @@ -450,6 +454,129 @@ static void elantech_report_absolute_v3(struct psmouse *psmouse,
>  	input_sync(dev);
>  }
>  
> +static void elantech_mt_sync(struct psmouse *psmouse)
> +{
> +	struct input_dev *dev = psmouse->dev;
> +	unsigned char *packet = psmouse->packet;
> +
> +	input_report_key(dev, BTN_LEFT, packet[0] & 0x01);
> +	input_report_key(dev, BTN_RIGHT, packet[0] & 0x02);
> +	input_mt_report_pointer_emulation(dev, true);
> +	input_sync(dev);
> +}
> +
> +static void process_packet_status(struct psmouse *psmouse)
> +{
> +	struct input_dev *dev = psmouse->dev;
> +	unsigned char *packet = psmouse->packet;
> +	unsigned fingers;
> +	int i;
> +
> +	/* notify finger state change */
> +	fingers = packet[1] & 0x1f;
> +	for (i = 0; i < ETP_MAX_FINGERS; i++) {
> +		if ((fingers & (1 << i)) == 0) {
> +			input_mt_slot(dev, i);
> +			input_mt_report_slot_state(dev, MT_TOOL_FINGER, false);
> +		}
> +	}
> +
> +	elantech_mt_sync(psmouse);
> +}
> +
> +static void process_packet_head(struct psmouse *psmouse)
> +{
> +	struct input_dev *dev = psmouse->dev;
> +	struct elantech_data *etd = psmouse->private;
> +	unsigned char *packet = psmouse->packet;
> +	int id = ((packet[3] & 0xe0) >> 5) - 1;
> +	int pres, traces;
> +
> +	if (id < 0)
> +		return;
> +
> +	etd->mt[id].x = ((packet[1] & 0x0f) << 8) | packet[2];
> +	etd->mt[id].y = etd->y_max - (((packet[4] & 0x0f) << 8) | packet[5]);
> +	pres = (packet[1] & 0xf0) | ((packet[4] & 0xf0) >> 4);
> +	traces = (packet[0] & 0xf0) >> 4;
> +
> +	input_mt_slot(dev, id);
> +	input_mt_report_slot_state(dev, MT_TOOL_FINGER, true);
> +
> +	input_report_abs(dev, ABS_MT_POSITION_X, etd->mt[id].x);
> +	input_report_abs(dev, ABS_MT_POSITION_Y, etd->mt[id].y);
> +	input_report_abs(dev, ABS_MT_PRESSURE, pres);
> +	input_report_abs(dev, ABS_MT_TOUCH_MAJOR, traces * etd->width);
> +	/* report this for backwards compatibility */
> +	input_report_abs(dev, ABS_TOOL_WIDTH, traces);
> +
> +	elantech_mt_sync(psmouse);
> +}
> +
> +static void process_packet_motion(struct psmouse *psmouse)
> +{
> +	struct input_dev *dev = psmouse->dev;
> +	struct elantech_data *etd = psmouse->private;
> +	unsigned char *packet = psmouse->packet;
> +	int weight, delta_x1 = 0, delta_y1 = 0, delta_x2 = 0, delta_y2 = 0;
> +	int id, sid;
> +
> +	id = ((packet[0] & 0xe0) >> 5) - 1;
> +	if (id < 0)
> +		return;
> +
> +	sid = ((packet[3] & 0xe0) >> 5) - 1;
> +	weight = (packet[0] & 0x10) ? ETP_WEIGHT_VALUE : 1;
> +	/*
> +	 * Motion packets give us the delta of x, y values of specific fingers,
> +	 * but in two's complement. Let the compiler do the conversion for us.
> +	 * Also _enlarge_ the numbers to int, in case of overflow.
> +	 */
> +	delta_x1 = (signed char)packet[1];
> +	delta_y1 = (signed char)packet[2];
> +	delta_x2 = (signed char)packet[4];
> +	delta_y2 = (signed char)packet[5];
> +
> +	etd->mt[id].x += delta_x1 * weight;
> +	etd->mt[id].y -= delta_y1 * weight;
> +	input_mt_slot(dev, id);
> +	input_report_abs(dev, ABS_MT_POSITION_X, etd->mt[id].x);
> +	input_report_abs(dev, ABS_MT_POSITION_Y, etd->mt[id].y);
> +
> +	if (sid >= 0) {
> +		etd->mt[sid].x += delta_x2 * weight;
> +		etd->mt[sid].y -= delta_y2 * weight;
> +		input_mt_slot(dev, sid);
> +		input_report_abs(dev, ABS_MT_POSITION_X, etd->mt[sid].x);
> +		input_report_abs(dev, ABS_MT_POSITION_Y, etd->mt[sid].y);
> +	}
> +
> +	elantech_mt_sync(psmouse);
> +}
> +
> +static void elantech_report_absolute_v4(struct psmouse *psmouse,
> +					int packet_type)
> +{
> +	switch (packet_type) {
> +	case PACKET_V4_STATUS:
> +		process_packet_status(psmouse);
> +		break;
> +
> +	case PACKET_V4_HEAD:
> +		process_packet_head(psmouse);
> +		break;
> +
> +	case PACKET_V4_MOTION:
> +		process_packet_motion(psmouse);
> +		break;
> +
> +	case PACKET_UNKNOWN:
		  elantech_packet_dump(psmouse->packet, psmosue->pktsize);
You could add this function here.
We will get detail data for debug when data type is PACKET_UNKNOWN.
> +	default:
> +		/* impossible to get here */
> +		break;
> +	}
> +}
> +
>  static int elantech_packet_check_v1(struct psmouse *psmouse)
>  {
>  	struct elantech_data *etd = psmouse->private;
> @@ -504,7 +631,7 @@ static int elantech_packet_check_v2(struct psmouse *psmouse)
>  
>  /*
>   * We check the constant bits to determine what packet type we get,
> - * so packet checking is mandatory for v3 hardware.
> + * so packet checking is mandatory for v3 and later hardware.
>   */
>  static int elantech_packet_check_v3(struct psmouse *psmouse)
>  {
> @@ -527,6 +654,25 @@ static int elantech_packet_check_v3(struct psmouse *psmouse)
>  	return PACKET_UNKNOWN;
>  }
>  
> +static int elantech_packet_check_v4(struct psmouse *psmouse)
> +{
> +	unsigned char *packet = psmouse->packet;
> +
> +	if ((packet[0] & 0x0c) == 0x04 &&
> +	    (packet[3] & 0x1f) == 0x11)
> +		return PACKET_V4_HEAD;
> +
> +	if ((packet[0] & 0x0c) == 0x04 &&
> +	    (packet[3] & 0x1f) == 0x12)
> +		return PACKET_V4_MOTION;
> +
> +	if ((packet[0] & 0x0c) == 0x04 &&
> +	    (packet[3] & 0x1f) == 0x10)
> +		return PACKET_V4_STATUS;
> +
> +	return PACKET_UNKNOWN;
> +}
> +
>  /*
>   * Process byte stream from mouse and handle complete packets
>   */
> @@ -567,6 +713,14 @@ static psmouse_ret_t elantech_process_byte(struct psmouse *psmouse)
>  
>  		elantech_report_absolute_v3(psmouse, packet_type);
>  		break;
> +
> +	case 4:
> +		packet_type = elantech_packet_check_v4(psmouse);
> +		if (packet_type == PACKET_UNKNOWN)
> +			return PSMOUSE_BAD_DATA;
> +
> +		elantech_report_absolute_v4(psmouse, packet_type);
> +		break;
>  	}
>  
>  	return PSMOUSE_FULL_PACKET;
> @@ -610,6 +764,13 @@ static int elantech_set_absolute_mode(struct psmouse *psmouse)
>  			rc = -1;
>  
>  		break;
> +
> +	case 4:
> +		etd->reg_07 = 0x01;
> +		if (elantech_write_reg(psmouse, 0x07, etd->reg_07))
> +			rc = -1;
> +
> +		goto skip_readback_reg_10; /* v4 has no reg 0x10 to read */
>  	}
>  
>  	if (rc == 0) {
> @@ -637,6 +798,7 @@ static int elantech_set_absolute_mode(struct psmouse *psmouse)
>  		}
>  	}
>  
> + skip_readback_reg_10:
>  	if (rc)
>  		pr_err("failed to initialise registers.\n");
>  
> @@ -645,10 +807,11 @@ static int elantech_set_absolute_mode(struct psmouse *psmouse)
>  
>  static int set_range(struct psmouse *psmouse, unsigned int *x_min,
>  		     unsigned int *y_min, unsigned int *x_max,
> -		     unsigned int *y_max)
> +		     unsigned int *y_max, unsigned int *width)
>  {
>  	struct elantech_data *etd = psmouse->private;
>  	unsigned char param[3];
> +	unsigned char traces = 0;
>  	int i;
>  
>  	switch (etd->hw_version) {
> @@ -677,12 +840,16 @@ static int set_range(struct psmouse *psmouse, unsigned int *x_min,
>  		}
>  		break;
>  
> +	case 4:
> +		traces = etd->capabilities[1];
> +		/* pass through */
>  	case 3:
>  		if (synaptics_send_cmd(psmouse, ETP_FW_ID_QUERY, param))
>  			return -1;
>  
>  		*x_max = (0x0f & param[0]) << 8 | param[1];
>  		*y_max = (0xf0 & param[0]) << 4 | param[2];
> +		*width = *x_max / (traces - 1);
>  		break;
>  	}
>  
> @@ -696,9 +863,9 @@ static int elantech_set_input_params(struct psmouse *psmouse)
>  {
>  	struct input_dev *dev = psmouse->dev;
>  	struct elantech_data *etd = psmouse->private;
> -	unsigned int x_min = 0, y_min = 0, x_max = 0, y_max = 0;
> +	unsigned int x_min = 0, y_min = 0, x_max = 0, y_max = 0, width = 0;
>  
> -	if (set_range(psmouse, &x_min, &y_min, &x_max, &y_max))
> +	if (set_range(psmouse, &x_min, &y_min, &x_max, &y_max, &width))
>  		return -1;
>  
>  	__set_bit(EV_KEY, dev->evbit);
> @@ -742,9 +909,37 @@ static int elantech_set_input_params(struct psmouse *psmouse)
>  		input_set_abs_params(dev, ABS_MT_POSITION_X, x_min, x_max, 0, 0);
>  		input_set_abs_params(dev, ABS_MT_POSITION_Y, y_min, y_max, 0, 0);
>  		break;
> +
> +	case 4:
> +		__set_bit(BTN_TOOL_QUADTAP, dev->keybit);
> +		/* For X to recognize me as touchpad. */
> +		input_set_abs_params(dev, ABS_X, x_min, x_max, 0, 0);
> +		input_set_abs_params(dev, ABS_Y, y_min, y_max, 0, 0);
> +		/*
> +		 * range of pressure and width is the same as v2,
> +		 * report ABS_PRESSURE, ABS_TOOL_WIDTH for compatibility.
> +		 */
> +		input_set_abs_params(dev, ABS_PRESSURE, ETP_PMIN_V2,
> +				     ETP_PMAX_V2, 0, 0);
> +		input_set_abs_params(dev, ABS_TOOL_WIDTH, ETP_WMIN_V2,
> +				     ETP_WMAX_V2, 0, 0);
> +		/* Multitouch capable pad, up to 5 fingers. */
> +		input_mt_init_slots(dev, ETP_MAX_FINGERS);
> +		input_set_abs_params(dev, ABS_MT_POSITION_X, x_min, x_max, 0, 0);
> +		input_set_abs_params(dev, ABS_MT_POSITION_Y, y_min, y_max, 0, 0);
> +		input_set_abs_params(dev, ABS_MT_PRESSURE, ETP_PMIN_V2,
> +				     ETP_PMAX_V2, 0, 0);
> +		/*
> +		 * The firmware reports how many trace lines the finger spans,
> +		 * convert to surface unit as Protocol-B requires.
> +		 */
> +		input_set_abs_params(dev, ABS_MT_TOUCH_MAJOR, 0,
> +				     ETP_WMAX_V2 * width, 0, 0);
> +		break;
>  	}
>  
>  	etd->y_max = y_max;
> +	etd->width = width;
>  
>  	return 0;
>  }
> @@ -816,6 +1011,7 @@ static ssize_t elantech_set_int_attr(struct psmouse *psmouse,
>  			    elantech_show_int_attr,			\
>  			    elantech_set_int_attr)
>  
> +ELANTECH_INT_ATTR(reg_07, 0x07);
>  ELANTECH_INT_ATTR(reg_10, 0x10);
>  ELANTECH_INT_ATTR(reg_11, 0x11);
>  ELANTECH_INT_ATTR(reg_20, 0x20);
> @@ -829,6 +1025,7 @@ ELANTECH_INT_ATTR(debug, 0);
>  ELANTECH_INT_ATTR(paritycheck, 0);
>  
>  static struct attribute *elantech_attrs[] = {
> +	&psmouse_attr_reg_07.dattr.attr,
>  	&psmouse_attr_reg_10.dattr.attr,
>  	&psmouse_attr_reg_11.dattr.attr,
>  	&psmouse_attr_reg_20.dattr.attr,
> @@ -957,12 +1154,16 @@ static int elantech_reconnect(struct psmouse *psmouse)
>   */
>  static int elantech_set_properties(struct elantech_data *etd)
>  {
> +	int ver = (etd->fw_version & 0x0f0000) >> 16;
> +
>  	if (etd->fw_version < 0x020030 || etd->fw_version == 0x020600)
>  		etd->hw_version = 1;
>  	else if (etd->fw_version < 0x150600)
>  		etd->hw_version = 2;
> -	else if ((etd->fw_version & 0x0f0000) >> 16 == 5)
> +	else if (ver == 5)
>  		etd->hw_version = 3;
> +	else if (ver == 6)
> +		etd->hw_version = 4;
>  	else
>  		return -1;
>  
> diff --git a/drivers/input/mouse/elantech.h b/drivers/input/mouse/elantech.h
> index 236c33c..7ecaef0 100644
> --- a/drivers/input/mouse/elantech.h
> +++ b/drivers/input/mouse/elantech.h
> @@ -82,14 +82,37 @@
>  #define ETP_WMAX_V2			15
>  
>  /*
> - * v3 hardware has 2 kinds of packet types.
> + * v3 hardware has 2 kinds of packet types,
> + * v4 hardware has 3.
>   */
>  #define PACKET_UNKNOWN			0x01
>  #define PACKET_DEBOUNCE			0x02
>  #define PACKET_V3_HEAD			0x03
>  #define PACKET_V3_TAIL			0x04
> +#define PACKET_V4_HEAD			0x05
> +#define PACKET_V4_MOTION		0x06
> +#define PACKET_V4_STATUS		0x07
> +
> +/*
> + * track up to 5 fingers for v4 hardware
> + */
> +#define ETP_MAX_FINGERS			5
> +
> +/*
> + * weight value for v4 hardware
> + */
> +#define ETP_WEIGHT_VALUE		5
> +
> +/*
> + * The base position for one finger, v4 hardware
> + */
> +struct finger_pos {
> +	unsigned int x;
> +	unsigned int y;
> +};
>  
>  struct elantech_data {
> +	unsigned char reg_07;
>  	unsigned char reg_10;
>  	unsigned char reg_11;
>  	unsigned char reg_20;
> @@ -108,8 +131,8 @@ struct elantech_data {
>  	unsigned int fw_version;
>  	unsigned int single_finger_reports;
>  	unsigned int y_max;
> -	unsigned int prev_x;
> -	unsigned int prev_y;
> +	unsigned int width;
> +	struct finger_pos mt[ETP_MAX_FINGERS];
>  	unsigned char parity[256];
>  };
>  


--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Éric Piel Aug. 31, 2011, 12:50 p.m. UTC | #2
Op 29-08-11 10:28, JJ Ding schreef:
> v4 hardware is a true multitouch capable touchpad (up to 5 fingers).
> The packet format is quite complex, please see protocol document for
> reference.
Hi,

It's great that you add support for another version!

Looks good. Just a few comment (inline).

Cheers,
Éric


>
> Signed-off-by: JJ Ding<jj_ding@emc.com.tw>
> ---
>   Documentation/input/elantech.txt |  170 ++++++++++++++++++++++++++
>   drivers/input/mouse/elantech.c   |  247 ++++++++++++++++++++++++++++++++++----
>   drivers/input/mouse/elantech.h   |   29 ++++-
>   3 files changed, 420 insertions(+), 26 deletions(-)
>
> diff --git a/Documentation/input/elantech.txt b/Documentation/input/elantech.txt
> index cee08ee..f63115a 100644
> --- a/Documentation/input/elantech.txt
> +++ b/Documentation/input/elantech.txt
> @@ -32,6 +32,12 @@ Contents
>       6.2 Native absolute mode 6 byte packet format
>           6.2.1 One/Three finger touch
>           6.2.2 Two finger touch
> + 7. Hardware version 4
> +    7.1 Registers
> +    7.2 Native absolute mode 6 byte packet format
> +        7.2.1 Status packet
> +        7.2.2 Head packet
> +        7.2.3 Motion packet
>
>
>
> @@ -573,3 +579,167 @@ The packet format is exactly the same for two finger touch, except the hardware
>   sends two 6 byte packets. The first packet contains data for the first finger,
>   the second packet has data for the second finger. So for two finger touch a
>   total of 12 bytes are sent.
> +
> +/////////////////////////////////////////////////////////////////////////////
> +
> +7. Hardware version 4
> +   ==================
> +
> +7.1 Registers
> +    ~~~~~~~~~
> +* reg_07
> +
> +   bit   7   6   5   4   3   2   1   0
> +         0   0   0   0   0   0   0   A
> +
> +         A: 1 = enable absolute tracking
> +
> +7.2 Native absolute mode 6 byte packet format
> +    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +v4 hardware is a true multitouch touchpad, capable of tracking up to 5 fingers.
> +Unfortunately, due to PS/2's limited bandwidth, its packet format is rather
> +complex.
> +
> +Whenever the numbers or identities of the fingers changes, the hardware sends a
> +status packet to indicate how many and which fingers is on touchpad, followed by
> +head packets or motion packets. A head packet contains data of finger id, finger
> +position (absolute x, y values), width, and presure. A motion packet contains
Typo: pres_s_ure

> +two fingers' position delta.
> +
:
> diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c
> index c4ceefd..0d3936d 100644
> --- a/drivers/input/mouse/elantech.c
> +++ b/drivers/input/mouse/elantech.c
> @@ -84,12 +84,6 @@ static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg,
>   	unsigned char param[3];
>   	int rc = 0;
>
> -	if (reg<  0x10 || reg>  0x26)
> -		return -1;
> -
> -	if (reg>  0x11&&  reg<  0x20)
> -		return -1;
> -
You could still leave a check of reg being between 0x07 and 0x26, it's
better than nothing.

:
>
> +static void elantech_mt_sync(struct psmouse *psmouse)
> +{
> +	struct input_dev *dev = psmouse->dev;
> +	unsigned char *packet = psmouse->packet;
> +
> +	input_report_key(dev, BTN_LEFT, packet[0]&  0x01);
> +	input_report_key(dev, BTN_RIGHT, packet[0]&  0x02);
> +	input_mt_report_pointer_emulation(dev, true);
> +	input_sync(dev);
> +}
The function naming is a bit strange. If you put _mt_, I expect there is 
only code related to multitouch. Maybe rename to:
elantech_input_sync_v4()


> +
> +static void process_packet_status(struct psmouse *psmouse)
:
> +static void process_packet_head(struct psmouse *psmouse)
:
> +static void process_packet_motion(struct psmouse *psmouse)
Maybe rename these function to *_v4(), so that it's clear it's not for 
v3 hardware or any other version.

:
> @@ -645,10 +807,11 @@ static int elantech_set_absolute_mode(struct psmouse *psmouse)
>
>   static int set_range(struct psmouse *psmouse, unsigned int *x_min,
>   		     unsigned int *y_min, unsigned int *x_max,
> -		     unsigned int *y_max)
> +		     unsigned int *y_max, unsigned int *width)
>   {
>   	struct elantech_data *etd = psmouse->private;
>   	unsigned char param[3];
> +	unsigned char traces = 0;
Don't initialize it.

>   	int i;
>
>   	switch (etd->hw_version) {
> @@ -677,12 +840,16 @@ static int set_range(struct psmouse *psmouse, unsigned int *x_min,
>   		}
>   		break;
>
> +	case 4:
> +		traces = etd->capabilities[1];
> +		/* pass through */
>   	case 3:
>   		if (synaptics_send_cmd(psmouse, ETP_FW_ID_QUERY, param))
>   			return -1;
>
>   		*x_max = (0x0f&  param[0])<<  8 | param[1];
>   		*y_max = (0xf0&  param[0])<<  4 | param[2];
> +		*width = *x_max / (traces - 1);
>   		break;
>   	}
width is used only for firmware 4, right? If so then this code is too 
tricky. Order normally the cases, and duplicate the few common lines. 
Maintainability is more important than saving a couple of bytes :-)

Also, what happens if the firmware returns 1 in  etd->capabilities[1]? 
Make sure a division by zero is _impossible_. Please add a check on sane 
values for "traces", and bail out if it's not within these limits:
	if ((traces < 2) || (traces > *x_max))
		return -1;

Éric

--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
JJ Ding Sept. 1, 2011, 1:31 a.m. UTC | #3
Hi Éric,

Thanks for your comments.
Reply below inline, thank you.

jj

On Wed, 31 Aug 2011 14:50:59 +0200, Éric Piel <E.A.B.Piel@tudelft.nl> wrote:
> Op 29-08-11 10:28, JJ Ding schreef:
> > v4 hardware is a true multitouch capable touchpad (up to 5 fingers).
> > The packet format is quite complex, please see protocol document for
> > reference.
> Hi,
> 
> It's great that you add support for another version!
> 
> Looks good. Just a few comment (inline).
> 
> Cheers,
> Éric
> 
> 
> >
> > Signed-off-by: JJ Ding<jj_ding@emc.com.tw>
> > ---
> >   Documentation/input/elantech.txt |  170 ++++++++++++++++++++++++++
> >   drivers/input/mouse/elantech.c   |  247 ++++++++++++++++++++++++++++++++++----
> >   drivers/input/mouse/elantech.h   |   29 ++++-
> >   3 files changed, 420 insertions(+), 26 deletions(-)
> >
> > diff --git a/Documentation/input/elantech.txt b/Documentation/input/elantech.txt
> > index cee08ee..f63115a 100644
> > --- a/Documentation/input/elantech.txt
> > +++ b/Documentation/input/elantech.txt
> > @@ -32,6 +32,12 @@ Contents
> >       6.2 Native absolute mode 6 byte packet format
> >           6.2.1 One/Three finger touch
> >           6.2.2 Two finger touch
> > + 7. Hardware version 4
> > +    7.1 Registers
> > +    7.2 Native absolute mode 6 byte packet format
> > +        7.2.1 Status packet
> > +        7.2.2 Head packet
> > +        7.2.3 Motion packet
> >
> >
> >
> > @@ -573,3 +579,167 @@ The packet format is exactly the same for two finger touch, except the hardware
> >   sends two 6 byte packets. The first packet contains data for the first finger,
> >   the second packet has data for the second finger. So for two finger touch a
> >   total of 12 bytes are sent.
> > +
> > +/////////////////////////////////////////////////////////////////////////////
> > +
> > +7. Hardware version 4
> > +   ==================
> > +
> > +7.1 Registers
> > +    ~~~~~~~~~
> > +* reg_07
> > +
> > +   bit   7   6   5   4   3   2   1   0
> > +         0   0   0   0   0   0   0   A
> > +
> > +         A: 1 = enable absolute tracking
> > +
> > +7.2 Native absolute mode 6 byte packet format
> > +    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > +v4 hardware is a true multitouch touchpad, capable of tracking up to 5 fingers.
> > +Unfortunately, due to PS/2's limited bandwidth, its packet format is rather
> > +complex.
> > +
> > +Whenever the numbers or identities of the fingers changes, the hardware sends a
> > +status packet to indicate how many and which fingers is on touchpad, followed by
> > +head packets or motion packets. A head packet contains data of finger id, finger
> > +position (absolute x, y values), width, and presure. A motion packet contains
> Typo: pres_s_ure
Fixed.
> > +two fingers' position delta.
> > +
> :
> > diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c
> > index c4ceefd..0d3936d 100644
> > --- a/drivers/input/mouse/elantech.c
> > +++ b/drivers/input/mouse/elantech.c
> > @@ -84,12 +84,6 @@ static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg,
> >   	unsigned char param[3];
> >   	int rc = 0;
> >
> > -	if (reg<  0x10 || reg>  0x26)
> > -		return -1;
> > -
> > -	if (reg>  0x11&&  reg<  0x20)
> > -		return -1;
> > -
> You could still leave a check of reg being between 0x07 and 0x26, it's
> better than nothing.
OK, I will add them back. 
> :
> >
> > +static void elantech_mt_sync(struct psmouse *psmouse)
> > +{
> > +	struct input_dev *dev = psmouse->dev;
> > +	unsigned char *packet = psmouse->packet;
> > +
> > +	input_report_key(dev, BTN_LEFT, packet[0]&  0x01);
> > +	input_report_key(dev, BTN_RIGHT, packet[0]&  0x02);
> > +	input_mt_report_pointer_emulation(dev, true);
> > +	input_sync(dev);
> > +}
> The function naming is a bit strange. If you put _mt_, I expect there is 
> only code related to multitouch. Maybe rename to:
> elantech_input_sync_v4()
Changed.
> 
> > +
> > +static void process_packet_status(struct psmouse *psmouse)
> :
> > +static void process_packet_head(struct psmouse *psmouse)
> :
> > +static void process_packet_motion(struct psmouse *psmouse)
> Maybe rename these function to *_v4(), so that it's clear it's not for 
> v3 hardware or any other version.
Changed.
> :
> > @@ -645,10 +807,11 @@ static int elantech_set_absolute_mode(struct psmouse *psmouse)
> >
> >   static int set_range(struct psmouse *psmouse, unsigned int *x_min,
> >   		     unsigned int *y_min, unsigned int *x_max,
> > -		     unsigned int *y_max)
> > +		     unsigned int *y_max, unsigned int *width)
> >   {
> >   	struct elantech_data *etd = psmouse->private;
> >   	unsigned char param[3];
> > +	unsigned char traces = 0;
> Don't initialize it.
Fixed.
> 
> >   	int i;
> >
> >   	switch (etd->hw_version) {
> > @@ -677,12 +840,16 @@ static int set_range(struct psmouse *psmouse, unsigned int *x_min,
> >   		}
> >   		break;
> >
> > +	case 4:
> > +		traces = etd->capabilities[1];
> > +		/* pass through */
> >   	case 3:
> >   		if (synaptics_send_cmd(psmouse, ETP_FW_ID_QUERY, param))
> >   			return -1;
> >
> >   		*x_max = (0x0f&  param[0])<<  8 | param[1];
> >   		*y_max = (0xf0&  param[0])<<  4 | param[2];
> > +		*width = *x_max / (traces - 1);
> >   		break;
> >   	}
> width is used only for firmware 4, right? If so then this code is too 
> tricky. Order normally the cases, and duplicate the few common lines. 
> Maintainability is more important than saving a couple of bytes :-)
Yes, width is only used for firmware 4. I will change this for better
maintainability.
> Also, what happens if the firmware returns 1 in  etd->capabilities[1]? 
> Make sure a division by zero is _impossible_. Please add a check on sane 
> values for "traces", and bail out if it's not within these limits:
> 	if ((traces < 2) || (traces > *x_max))
> 		return -1;
Thanks for reminding me this. I will fix it.
> Éric
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/input/elantech.txt b/Documentation/input/elantech.txt
index cee08ee..f63115a 100644
--- a/Documentation/input/elantech.txt
+++ b/Documentation/input/elantech.txt
@@ -32,6 +32,12 @@  Contents
     6.2 Native absolute mode 6 byte packet format
         6.2.1 One/Three finger touch
         6.2.2 Two finger touch
+ 7. Hardware version 4
+    7.1 Registers
+    7.2 Native absolute mode 6 byte packet format
+        7.2.1 Status packet
+        7.2.2 Head packet
+        7.2.3 Motion packet
 
 
 
@@ -573,3 +579,167 @@  The packet format is exactly the same for two finger touch, except the hardware
 sends two 6 byte packets. The first packet contains data for the first finger,
 the second packet has data for the second finger. So for two finger touch a
 total of 12 bytes are sent.
+
+/////////////////////////////////////////////////////////////////////////////
+
+7. Hardware version 4
+   ==================
+
+7.1 Registers
+    ~~~~~~~~~
+* reg_07
+
+   bit   7   6   5   4   3   2   1   0
+         0   0   0   0   0   0   0   A
+
+         A: 1 = enable absolute tracking
+
+7.2 Native absolute mode 6 byte packet format
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+v4 hardware is a true multitouch touchpad, capable of tracking up to 5 fingers.
+Unfortunately, due to PS/2's limited bandwidth, its packet format is rather
+complex.
+
+Whenever the numbers or identities of the fingers changes, the hardware sends a
+status packet to indicate how many and which fingers is on touchpad, followed by
+head packets or motion packets. A head packet contains data of finger id, finger
+position (absolute x, y values), width, and presure. A motion packet contains
+two fingers' position delta.
+
+For example, when status packet tells there are 2 fingers on touchpad, then we
+can expect two following head packets. If the finger status doesn't change,
+the following packets would be motion packets, only sending delta of finger
+position, until we receive a status packet.
+
+One exception is one finger touch. when a status packet tells us there is only
+one finger, the hardware would just send head packets afterwards.
+
+7.2.1 Status packet
+      ~~~~~~~~~~~~~
+
+byte 0:
+
+   bit   7   6   5   4   3   2   1   0
+         .   .   .   .   0   1   R   L
+
+         L, R = 1 when Left, Right mouse button pressed
+
+byte 1:
+
+   bit   7   6   5   4   3   2   1   0
+         .   .   . ft4 ft3 ft2 ft1 ft0
+
+         ft4 ft3 ft2 ft1 ft0 ftn = 1 when finger n is on touchpad
+
+byte 2: not used
+
+byte 3:
+
+   bit   7   6   5   4   3   2   1   0
+         .   .   .   1   0   0   0   0
+
+         constant bits
+
+byte 4:
+
+   bit   7   6   5   4   3   2   1   0
+         p   .   .   .   .   .   .   .
+
+         p = 1 for palm
+
+byte 5: not used
+
+7.2.2 Head packet
+      ~~~~~~~~~~~
+
+byte 0:
+
+   bit   7   6   5   4   3   2   1   0
+        w3  w2  w1  w0   0   1   R   L
+
+        L, R = 1 when Left, Right mouse button pressed
+        w3..w0 = finger width (spans how many trace lines)
+
+byte 1:
+
+   bit   7   6   5   4   3   2   1   0
+        p7  p6  p5  p4 x11 x10  x9  x8
+
+byte 2:
+
+   bit   7   6   5   4   3   2   1   0
+        x7  x6  x5  x4  x3  x2  x1  x0
+
+        x11..x0 = absolute x value (horizontal)
+
+byte 3:
+
+   bit   7   6   5   4   3   2   1   0
+       id2 id1 id0   1   0   0   0   1
+
+       id2..id0 = finger id
+
+byte 4:
+
+   bit   7   6   5   4   3   2   1   0
+        p3  p1  p2  p0  y11 y10 y9  y8
+
+        p7..p0 = pressure
+
+byte 5:
+
+   bit   7   6   5   4   3   2   1   0
+        y7  y6  y5  y4  y3  y2  y1  y0
+
+        y11..y0 = absolute y value (vertical)
+
+7.2.3 Motion packet
+      ~~~~~~~~~~~~~
+
+byte 0:
+
+   bit   7   6   5   4   3   2   1   0
+       id2 id1 id0   w   0   1   R   L
+
+       L, R = 1 when Left, Right mouse button pressed
+       id2..id0 = finger id
+       w = 1 when delta overflows (> 127 or < -128), in this case
+       firmware sends us (delta x / 5) and (delta y  / 5)
+
+byte 1:
+
+   bit   7   6   5   4   3   2   1   0
+        x7  x6  x5  x4  x3  x2  x1  x0
+
+        x7..x0 = delta x (two's complement)
+
+byte 2:
+
+   bit   7   6   5   4   3   2   1   0
+        y7  y6  y5  y4  y3  y2  y1  y0
+
+        y7..y0 = delta y (two's complement)
+
+byte 3:
+
+   bit   7   6   5   4   3   2   1   0
+       id2 id1 id0   1   0   0   1   0
+
+       id2..id0 = finger id
+
+byte 4:
+
+   bit   7   6   5   4   3   2   1   0
+        x7  x6  x5  x4  x3  x2  x1  x0
+
+        x7..x0 = delta x (two's complement)
+
+byte 5:
+
+   bit   7   6   5   4   3   2   1   0
+        y7  y6  y5  y4  y3  y2  y1  y0
+
+        y7..y0 = delta y (two's complement)
+
+        byte 0 ~ 2 for one finger
+        byte 3 ~ 5 for another
diff --git a/drivers/input/mouse/elantech.c b/drivers/input/mouse/elantech.c
index c4ceefd..0d3936d 100644
--- a/drivers/input/mouse/elantech.c
+++ b/drivers/input/mouse/elantech.c
@@ -84,12 +84,6 @@  static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg,
 	unsigned char param[3];
 	int rc = 0;
 
-	if (reg < 0x10 || reg > 0x26)
-		return -1;
-
-	if (reg > 0x11 && reg < 0x20)
-		return -1;
-
 	switch (etd->hw_version) {
 	case 1:
 		if (psmouse_sliced_command(psmouse, ETP_REGISTER_READ) ||
@@ -109,7 +103,7 @@  static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg,
 		}
 		break;
 
-	case 3:
+	case 3 ... 4:
 		if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
 		    elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READWRITE) ||
 		    elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
@@ -122,8 +116,10 @@  static int elantech_read_reg(struct psmouse *psmouse, unsigned char reg,
 
 	if (rc)
 		pr_err("failed to read register 0x%02x.\n", reg);
-	else
+	else if (etd->hw_version != 4)
 		*val = param[0];
+	else
+		*val = param[1];
 
 	return rc;
 }
@@ -137,12 +133,6 @@  static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg,
 	struct elantech_data *etd = psmouse->private;
 	int rc = 0;
 
-	if (reg < 0x10 || reg > 0x26)
-		return -1;
-
-	if (reg > 0x11 && reg < 0x20)
-		return -1;
-
 	switch (etd->hw_version) {
 	case 1:
 		if (psmouse_sliced_command(psmouse, ETP_REGISTER_WRITE) ||
@@ -176,6 +166,20 @@  static int elantech_write_reg(struct psmouse *psmouse, unsigned char reg,
 			rc = -1;
 		}
 		break;
+
+	case 4:
+		if (elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
+		    elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READWRITE) ||
+		    elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
+		    elantech_ps2_command(psmouse, NULL, reg) ||
+		    elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
+		    elantech_ps2_command(psmouse, NULL, ETP_REGISTER_READWRITE) ||
+		    elantech_ps2_command(psmouse, NULL, ETP_PS2_CUSTOM_COMMAND) ||
+		    elantech_ps2_command(psmouse, NULL, val) ||
+		    elantech_ps2_command(psmouse, NULL, PSMOUSE_CMD_SETSCALE11)) {
+			rc = -1;
+		}
+		break;
 	}
 
 	if (rc)
@@ -409,12 +413,12 @@  static void elantech_report_absolute_v3(struct psmouse *psmouse,
 			 * byte 1:   .    .    .    .  ax11 ax10 ax9  ax8
 			 * byte 2: ax7  ax6  ax5  ax4  ax3  ax2  ax1  ax0
 			 */
-			etd->prev_x = ((packet[1] & 0x0f) << 8) | packet[2];
+			etd->mt[0].x = ((packet[1] & 0x0f) << 8) | packet[2];
 			/*
 			 * byte 4:   .    .    .    .  ay11 ay10 ay9  ay8
 			 * byte 5: ay7  ay6  ay5  ay4  ay3  ay2  ay1  ay0
 			 */
-			etd->prev_y = etd->y_max -
+			etd->mt[0].y = etd->y_max -
 				(((packet[4] & 0x0f) << 8) | packet[5]);
 			/*
 			 * wait for next packet
@@ -423,8 +427,8 @@  static void elantech_report_absolute_v3(struct psmouse *psmouse,
 		}
 
 		/* packet_type == PACKET_V3_TAIL */
-		x1 = etd->prev_x;
-		y1 = etd->prev_y;
+		x1 = etd->mt[0].x;
+		y1 = etd->mt[0].y;
 		x2 = ((packet[1] & 0x0f) << 8) | packet[2];
 		y2 = etd->y_max - (((packet[4] & 0x0f) << 8) | packet[5]);
 		break;
@@ -450,6 +454,129 @@  static void elantech_report_absolute_v3(struct psmouse *psmouse,
 	input_sync(dev);
 }
 
+static void elantech_mt_sync(struct psmouse *psmouse)
+{
+	struct input_dev *dev = psmouse->dev;
+	unsigned char *packet = psmouse->packet;
+
+	input_report_key(dev, BTN_LEFT, packet[0] & 0x01);
+	input_report_key(dev, BTN_RIGHT, packet[0] & 0x02);
+	input_mt_report_pointer_emulation(dev, true);
+	input_sync(dev);
+}
+
+static void process_packet_status(struct psmouse *psmouse)
+{
+	struct input_dev *dev = psmouse->dev;
+	unsigned char *packet = psmouse->packet;
+	unsigned fingers;
+	int i;
+
+	/* notify finger state change */
+	fingers = packet[1] & 0x1f;
+	for (i = 0; i < ETP_MAX_FINGERS; i++) {
+		if ((fingers & (1 << i)) == 0) {
+			input_mt_slot(dev, i);
+			input_mt_report_slot_state(dev, MT_TOOL_FINGER, false);
+		}
+	}
+
+	elantech_mt_sync(psmouse);
+}
+
+static void process_packet_head(struct psmouse *psmouse)
+{
+	struct input_dev *dev = psmouse->dev;
+	struct elantech_data *etd = psmouse->private;
+	unsigned char *packet = psmouse->packet;
+	int id = ((packet[3] & 0xe0) >> 5) - 1;
+	int pres, traces;
+
+	if (id < 0)
+		return;
+
+	etd->mt[id].x = ((packet[1] & 0x0f) << 8) | packet[2];
+	etd->mt[id].y = etd->y_max - (((packet[4] & 0x0f) << 8) | packet[5]);
+	pres = (packet[1] & 0xf0) | ((packet[4] & 0xf0) >> 4);
+	traces = (packet[0] & 0xf0) >> 4;
+
+	input_mt_slot(dev, id);
+	input_mt_report_slot_state(dev, MT_TOOL_FINGER, true);
+
+	input_report_abs(dev, ABS_MT_POSITION_X, etd->mt[id].x);
+	input_report_abs(dev, ABS_MT_POSITION_Y, etd->mt[id].y);
+	input_report_abs(dev, ABS_MT_PRESSURE, pres);
+	input_report_abs(dev, ABS_MT_TOUCH_MAJOR, traces * etd->width);
+	/* report this for backwards compatibility */
+	input_report_abs(dev, ABS_TOOL_WIDTH, traces);
+
+	elantech_mt_sync(psmouse);
+}
+
+static void process_packet_motion(struct psmouse *psmouse)
+{
+	struct input_dev *dev = psmouse->dev;
+	struct elantech_data *etd = psmouse->private;
+	unsigned char *packet = psmouse->packet;
+	int weight, delta_x1 = 0, delta_y1 = 0, delta_x2 = 0, delta_y2 = 0;
+	int id, sid;
+
+	id = ((packet[0] & 0xe0) >> 5) - 1;
+	if (id < 0)
+		return;
+
+	sid = ((packet[3] & 0xe0) >> 5) - 1;
+	weight = (packet[0] & 0x10) ? ETP_WEIGHT_VALUE : 1;
+	/*
+	 * Motion packets give us the delta of x, y values of specific fingers,
+	 * but in two's complement. Let the compiler do the conversion for us.
+	 * Also _enlarge_ the numbers to int, in case of overflow.
+	 */
+	delta_x1 = (signed char)packet[1];
+	delta_y1 = (signed char)packet[2];
+	delta_x2 = (signed char)packet[4];
+	delta_y2 = (signed char)packet[5];
+
+	etd->mt[id].x += delta_x1 * weight;
+	etd->mt[id].y -= delta_y1 * weight;
+	input_mt_slot(dev, id);
+	input_report_abs(dev, ABS_MT_POSITION_X, etd->mt[id].x);
+	input_report_abs(dev, ABS_MT_POSITION_Y, etd->mt[id].y);
+
+	if (sid >= 0) {
+		etd->mt[sid].x += delta_x2 * weight;
+		etd->mt[sid].y -= delta_y2 * weight;
+		input_mt_slot(dev, sid);
+		input_report_abs(dev, ABS_MT_POSITION_X, etd->mt[sid].x);
+		input_report_abs(dev, ABS_MT_POSITION_Y, etd->mt[sid].y);
+	}
+
+	elantech_mt_sync(psmouse);
+}
+
+static void elantech_report_absolute_v4(struct psmouse *psmouse,
+					int packet_type)
+{
+	switch (packet_type) {
+	case PACKET_V4_STATUS:
+		process_packet_status(psmouse);
+		break;
+
+	case PACKET_V4_HEAD:
+		process_packet_head(psmouse);
+		break;
+
+	case PACKET_V4_MOTION:
+		process_packet_motion(psmouse);
+		break;
+
+	case PACKET_UNKNOWN:
+	default:
+		/* impossible to get here */
+		break;
+	}
+}
+
 static int elantech_packet_check_v1(struct psmouse *psmouse)
 {
 	struct elantech_data *etd = psmouse->private;
@@ -504,7 +631,7 @@  static int elantech_packet_check_v2(struct psmouse *psmouse)
 
 /*
  * We check the constant bits to determine what packet type we get,
- * so packet checking is mandatory for v3 hardware.
+ * so packet checking is mandatory for v3 and later hardware.
  */
 static int elantech_packet_check_v3(struct psmouse *psmouse)
 {
@@ -527,6 +654,25 @@  static int elantech_packet_check_v3(struct psmouse *psmouse)
 	return PACKET_UNKNOWN;
 }
 
+static int elantech_packet_check_v4(struct psmouse *psmouse)
+{
+	unsigned char *packet = psmouse->packet;
+
+	if ((packet[0] & 0x0c) == 0x04 &&
+	    (packet[3] & 0x1f) == 0x11)
+		return PACKET_V4_HEAD;
+
+	if ((packet[0] & 0x0c) == 0x04 &&
+	    (packet[3] & 0x1f) == 0x12)
+		return PACKET_V4_MOTION;
+
+	if ((packet[0] & 0x0c) == 0x04 &&
+	    (packet[3] & 0x1f) == 0x10)
+		return PACKET_V4_STATUS;
+
+	return PACKET_UNKNOWN;
+}
+
 /*
  * Process byte stream from mouse and handle complete packets
  */
@@ -567,6 +713,14 @@  static psmouse_ret_t elantech_process_byte(struct psmouse *psmouse)
 
 		elantech_report_absolute_v3(psmouse, packet_type);
 		break;
+
+	case 4:
+		packet_type = elantech_packet_check_v4(psmouse);
+		if (packet_type == PACKET_UNKNOWN)
+			return PSMOUSE_BAD_DATA;
+
+		elantech_report_absolute_v4(psmouse, packet_type);
+		break;
 	}
 
 	return PSMOUSE_FULL_PACKET;
@@ -610,6 +764,13 @@  static int elantech_set_absolute_mode(struct psmouse *psmouse)
 			rc = -1;
 
 		break;
+
+	case 4:
+		etd->reg_07 = 0x01;
+		if (elantech_write_reg(psmouse, 0x07, etd->reg_07))
+			rc = -1;
+
+		goto skip_readback_reg_10; /* v4 has no reg 0x10 to read */
 	}
 
 	if (rc == 0) {
@@ -637,6 +798,7 @@  static int elantech_set_absolute_mode(struct psmouse *psmouse)
 		}
 	}
 
+ skip_readback_reg_10:
 	if (rc)
 		pr_err("failed to initialise registers.\n");
 
@@ -645,10 +807,11 @@  static int elantech_set_absolute_mode(struct psmouse *psmouse)
 
 static int set_range(struct psmouse *psmouse, unsigned int *x_min,
 		     unsigned int *y_min, unsigned int *x_max,
-		     unsigned int *y_max)
+		     unsigned int *y_max, unsigned int *width)
 {
 	struct elantech_data *etd = psmouse->private;
 	unsigned char param[3];
+	unsigned char traces = 0;
 	int i;
 
 	switch (etd->hw_version) {
@@ -677,12 +840,16 @@  static int set_range(struct psmouse *psmouse, unsigned int *x_min,
 		}
 		break;
 
+	case 4:
+		traces = etd->capabilities[1];
+		/* pass through */
 	case 3:
 		if (synaptics_send_cmd(psmouse, ETP_FW_ID_QUERY, param))
 			return -1;
 
 		*x_max = (0x0f & param[0]) << 8 | param[1];
 		*y_max = (0xf0 & param[0]) << 4 | param[2];
+		*width = *x_max / (traces - 1);
 		break;
 	}
 
@@ -696,9 +863,9 @@  static int elantech_set_input_params(struct psmouse *psmouse)
 {
 	struct input_dev *dev = psmouse->dev;
 	struct elantech_data *etd = psmouse->private;
-	unsigned int x_min = 0, y_min = 0, x_max = 0, y_max = 0;
+	unsigned int x_min = 0, y_min = 0, x_max = 0, y_max = 0, width = 0;
 
-	if (set_range(psmouse, &x_min, &y_min, &x_max, &y_max))
+	if (set_range(psmouse, &x_min, &y_min, &x_max, &y_max, &width))
 		return -1;
 
 	__set_bit(EV_KEY, dev->evbit);
@@ -742,9 +909,37 @@  static int elantech_set_input_params(struct psmouse *psmouse)
 		input_set_abs_params(dev, ABS_MT_POSITION_X, x_min, x_max, 0, 0);
 		input_set_abs_params(dev, ABS_MT_POSITION_Y, y_min, y_max, 0, 0);
 		break;
+
+	case 4:
+		__set_bit(BTN_TOOL_QUADTAP, dev->keybit);
+		/* For X to recognize me as touchpad. */
+		input_set_abs_params(dev, ABS_X, x_min, x_max, 0, 0);
+		input_set_abs_params(dev, ABS_Y, y_min, y_max, 0, 0);
+		/*
+		 * range of pressure and width is the same as v2,
+		 * report ABS_PRESSURE, ABS_TOOL_WIDTH for compatibility.
+		 */
+		input_set_abs_params(dev, ABS_PRESSURE, ETP_PMIN_V2,
+				     ETP_PMAX_V2, 0, 0);
+		input_set_abs_params(dev, ABS_TOOL_WIDTH, ETP_WMIN_V2,
+				     ETP_WMAX_V2, 0, 0);
+		/* Multitouch capable pad, up to 5 fingers. */
+		input_mt_init_slots(dev, ETP_MAX_FINGERS);
+		input_set_abs_params(dev, ABS_MT_POSITION_X, x_min, x_max, 0, 0);
+		input_set_abs_params(dev, ABS_MT_POSITION_Y, y_min, y_max, 0, 0);
+		input_set_abs_params(dev, ABS_MT_PRESSURE, ETP_PMIN_V2,
+				     ETP_PMAX_V2, 0, 0);
+		/*
+		 * The firmware reports how many trace lines the finger spans,
+		 * convert to surface unit as Protocol-B requires.
+		 */
+		input_set_abs_params(dev, ABS_MT_TOUCH_MAJOR, 0,
+				     ETP_WMAX_V2 * width, 0, 0);
+		break;
 	}
 
 	etd->y_max = y_max;
+	etd->width = width;
 
 	return 0;
 }
@@ -816,6 +1011,7 @@  static ssize_t elantech_set_int_attr(struct psmouse *psmouse,
 			    elantech_show_int_attr,			\
 			    elantech_set_int_attr)
 
+ELANTECH_INT_ATTR(reg_07, 0x07);
 ELANTECH_INT_ATTR(reg_10, 0x10);
 ELANTECH_INT_ATTR(reg_11, 0x11);
 ELANTECH_INT_ATTR(reg_20, 0x20);
@@ -829,6 +1025,7 @@  ELANTECH_INT_ATTR(debug, 0);
 ELANTECH_INT_ATTR(paritycheck, 0);
 
 static struct attribute *elantech_attrs[] = {
+	&psmouse_attr_reg_07.dattr.attr,
 	&psmouse_attr_reg_10.dattr.attr,
 	&psmouse_attr_reg_11.dattr.attr,
 	&psmouse_attr_reg_20.dattr.attr,
@@ -957,12 +1154,16 @@  static int elantech_reconnect(struct psmouse *psmouse)
  */
 static int elantech_set_properties(struct elantech_data *etd)
 {
+	int ver = (etd->fw_version & 0x0f0000) >> 16;
+
 	if (etd->fw_version < 0x020030 || etd->fw_version == 0x020600)
 		etd->hw_version = 1;
 	else if (etd->fw_version < 0x150600)
 		etd->hw_version = 2;
-	else if ((etd->fw_version & 0x0f0000) >> 16 == 5)
+	else if (ver == 5)
 		etd->hw_version = 3;
+	else if (ver == 6)
+		etd->hw_version = 4;
 	else
 		return -1;
 
diff --git a/drivers/input/mouse/elantech.h b/drivers/input/mouse/elantech.h
index 236c33c..7ecaef0 100644
--- a/drivers/input/mouse/elantech.h
+++ b/drivers/input/mouse/elantech.h
@@ -82,14 +82,37 @@ 
 #define ETP_WMAX_V2			15
 
 /*
- * v3 hardware has 2 kinds of packet types.
+ * v3 hardware has 2 kinds of packet types,
+ * v4 hardware has 3.
  */
 #define PACKET_UNKNOWN			0x01
 #define PACKET_DEBOUNCE			0x02
 #define PACKET_V3_HEAD			0x03
 #define PACKET_V3_TAIL			0x04
+#define PACKET_V4_HEAD			0x05
+#define PACKET_V4_MOTION		0x06
+#define PACKET_V4_STATUS		0x07
+
+/*
+ * track up to 5 fingers for v4 hardware
+ */
+#define ETP_MAX_FINGERS			5
+
+/*
+ * weight value for v4 hardware
+ */
+#define ETP_WEIGHT_VALUE		5
+
+/*
+ * The base position for one finger, v4 hardware
+ */
+struct finger_pos {
+	unsigned int x;
+	unsigned int y;
+};
 
 struct elantech_data {
+	unsigned char reg_07;
 	unsigned char reg_10;
 	unsigned char reg_11;
 	unsigned char reg_20;
@@ -108,8 +131,8 @@  struct elantech_data {
 	unsigned int fw_version;
 	unsigned int single_finger_reports;
 	unsigned int y_max;
-	unsigned int prev_x;
-	unsigned int prev_y;
+	unsigned int width;
+	struct finger_pos mt[ETP_MAX_FINGERS];
 	unsigned char parity[256];
 };