diff mbox series

Transparent pass-though mode for synaptics touchpad with trackpoint

Message ID 71d9dc66-9576-c26f-c9d9-129217f50255@gmail.com (mailing list archive)
State New, archived
Headers show
Series Transparent pass-though mode for synaptics touchpad with trackpoint | expand

Commit Message

Miroslav Bendík Feb. 26, 2022, 5:48 p.m. UTC
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@gmail.com/

This patch can make trackpoint experience on AMD machines great again.

Comments

Miroslav Bendík May 7, 2022, 3:36 a.m. UTC | #1
Hello,

this is second version of patch with transparent mode for synaptics 
trackpoint.

This version can fix synchronization problems and reset is forwarded 
from psmouse-base to parent device instead of delayed work.
diff mbox series

Patch

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