diff mbox series

Input: i8042 - disable non-wakeup port during suspend

Message ID 20210524103627.4235-1-hui.wang@canonical.com (mailing list archive)
State New, archived
Headers show
Series Input: i8042 - disable non-wakeup port during suspend | expand

Commit Message

Hui Wang May 24, 2021, 10:36 a.m. UTC
We found an suspend/resume issue on many Lenovo and Dell laptops,
those machines support s2idle and have i8042 kbd, i8042 trackpoint and
i8042 touchpad, the current driver will enable the wakeup on kbd if
the s2idle is the current mode. If we suspend the system, then press
any key on the keyboard, the system will be resumed as expected. But
if we press trackpoint or touchpad ahead of kbd, the kbd can't wakeup
the system anymore, need to press PWR_BUTTON to resume the system.

Similarly, if we enable wakeup on i8042 trackpoint and disable wakeup
on i8042 kbd via sysfs interface, and after suspend, we press the kbd
ahead of trackpoint, the trackpoint can't wakeup the system too.

It is highly possible that this is a defect in the EC firmware, but
this issue exists on most of the laptops (maybe all laptops with i8042
kbd, i8042 trackpoint and i8042 touchpad), it is not practical to
fix this issue by upgrading the firmware.

Let's workaround this issue in the kernel driver. So far, this
issue is only reproduced on KBD port + AUX port, there is no issue
reported on MUX port yet, we only handle these 2 ports here. If
one of them is enabled for wakeup, we temporarily disable the port
which is not enabled for wakeup. If both of them are enabled or
disabled for wakeup, do nothing.

Signed-off-by: Hui Wang <hui.wang@canonical.com>
---
 drivers/input/serio/i8042.c | 66 +++++++++++++++++++++++++++++++++++--
 1 file changed, 64 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c
index abae23af0791..90a7f042628f 100644
--- a/drivers/input/serio/i8042.c
+++ b/drivers/input/serio/i8042.c
@@ -605,6 +605,25 @@  static irqreturn_t i8042_interrupt(int irq, void *dev_id)
 	return IRQ_RETVAL(ret);
 }
 
+/*
+ * i8042_disable_kbd_port disables keyboard port on chip
+ */
+
+static int i8042_disable_kbd_port(void)
+{
+	i8042_ctr |= I8042_CTR_KBDDIS;
+	i8042_ctr &= ~I8042_CTR_KBDINT;
+
+	if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
+		i8042_ctr |= I8042_CTR_KBDINT;
+		i8042_ctr &= ~I8042_CTR_KBDDIS;
+		pr_err("Failed to disable KBD port\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
 /*
  * i8042_enable_kbd_port enables keyboard port on chip
  */
@@ -624,6 +643,25 @@  static int i8042_enable_kbd_port(void)
 	return 0;
 }
 
+/*
+ * i8042_disable_aux_port disables AUX (mouse) port on chip
+ */
+
+static int i8042_disable_aux_port(void)
+{
+	i8042_ctr |= I8042_CTR_AUXDIS;
+	i8042_ctr &= ~I8042_CTR_AUXINT;
+
+	if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
+		i8042_ctr |= I8042_CTR_AUXINT;
+		i8042_ctr &= ~I8042_CTR_AUXDIS;
+		pr_err("Failed to disable AUX port\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
 /*
  * i8042_enable_aux_port enables AUX (mouse) port on chip
  */
@@ -1227,6 +1265,7 @@  static int i8042_controller_resume(bool s2r_wants_reset)
 static int i8042_pm_suspend(struct device *dev)
 {
 	int i;
+	bool wakeup_enabled[I8042_NUM_PORTS] = { false };
 
 	if (pm_suspend_via_firmware())
 		i8042_controller_reset(true);
@@ -1235,8 +1274,21 @@  static int i8042_pm_suspend(struct device *dev)
 	for (i = 0; i < I8042_NUM_PORTS; i++) {
 		struct serio *serio = i8042_ports[i].serio;
 
-		if (serio && device_may_wakeup(&serio->dev))
+		if (serio && device_may_wakeup(&serio->dev)) {
 			enable_irq_wake(i8042_ports[i].irq);
+			wakeup_enabled[i] = true;
+		}
+	}
+
+	/* For KBD and AUX ports, if at least one of them is enabled for wakeup
+	 * the system, we need to disable the one which isn't enabled for
+	 * wakeup, otherwise this port could impact the wakeup of the other port
+	 */
+	if (wakeup_enabled[I8042_KBD_PORT_NO] || wakeup_enabled[I8042_AUX_PORT_NO]) {
+		if (i8042_ports[I8042_KBD_PORT_NO].serio && !wakeup_enabled[I8042_KBD_PORT_NO])
+			i8042_disable_kbd_port();
+		else if (i8042_ports[I8042_AUX_PORT_NO].serio && !wakeup_enabled[I8042_AUX_PORT_NO])
+			i8042_disable_aux_port();
 	}
 
 	return 0;
@@ -1254,12 +1306,22 @@  static int i8042_pm_resume(struct device *dev)
 {
 	bool want_reset;
 	int i;
+	bool wakeup_enabled[I8042_NUM_PORTS] = { false };
 
 	for (i = 0; i < I8042_NUM_PORTS; i++) {
 		struct serio *serio = i8042_ports[i].serio;
 
-		if (serio && device_may_wakeup(&serio->dev))
+		if (serio && device_may_wakeup(&serio->dev)) {
 			disable_irq_wake(i8042_ports[i].irq);
+			wakeup_enabled[i] = true;
+		}
+	}
+
+	if (wakeup_enabled[I8042_KBD_PORT_NO] || wakeup_enabled[I8042_AUX_PORT_NO]) {
+		if (i8042_ports[I8042_KBD_PORT_NO].serio && !wakeup_enabled[I8042_KBD_PORT_NO])
+			i8042_enable_kbd_port();
+		else if (i8042_ports[I8042_AUX_PORT_NO].serio && !wakeup_enabled[I8042_AUX_PORT_NO])
+			i8042_enable_aux_port();
 	}
 
 	/*