]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
HID: i2c-hid: Resolve touchpad issues on Dell systems during S4
authorMario Limonciello (AMD) <superm1@kernel.org>
Tue, 9 Sep 2025 11:00:06 +0000 (06:00 -0500)
committerBenjamin Tissoires <bentiss@kernel.org>
Tue, 16 Sep 2025 08:24:46 +0000 (10:24 +0200)
Dell systems utilize an EC-based touchpad emulation when the ACPI
touchpad _DSM is not invoked. This emulation acts as a secondary
master on the I2C bus, designed for scenarios where the I2C touchpad
driver is absent, such as in BIOS menus. Typically, loading the
i2c-hid module triggers the _DSM at initialization, disabling the
EC-based emulation.

However, if the i2c-hid module is missing from the boot kernel
used for hibernation snapshot restoration, the _DSM remains
uncalled, resulting in dual masters on the I2C bus and
subsequent arbitration errors. This issue arises when i2c-hid
resides in the rootfs instead of the kernel or initramfs.

To address this, switch from using the SYSTEM_SLEEP_PM_OPS()
macro to dedicated callbacks, introducing a specific
callback for restoring the S4 image. This callback ensures
the _DSM is invoked.

Signed-off-by: Mario Limonciello (AMD) <superm1@kernel.org>
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
drivers/hid/i2c-hid/i2c-hid-acpi.c
drivers/hid/i2c-hid/i2c-hid-core.c
drivers/hid/i2c-hid/i2c-hid.h

index 1b49243adb16a5606038d239be963c35c5b205a3..abd700a101f46c199c449faf8d263c815bd4b624 100644 (file)
@@ -76,6 +76,13 @@ static int i2c_hid_acpi_get_descriptor(struct i2c_hid_acpi *ihid_acpi)
        return hid_descriptor_address;
 }
 
+static void i2c_hid_acpi_restore_sequence(struct i2chid_ops *ops)
+{
+       struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops);
+
+       i2c_hid_acpi_get_descriptor(ihid_acpi);
+}
+
 static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops)
 {
        struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops);
@@ -96,6 +103,7 @@ static int i2c_hid_acpi_probe(struct i2c_client *client)
 
        ihid_acpi->adev = ACPI_COMPANION(dev);
        ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
+       ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence;
 
        ret = i2c_hid_acpi_get_descriptor(ihid_acpi);
        if (ret < 0)
index d3912e3f2f13ae741e2f154e58f184ee925c49f8..3257aa87be89809cc42913c65f99fee61a287175 100644 (file)
@@ -961,6 +961,14 @@ static void i2c_hid_core_shutdown_tail(struct i2c_hid *ihid)
        ihid->ops->shutdown_tail(ihid->ops);
 }
 
+static void i2c_hid_core_restore_sequence(struct i2c_hid *ihid)
+{
+       if (!ihid->ops->restore_sequence)
+               return;
+
+       ihid->ops->restore_sequence(ihid->ops);
+}
+
 static int i2c_hid_core_suspend(struct i2c_hid *ihid, bool force_poweroff)
 {
        struct i2c_client *client = ihid->client;
@@ -1360,8 +1368,26 @@ static int i2c_hid_core_pm_resume(struct device *dev)
        return i2c_hid_core_resume(ihid);
 }
 
+static int i2c_hid_core_pm_restore(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct i2c_hid *ihid = i2c_get_clientdata(client);
+
+       if (ihid->is_panel_follower)
+               return 0;
+
+       i2c_hid_core_restore_sequence(ihid);
+
+       return i2c_hid_core_resume(ihid);
+}
+
 const struct dev_pm_ops i2c_hid_core_pm = {
-       SYSTEM_SLEEP_PM_OPS(i2c_hid_core_pm_suspend, i2c_hid_core_pm_resume)
+       .suspend = pm_sleep_ptr(i2c_hid_core_pm_suspend),
+       .resume = pm_sleep_ptr(i2c_hid_core_pm_resume),
+       .freeze = pm_sleep_ptr(i2c_hid_core_pm_suspend),
+       .thaw = pm_sleep_ptr(i2c_hid_core_pm_resume),
+       .poweroff = pm_sleep_ptr(i2c_hid_core_pm_suspend),
+       .restore = pm_sleep_ptr(i2c_hid_core_pm_restore),
 };
 EXPORT_SYMBOL_GPL(i2c_hid_core_pm);
 
index 2c7b66d5caa0f9b6dd96120b09ad5d960c9293e6..1724a435c783aaeecf59334c66e6d21eeebc54b2 100644 (file)
@@ -27,11 +27,13 @@ static inline u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product)
  * @power_up: do sequencing to power up the device.
  * @power_down: do sequencing to power down the device.
  * @shutdown_tail: called at the end of shutdown.
+ * @restore_sequence: hibernation restore sequence.
  */
 struct i2chid_ops {
        int (*power_up)(struct i2chid_ops *ops);
        void (*power_down)(struct i2chid_ops *ops);
        void (*shutdown_tail)(struct i2chid_ops *ops);
+       void (*restore_sequence)(struct i2chid_ops *ops);
 };
 
 int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,