===================================================================
@@ -17,6 +17,7 @@
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/cpu.h>
+#include <linux/pm_runtime.h>
#include "pci.h"
/*
@@ -570,6 +571,70 @@ static void pci_pm_complete(struct devic
drv->pm->complete(dev);
}
+/*
+ * pci_pm_check_wakeup_event - pdev is suspected to invoke a wakeup event
+ * @pdev: the suspected pci device
+ *
+ * Clear PME status and disable PME, return if the pci device @pdev really
+ * invokes PME
+ **/
+bool pci_pm_check_wakeup_event(struct pci_dev *pdev)
+{
+ int pme_pos = pdev->pm_cap;
+ u16 pmcsr;
+ bool ret = false;
+
+ if (!pme_pos)
+ return false;
+
+ /* clear PME status and disable PME to avoid interrupt flood */
+ pci_read_config_word(pdev, pme_pos + PCI_PM_CTRL, &pmcsr);
+ if (!(pmcsr & PCI_PM_CTRL_PME_STATUS))
+ return false;
+
+ /* I see spurious PME here, just ignore it for now */
+ if (pmcsr & PCI_PM_CTRL_PME_ENABLE) {
+ pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
+ ret = true;
+ }
+
+ pmcsr |= PCI_PM_CTRL_PME_STATUS;
+ pci_write_config_word(pdev, pme_pos + PCI_PM_CTRL, pmcsr);
+
+ return ret;
+}
+
+/*
+ * pci_pm_handle_wakeup_event_platform - handle platform PCI wakeup event
+ * @target is suspected to invoke a wakeup event
+ */
+bool pci_pm_handle_wakeup_event_platform(struct pci_dev *target)
+{
+ bool ret = false;
+ struct pci_dev *tmp = NULL;
+
+ /*
+ * In the platform approach (ACPI), target PME status might get cleared
+ * by BIOS before OS receives a notification, so we haven't reliable
+ * method to detect if target is the wakeup source, just trust it is
+ */
+ pci_pm_check_wakeup_event(target);
+ pm_request_resume(&target->dev);
+ if (!target->subordinate)
+ ret = true;
+
+ /* scan all pci devices to find the wakeup source */
+ while ((tmp = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, tmp)) != NULL) {
+ if (tmp == target)
+ continue;
+ if (pci_pm_check_wakeup_event(tmp)) {
+ ret = true;
+ pm_request_resume(&tmp->dev);
+ }
+ }
+ return ret;
+}
+
#ifdef CONFIG_SUSPEND
static int pci_pm_suspend(struct device *dev)
===================================================================
@@ -83,6 +83,9 @@ static inline void pci_vpd_release(struc
dev->vpd->ops->release(dev);
}
+extern bool pci_pm_check_wakeup_event(struct pci_dev *target);
+extern bool pci_pm_handle_wakeup_event_platform(struct pci_dev *target);
+
/* PCI /proc functions */
#ifdef CONFIG_PROC_FS
extern int pci_proc_attach_device(struct pci_dev *dev);