linux-input.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] Input: i8042 - disable non-wakeup port during suspend
@ 2021-05-24 10:36 Hui Wang
  0 siblings, 0 replies; only message in thread
From: Hui Wang @ 2021-05-24 10:36 UTC (permalink / raw)
  To: linux-input, dmitry.torokhov; +Cc: hui.wang

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 --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();
 	}
 
 	/*
-- 
2.25.1


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2021-05-24 10:36 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2021-05-24 10:36 [PATCH] Input: i8042 - disable non-wakeup port during suspend Hui Wang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).