Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a2418fc4 authored by Ulrik De Bie's avatar Ulrik De Bie Committed by Dmitry Torokhov
Browse files

Input: elantech - add support for trackpoint found on some v3 models



Some elantech v3 touchpad equipped laptops also have a trackpoint, before
this commit, these give sync errors. With this patch, the trackpoint is
provided as another input device: 'Elantech PS/2 TrackPoint'

The patch will also output messages that do not follow the expected pattern.
In the mean time I've seen 2 unknown packets occasionally:
0x04 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00
0x00 , 0x00 , 0x00 , 0x02 , 0x00 , 0x00
I don't know what those are for, but they can be safely ignored.

Currently all packets that are not known to v3 touchpad and where
packet[3] (the fourth byte) lowest nibble is 6 are now recognized as
PACKET_TRACKPOINT and processed by the new elantech_report_trackpoint.

This has been verified to work on a laptop Lenovo L530 where the
touchpad/trackpoint combined identify themselves as:
psmouse serio1: elantech: assuming hardware version 3 (with firmware version 0x350f02)
psmouse serio1: elantech: Synaptics capabilities query result 0xb9, 0x15, 0x0c.

Reviewed-by: default avatarDavid Herrmann <dh.herrmann@gmail.com>
Reviewed-by: default avatarHans de Goede <hdegoede@redhat.com>
Signed-off-by: default avatarUlrik De Bie <ulrik.debie-os@e2big.org>
Signed-off-by: default avatarDmitry Torokhov <dmitry.torokhov@gmail.com>
parent ac84eba2
Loading
Loading
Loading
Loading
+127 −9
Original line number Original line Diff line number Diff line
@@ -18,6 +18,7 @@
#include <linux/input/mt.h>
#include <linux/input/mt.h>
#include <linux/serio.h>
#include <linux/serio.h>
#include <linux/libps2.h>
#include <linux/libps2.h>
#include <asm/unaligned.h>
#include "psmouse.h"
#include "psmouse.h"
#include "elantech.h"
#include "elantech.h"


@@ -403,6 +404,68 @@ static void elantech_report_absolute_v2(struct psmouse *psmouse)
	input_sync(dev);
	input_sync(dev);
}
}


static void elantech_report_trackpoint(struct psmouse *psmouse,
				       int packet_type)
{
	/*
	 * byte 0:  0   0  sx  sy   0   M   R   L
	 * byte 1:~sx   0   0   0   0   0   0   0
	 * byte 2:~sy   0   0   0   0   0   0   0
	 * byte 3:  0   0 ~sy ~sx   0   1   1   0
	 * byte 4: x7  x6  x5  x4  x3  x2  x1  x0
	 * byte 5: y7  y6  y5  y4  y3  y2  y1  y0
	 *
	 * x and y are written in two's complement spread
	 * over 9 bits with sx/sy the relative top bit and
	 * x7..x0 and y7..y0 the lower bits.
	 * The sign of y is opposite to what the input driver
	 * expects for a relative movement
	 */

	struct elantech_data *etd = psmouse->private;
	struct input_dev *tp_dev = etd->tp_dev;
	unsigned char *packet = psmouse->packet;
	int x, y;
	u32 t;

	if (dev_WARN_ONCE(&psmouse->ps2dev.serio->dev,
			  !tp_dev,
			  psmouse_fmt("Unexpected trackpoint message\n"))) {
		if (etd->debug == 1)
			elantech_packet_dump(psmouse);
		return;
	}

	t = get_unaligned_le32(&packet[0]);

	switch (t & ~7U) {
	case 0x06000030U:
	case 0x16008020U:
	case 0x26800010U:
	case 0x36808000U:
		x = packet[4] - (int)((packet[1]^0x80) << 1);
		y = (int)((packet[2]^0x80) << 1) - packet[5];

		input_report_key(tp_dev, BTN_LEFT, packet[0] & 0x01);
		input_report_key(tp_dev, BTN_RIGHT, packet[0] & 0x02);
		input_report_key(tp_dev, BTN_MIDDLE, packet[0] & 0x04);

		input_report_rel(tp_dev, REL_X, x);
		input_report_rel(tp_dev, REL_Y, y);

		input_sync(tp_dev);

		break;

	default:
		/* Dump unexpected packet sequences if debug=1 (default) */
		if (etd->debug == 1)
			elantech_packet_dump(psmouse);

		break;
	}
}

/*
/*
 * Interpret complete data packets and report absolute mode input events for
 * Interpret complete data packets and report absolute mode input events for
 * hardware version 3. (12 byte packets for two fingers)
 * hardware version 3. (12 byte packets for two fingers)
@@ -715,6 +778,8 @@ static int elantech_packet_check_v3(struct psmouse *psmouse)


		if ((packet[0] & 0x0c) == 0x0c && (packet[3] & 0xce) == 0x0c)
		if ((packet[0] & 0x0c) == 0x0c && (packet[3] & 0xce) == 0x0c)
			return PACKET_V3_TAIL;
			return PACKET_V3_TAIL;
		if ((packet[3] & 0x0f) == 0x06)
			return PACKET_TRACKPOINT;
	}
	}


	return PACKET_UNKNOWN;
	return PACKET_UNKNOWN;
@@ -791,15 +856,24 @@ static psmouse_ret_t elantech_process_byte(struct psmouse *psmouse)


	case 3:
	case 3:
		packet_type = elantech_packet_check_v3(psmouse);
		packet_type = elantech_packet_check_v3(psmouse);
		switch (packet_type) {
		case PACKET_UNKNOWN:
			return PSMOUSE_BAD_DATA;

		case PACKET_DEBOUNCE:
			/* ignore debounce */
			/* ignore debounce */
		if (packet_type == PACKET_DEBOUNCE)
			break;
			return PSMOUSE_FULL_PACKET;


		if (packet_type == PACKET_UNKNOWN)
		case PACKET_TRACKPOINT:
			return PSMOUSE_BAD_DATA;
			elantech_report_trackpoint(psmouse, packet_type);
			break;


		default:
			elantech_report_absolute_v3(psmouse, packet_type);
			elantech_report_absolute_v3(psmouse, packet_type);
			break;
			break;
		}

		break;


	case 4:
	case 4:
		packet_type = elantech_packet_check_v4(psmouse);
		packet_type = elantech_packet_check_v4(psmouse);
@@ -1018,8 +1092,10 @@ static int elantech_get_resolution_v4(struct psmouse *psmouse,
 * Asus UX31               0x361f00        20, 15, 0e      clickpad
 * Asus UX31               0x361f00        20, 15, 0e      clickpad
 * Asus UX32VD             0x361f02        00, 15, 0e      clickpad
 * Asus UX32VD             0x361f02        00, 15, 0e      clickpad
 * Avatar AVIU-145A2       0x361f00        ?               clickpad
 * Avatar AVIU-145A2       0x361f00        ?               clickpad
 * Fujitsu H730            0x570f00        c0, 14, 0c      3 hw buttons (**)
 * Gigabyte U2442          0x450f01        58, 17, 0c      2 hw buttons
 * Gigabyte U2442          0x450f01        58, 17, 0c      2 hw buttons
 * Lenovo L430             0x350f02        b9, 15, 0c      2 hw buttons (*)
 * Lenovo L430             0x350f02        b9, 15, 0c      2 hw buttons (*)
 * Lenovo L530             0x350f02        b9, 15, 0c      2 hw buttons (*)
 * Samsung NF210           0x150b00        78, 14, 0a      2 hw buttons
 * Samsung NF210           0x150b00        78, 14, 0a      2 hw buttons
 * Samsung NP770Z5E        0x575f01        10, 15, 0f      clickpad
 * Samsung NP770Z5E        0x575f01        10, 15, 0f      clickpad
 * Samsung NP700Z5B        0x361f06        21, 15, 0f      clickpad
 * Samsung NP700Z5B        0x361f06        21, 15, 0f      clickpad
@@ -1029,6 +1105,8 @@ static int elantech_get_resolution_v4(struct psmouse *psmouse,
 * Samsung RF710           0x450f00        ?               2 hw buttons
 * Samsung RF710           0x450f00        ?               2 hw buttons
 * System76 Pangolin       0x250f01        ?               2 hw buttons
 * System76 Pangolin       0x250f01        ?               2 hw buttons
 * (*) + 3 trackpoint buttons
 * (*) + 3 trackpoint buttons
 * (**) + 0 trackpoint buttons
 * Note: Lenovo L430 and Lenovo L430 have the same fw_version/caps
 */
 */
static void elantech_set_buttonpad_prop(struct psmouse *psmouse)
static void elantech_set_buttonpad_prop(struct psmouse *psmouse)
{
{
@@ -1324,6 +1402,10 @@ int elantech_detect(struct psmouse *psmouse, bool set_properties)
 */
 */
static void elantech_disconnect(struct psmouse *psmouse)
static void elantech_disconnect(struct psmouse *psmouse)
{
{
	struct elantech_data *etd = psmouse->private;

	if (etd->tp_dev)
		input_unregister_device(etd->tp_dev);
	sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj,
	sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj,
			   &elantech_attr_group);
			   &elantech_attr_group);
	kfree(psmouse->private);
	kfree(psmouse->private);
@@ -1438,8 +1520,10 @@ static int elantech_set_properties(struct elantech_data *etd)
int elantech_init(struct psmouse *psmouse)
int elantech_init(struct psmouse *psmouse)
{
{
	struct elantech_data *etd;
	struct elantech_data *etd;
	int i, error;
	int i;
	int error = -EINVAL;
	unsigned char param[3];
	unsigned char param[3];
	struct input_dev *tp_dev;


	psmouse->private = etd = kzalloc(sizeof(struct elantech_data), GFP_KERNEL);
	psmouse->private = etd = kzalloc(sizeof(struct elantech_data), GFP_KERNEL);
	if (!etd)
	if (!etd)
@@ -1498,15 +1582,49 @@ int elantech_init(struct psmouse *psmouse)
		goto init_fail;
		goto init_fail;
	}
	}


	/* The MSB indicates the presence of the trackpoint */
	if ((etd->capabilities[0] & 0x80) == 0x80) {
		tp_dev = input_allocate_device();

		if (!tp_dev) {
			error = -ENOMEM;
			goto init_fail_tp_alloc;
		}

		etd->tp_dev = tp_dev;
		snprintf(etd->tp_phys, sizeof(etd->tp_phys), "%s/input1",
			psmouse->ps2dev.serio->phys);
		tp_dev->phys = etd->tp_phys;
		tp_dev->name = "Elantech PS/2 TrackPoint";
		tp_dev->id.bustype = BUS_I8042;
		tp_dev->id.vendor  = 0x0002;
		tp_dev->id.product = PSMOUSE_ELANTECH;
		tp_dev->id.version = 0x0000;
		tp_dev->dev.parent = &psmouse->ps2dev.serio->dev;
		tp_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
		tp_dev->relbit[BIT_WORD(REL_X)] =
			BIT_MASK(REL_X) | BIT_MASK(REL_Y);
		tp_dev->keybit[BIT_WORD(BTN_LEFT)] =
			BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_MIDDLE) |
			BIT_MASK(BTN_RIGHT);
		error = input_register_device(etd->tp_dev);
		if (error < 0)
			goto init_fail_tp_reg;
	}

	psmouse->protocol_handler = elantech_process_byte;
	psmouse->protocol_handler = elantech_process_byte;
	psmouse->disconnect = elantech_disconnect;
	psmouse->disconnect = elantech_disconnect;
	psmouse->reconnect = elantech_reconnect;
	psmouse->reconnect = elantech_reconnect;
	psmouse->pktsize = etd->hw_version > 1 ? 6 : 4;
	psmouse->pktsize = etd->hw_version > 1 ? 6 : 4;


	return 0;
	return 0;

 init_fail_tp_reg:
	input_free_device(tp_dev);
 init_fail_tp_alloc:
	sysfs_remove_group(&psmouse->ps2dev.serio->dev.kobj,
			   &elantech_attr_group);
 init_fail:
 init_fail:
	psmouse_reset(psmouse);
	psmouse_reset(psmouse);
	kfree(etd);
	kfree(etd);
	return -1;
	return error;
}
}
+3 −0
Original line number Original line Diff line number Diff line
@@ -94,6 +94,7 @@
#define PACKET_V4_HEAD			0x05
#define PACKET_V4_HEAD			0x05
#define PACKET_V4_MOTION		0x06
#define PACKET_V4_MOTION		0x06
#define PACKET_V4_STATUS		0x07
#define PACKET_V4_STATUS		0x07
#define PACKET_TRACKPOINT		0x08


/*
/*
 * track up to 5 fingers for v4 hardware
 * track up to 5 fingers for v4 hardware
@@ -114,6 +115,8 @@ struct finger_pos {
};
};


struct elantech_data {
struct elantech_data {
	struct input_dev *tp_dev;	/* Relative device for trackpoint */
	char tp_phys[32];
	unsigned char reg_07;
	unsigned char reg_07;
	unsigned char reg_10;
	unsigned char reg_10;
	unsigned char reg_11;
	unsigned char reg_11;