Transparent pass-though mode for synaptics touchpad with trackpoint

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

 



Hello,
this patch implements transparent pass-though (or GlassPass) mode for synaptics touchpad.

Transparent mode is documented in Synaptics PS/2 TouchPad Interfacing Guide. Touchpad with enabled transparent mode will forward packets from guest device (trackpoint) directly to host. If this mode is activated, touchpad cannot be detected until OS sends 0xe7, 0xe6 commands (in exact order). Change is semi-persistent on my machine - touchpad is not visible after reboot, in BIOS and in Lenovo diagnostic tool. After full shutdown and boot, touchpad is visible.

Explanation of some decisions:

This can be implemented simply by setting bit 5 in synaptics_set_mode and reconnecting serio device. It's simple, but touchpad can't be enabled after this operation, because synaptics device will not exist after reconnect. Mode can't be changed on fly and device will not work after spontaneous reset.

My patch is more complicated, because it don't remove master synaptics device. Incoming packets and write commands are forwarded to pass-though device. Transparent mode can be enabled/disabled on fly by writing 1/0 to transparent_mode file of master device. Module has new parameter synaptics_ps2_transparent_mode, which can be used to set transparent mode on load. Module detects sequence 0xaa, 0x00 (received from device after spontaneous reset) and enables transparent mode after reset automatically.

Why:

On my machine reporting rate drops bellow 40Hz. Maximal reporting rate is 80Hz according documentation, but if i am touching touchpad (i am using this place for hand rest), rate is divided to 2 devices, both 40Hz at maximum rate. Low rate remains long after last touch. Example video with high speed camera: https://youtu.be/1AlyjY-cJ0I (240fps). With transparent mode, rate is stable - 100Hz.

I have AMD machine. RMI4 mode is not supported because PIIX4 don't implement host notify and after my patch with notify support, synaptics sends 1000 attention signals per second with no reason (checked using logic analyzer). This drains battery very fast. More informations here: https://lore.kernel.org/all/5105b392-dee9-85fb-eeba-75c7c951d295@xxxxxxxxx/

This patch can make trackpoint experience on AMD machines great again.
diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
index ffad14280..04f0b3ca2 100644
--- a/drivers/input/mouse/synaptics.c
+++ b/drivers/input/mouse/synaptics.c
@@ -506,6 +506,12 @@ static const struct min_max_quirk min_max_pnpid_table[] = {
 	{ }
 };
 
+
+static bool synaptics_ps2_transparent_mode = false;
+module_param_named(synaptics_ps2_transparent_mode, synaptics_ps2_transparent_mode, bool, 0644);
+MODULE_PARM_DESC(synaptics_ps2_transparent_mode, "Enable transparent pass-through mode from PS2 guest to host.");
+
+
 /*****************************************************************************
  *	Synaptics communications functions
  ****************************************************************************/
@@ -625,12 +631,44 @@ static void synaptics_set_rate(struct psmouse *psmouse, unsigned int rate)
 /*****************************************************************************
  *	Synaptics pass-through PS/2 port support
  ****************************************************************************/
+static int synaptics_enter_transparent_mode(struct psmouse *psmouse)
+{
+	struct synaptics_data *priv = psmouse->private;
+
+	priv->mode |= SYN_BIT_TRANSPARENT_MODE;
+
+	if (synaptics_mode_cmd(psmouse, priv->mode))
+		return -EIO;
+
+	return 0;
+}
+
+static int synaptics_exit_transparent_mode(struct psmouse *psmouse)
+{
+	struct synaptics_data *priv = psmouse->private;
+
+	/* send scaling 2:1, 1:1 to exit transparent mode */
+	if (ps2_command(&psmouse->ps2dev, NULL, 0x00e7))
+		return -EIO;
+	if (ps2_command(&psmouse->ps2dev, NULL, 0x00e6))
+		return -EIO;
+
+	priv->mode &= ~SYN_BIT_TRANSPARENT_MODE;
+
+	return 0;
+}
+
 static int synaptics_pt_write(struct serio *serio, u8 c)
 {
 	struct psmouse *parent = serio_get_drvdata(serio->parent);
+	struct synaptics_data *priv = parent->private;
+
 	u8 rate_param = SYN_PS_CLIENT_CMD; /* indicates that we want pass-through port */
 	int error;
 
+	if (priv->transparent_mode)
+		return parent->ps2dev.serio->write(parent->ps2dev.serio, c);
+
 	error = ps2_sliced_command(&parent->ps2dev, c);
 	if (error)
 		return error;
@@ -642,6 +680,8 @@ static int synaptics_pt_write(struct serio *serio, u8 c)
 	return 0;
 }
 
+static void synaptics_update_protocol_handler(struct psmouse *psmouse);
+
 static int synaptics_pt_start(struct serio *serio)
 {
 	struct psmouse *parent = serio_get_drvdata(serio->parent);
@@ -651,6 +691,8 @@ static int synaptics_pt_start(struct serio *serio)
 	priv->pt_port = serio;
 	serio_continue_rx(parent->ps2dev.serio);
 
+	synaptics_update_protocol_handler(parent);
+
 	return 0;
 }
 
@@ -662,6 +704,8 @@ static void synaptics_pt_stop(struct serio *serio)
 	serio_pause_rx(parent->ps2dev.serio);
 	priv->pt_port = NULL;
 	serio_continue_rx(parent->ps2dev.serio);
+
+	synaptics_update_protocol_handler(parent);
 }
 
 static int synaptics_is_pt_packet(u8 *buf)
@@ -689,6 +733,10 @@ static void synaptics_pt_activate(struct psmouse *psmouse)
 	struct synaptics_data *priv = psmouse->private;
 	struct psmouse *child = serio_get_drvdata(priv->pt_port);
 
+	/* don't need change mode if transparent mode is active */
+	if (priv->transparent_mode)
+		return;
+
 	/* adjust the touchpad to child's choice of protocol */
 	if (child) {
 		if (child->pktsize == 4)
@@ -1228,6 +1276,30 @@ static psmouse_ret_t synaptics_process_byte(struct psmouse *psmouse)
 		PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA;
 }
 
+static psmouse_ret_t transparent_process_byte(struct psmouse *psmouse)
+{
+	struct synaptics_data *priv = psmouse->private;
+	struct psmouse *child;
+
+	if (!priv->pt_port)
+		return PSMOUSE_BAD_DATA;
+
+	serio_interrupt(priv->pt_port, psmouse->packet[psmouse->pktcnt - 1], 0);
+
+	// spontaneous reset
+	if (unlikely(
+	    psmouse->packet[0] == PSMOUSE_RET_BAT &&
+	    psmouse->packet[1] == PSMOUSE_RET_BAT &&
+	    psmouse->pktcnt <= 2)) {
+		psmouse_queue_work(psmouse, &priv->reset_work, 0);
+	}
+
+	child = serio_get_drvdata(priv->pt_port);
+	if (child && psmouse->pktcnt >= child->pktsize)
+		return PSMOUSE_FULL_PACKET;
+	return PSMOUSE_GOOD_DATA;
+}
+
 /*****************************************************************************
  *	Driver initialization/cleanup functions
  ****************************************************************************/
@@ -1400,6 +1472,52 @@ PSMOUSE_DEFINE_ATTR(disable_gesture, S_IWUSR | S_IRUGO, NULL,
 		    synaptics_show_disable_gesture,
 		    synaptics_set_disable_gesture);
 
+static ssize_t synaptics_show_transparent_mode(struct psmouse *psmouse,
+					      void *data, char *buf)
+{
+	struct synaptics_data *priv = psmouse->private;
+
+	return sprintf(buf, "%c\n", priv->transparent_mode ? '1' : '0');
+}
+
+static ssize_t synaptics_set_transparent_mode(struct psmouse *psmouse,
+					     void *data, const char *buf,
+					     size_t len)
+{
+	struct synaptics_data *priv = psmouse->private;
+	unsigned int value;
+	int err;
+
+	err = kstrtouint(buf, 10, &value);
+	if (err)
+		return err;
+
+	if (value > 1)
+		return -EINVAL;
+
+	if (value == priv->transparent_mode)
+		return len;
+
+	priv->transparent_mode = value;
+
+	synaptics_update_protocol_handler(psmouse);
+
+	if (value) {
+		if (synaptics_enter_transparent_mode(psmouse))
+			return -EIO;
+	}
+	else {
+		if (synaptics_exit_transparent_mode(psmouse))
+			return -EIO;
+	}
+
+	return len;
+}
+
+PSMOUSE_DEFINE_ATTR(transparent_mode, S_IWUSR | S_IRUGO, NULL,
+		    synaptics_show_transparent_mode,
+		    synaptics_set_transparent_mode);
+
 static void synaptics_disconnect(struct psmouse *psmouse)
 {
 	struct synaptics_data *priv = psmouse->private;
@@ -1410,10 +1528,16 @@ static void synaptics_disconnect(struct psmouse *psmouse)
 	 */
 	psmouse_smbus_cleanup(psmouse);
 
+	if (priv->transparent_mode)
+		synaptics_exit_transparent_mode(psmouse);
+
 	if (!priv->absolute_mode &&
 			SYN_ID_DISGEST_SUPPORTED(priv->info.identity))
 		device_remove_file(&psmouse->ps2dev.serio->dev,
 				   &psmouse_attr_disable_gesture.dattr);
+	if (SYN_CAP_PASS_THROUGH(priv->info.capabilities))
+		device_remove_file(&psmouse->ps2dev.serio->dev,
+				   &psmouse_attr_transparent_mode.dattr);
 
 	synaptics_reset(psmouse);
 	kfree(priv);
@@ -1440,8 +1564,15 @@ static int synaptics_reconnect(struct psmouse *psmouse)
 			 */
 			ssleep(1);
 		}
-		ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETID);
-		error = synaptics_detect(psmouse, 0);
+		if (priv->transparent_mode) {
+			error = synaptics_enter_transparent_mode(psmouse);
+			if (!error)
+				return 0;
+		}
+		else {
+			ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETID);
+			error = synaptics_detect(psmouse, 0);
+		}
 	} while (error && ++retry < 3);
 
 	if (error)
@@ -1552,6 +1683,45 @@ void __init synaptics_module_init(void)
 	cr48_profile_sensor = dmi_check_system(cr48_dmi_table);
 }
 
+static void synaptics_update_protocol_handler(struct psmouse *psmouse)
+{
+	struct synaptics_data *priv = psmouse->private;
+	struct serio *pt_port = priv->pt_port;
+
+	bool absolute_mode = priv->absolute_mode;
+	bool transparent_mode = priv->transparent_mode;
+
+	if (transparent_mode && pt_port) {
+		psmouse->protocol_handler = transparent_process_byte;
+	}
+	else {
+		if (absolute_mode) {
+			psmouse->protocol_handler = synaptics_process_byte;
+			psmouse->pktsize = 6;
+		} else {
+			/* Relative mode follows standard PS/2 mouse protocol */
+			psmouse->protocol_handler = psmouse_process_byte;
+			psmouse->pktsize = 3;
+		}
+	}
+}
+
+static void pamouse_handle_spontaneous_reset(struct work_struct *work)
+{
+	struct synaptics_data *priv = container_of(work, struct synaptics_data, reset_work.work);
+	struct psmouse *child = serio_get_drvdata(priv->pt_port);
+	struct psmouse *psmouse;
+
+	if (!child || !child->ps2dev.serio->parent)
+		return;
+
+	psmouse = serio_get_drvdata(child->ps2dev.serio->parent);
+	if (psmouse) {
+		psmouse_err(psmouse, "spontaneous reset detected, reconnecting\n");
+		serio_reconnect(psmouse->ps2dev.serio);
+	}
+}
+
 static int synaptics_init_ps2(struct psmouse *psmouse,
 			      struct synaptics_device_info *info,
 			      bool absolute_mode)
@@ -1570,6 +1740,8 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
 	if (SYN_ID_DISGEST_SUPPORTED(info->identity))
 		priv->disable_gesture = true;
 
+	INIT_DELAYED_WORK(&priv->reset_work, pamouse_handle_spontaneous_reset);
+
 	/*
 	 * Unfortunately ForcePad capability is not exported over PS/2,
 	 * so we have to resort to checking PNP IDs.
@@ -1610,14 +1782,7 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
 	psmouse->model = ((info->model_id & 0x00ff0000) >> 8) |
 			  (info->model_id & 0x000000ff);
 
-	if (absolute_mode) {
-		psmouse->protocol_handler = synaptics_process_byte;
-		psmouse->pktsize = 6;
-	} else {
-		/* Relative mode follows standard PS/2 mouse protocol */
-		psmouse->protocol_handler = psmouse_process_byte;
-		psmouse->pktsize = 3;
-	}
+	synaptics_update_protocol_handler(psmouse);
 
 	psmouse->set_rate = synaptics_set_rate;
 	psmouse->disconnect = synaptics_disconnect;
@@ -1652,6 +1817,24 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
 		}
 	}
 
+	if (SYN_CAP_PASS_THROUGH(info->capabilities)) {
+		err = device_create_file(&psmouse->ps2dev.serio->dev,
+					 &psmouse_attr_transparent_mode.dattr);
+		if (err) {
+			psmouse_err(psmouse,
+				    "Failed to create transparent_mode attribute (%d)",
+				    err);
+			goto init_fail;
+		}
+
+		if (synaptics_ps2_transparent_mode) {
+			priv->transparent_mode = true;
+			synaptics_update_protocol_handler(psmouse);
+			synaptics_enter_transparent_mode(psmouse);
+		}
+	}
+
+
 	return 0;
 
  init_fail:
diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h
index 08533d1b1..1a33d65fa 100644
--- a/drivers/input/mouse/synaptics.h
+++ b/drivers/input/mouse/synaptics.h
@@ -24,6 +24,7 @@
 /* synatics modes */
 #define SYN_BIT_ABSOLUTE_MODE		BIT(7)
 #define SYN_BIT_HIGH_RATE		BIT(6)
+#define SYN_BIT_TRANSPARENT_MODE	BIT(5)
 #define SYN_BIT_SLEEP_MODE		BIT(3)
 #define SYN_BIT_DISABLE_GESTURE		BIT(2)
 #define SYN_BIT_FOUR_BYTE_CLIENT	BIT(1)
@@ -186,8 +187,10 @@ struct synaptics_data {
 
 	bool absolute_mode;			/* run in Absolute mode */
 	bool disable_gesture;			/* disable gestures */
+	bool transparent_mode;			/* pass packets directly from guest */
 
 	struct serio *pt_port;			/* Pass-through serio port */
+	struct delayed_work reset_work;		/* Initiate device reset */
 
 	/*
 	 * Last received Advanced Gesture Mode (AGM) packet. An AGM packet

[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux