ACPI / AC: Add sleep quirk for Thinkpad e530
Lan Tianyu [Wed, 8 May 2013 07:28:46 +0000 (07:28 +0000)]
The Thinkpad e530's BIOS notifies the AC device first and then
sleeps for certain amount of time before doing real work in the
EC event handler (_Qxx):

 Method (_Q27, 0, NotSerialized)
 {
       Notify (AC, 0x80)
       Sleep (0x03E8)
       Store (Zero, PWRS)
       PNOT ()
 }

This causes the AC driver to report an outdated AC state to user
space, because it reads the state information from the device while
the EC handler is sleeping.

Introduce a quirk to cause the AC driver to wait in acpi_ac_notify()
before calling acpi_ac_get_state() on systems known to have this
problem and add Thinkpad e530 to the list of quirky machines (with
a 1s delay which has been verified to be sufficient for that
machine).

[rjw: Changelog]
References: https://bugzilla.kernel.org/show_bug.cgi?id=45221
Signed-off-by: Lan Tianyu <tianyu.lan@intel.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

drivers/acpi/ac.c

index 00d2efd..4f4e741 100644 (file)
@@ -28,6 +28,8 @@
 #include <linux/slab.h>
 #include <linux/init.h>
 #include <linux/types.h>
+#include <linux/dmi.h>
+#include <linux/delay.h>
 #ifdef CONFIG_ACPI_PROCFS_POWER
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
@@ -74,6 +76,8 @@ static int acpi_ac_resume(struct device *dev);
 #endif
 static SIMPLE_DEV_PM_OPS(acpi_ac_pm, NULL, acpi_ac_resume);
 
+static int ac_sleep_before_get_state_ms;
+
 static struct acpi_driver acpi_ac_driver = {
        .name = "ac",
        .class = ACPI_AC_CLASS,
@@ -252,6 +256,16 @@ static void acpi_ac_notify(struct acpi_device *device, u32 event)
        case ACPI_AC_NOTIFY_STATUS:
        case ACPI_NOTIFY_BUS_CHECK:
        case ACPI_NOTIFY_DEVICE_CHECK:
+               /*
+                * A buggy BIOS may notify AC first and then sleep for
+                * a specific time before doing actual operations in the
+                * EC event handler (_Qxx). This will cause the AC state
+                * reported by the ACPI event to be incorrect, so wait for a
+                * specific time for the EC event handler to make progress.
+                */
+               if (ac_sleep_before_get_state_ms > 0)
+                       msleep(ac_sleep_before_get_state_ms);
+
                acpi_ac_get_state(ac);
                acpi_bus_generate_proc_event(device, event, (u32) ac->state);
                acpi_bus_generate_netlink_event(device->pnp.device_class,
@@ -264,6 +278,24 @@ static void acpi_ac_notify(struct acpi_device *device, u32 event)
        return;
 }
 
+static int thinkpad_e530_quirk(const struct dmi_system_id *d)
+{
+       ac_sleep_before_get_state_ms = 1000;
+       return 0;
+}
+
+static struct dmi_system_id ac_dmi_table[] = {
+       {
+       .callback = thinkpad_e530_quirk,
+       .ident = "thinkpad e530",
+       .matches = {
+               DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+               DMI_MATCH(DMI_PRODUCT_NAME, "32597CG"),
+               },
+       },
+       {},
+};
+
 static int acpi_ac_add(struct acpi_device *device)
 {
        int result = 0;
@@ -312,6 +344,7 @@ static int acpi_ac_add(struct acpi_device *device)
                kfree(ac);
        }
 
+       dmi_check_system(ac_dmi_table);
        return result;
 }