Commit df17e62e authored by Matthew Garrett's avatar Matthew Garrett Committed by Jesse Barnes

PCI: Add support for polling PME state on suspended legacy PCI devices

Not all hardware vendors hook up the PME line for legacy PCI devices,
meaning that wakeup events get lost. The only way around this is to poll
the devices to see if their state has changed, so add support for doing
that on legacy PCI devices that aren't part of the core chipset.
Acked-by: default avatarRafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: default avatarMatthew Garrett <mjg@redhat.com>
Signed-off-by: default avatarJesse Barnes <jbarnes@virtuousgeek.org>
parent bf4d2908
...@@ -38,6 +38,19 @@ EXPORT_SYMBOL(pci_pci_problems); ...@@ -38,6 +38,19 @@ EXPORT_SYMBOL(pci_pci_problems);
unsigned int pci_pm_d3_delay; unsigned int pci_pm_d3_delay;
static void pci_pme_list_scan(struct work_struct *work);
static LIST_HEAD(pci_pme_list);
static DEFINE_MUTEX(pci_pme_list_mutex);
static DECLARE_DELAYED_WORK(pci_pme_work, pci_pme_list_scan);
struct pci_pme_device {
struct list_head list;
struct pci_dev *dev;
};
#define PME_TIMEOUT 1000 /* How long between PME checks */
static void pci_dev_d3_sleep(struct pci_dev *dev) static void pci_dev_d3_sleep(struct pci_dev *dev)
{ {
unsigned int delay = dev->d3_delay; unsigned int delay = dev->d3_delay;
...@@ -1331,6 +1344,32 @@ bool pci_pme_capable(struct pci_dev *dev, pci_power_t state) ...@@ -1331,6 +1344,32 @@ bool pci_pme_capable(struct pci_dev *dev, pci_power_t state)
return !!(dev->pme_support & (1 << state)); return !!(dev->pme_support & (1 << state));
} }
static void pci_pme_list_scan(struct work_struct *work)
{
struct pci_pme_device *pme_dev;
mutex_lock(&pci_pme_list_mutex);
if (!list_empty(&pci_pme_list)) {
list_for_each_entry(pme_dev, &pci_pme_list, list)
pci_pme_wakeup(pme_dev->dev, NULL);
schedule_delayed_work(&pci_pme_work, msecs_to_jiffies(PME_TIMEOUT));
}
mutex_unlock(&pci_pme_list_mutex);
}
/**
* pci_external_pme - is a device an external PCI PME source?
* @dev: PCI device to check
*
*/
static bool pci_external_pme(struct pci_dev *dev)
{
if (pci_is_pcie(dev) || dev->bus->number == 0)
return false;
return true;
}
/** /**
* pci_pme_active - enable or disable PCI device's PME# function * pci_pme_active - enable or disable PCI device's PME# function
* @dev: PCI device to handle. * @dev: PCI device to handle.
...@@ -1354,6 +1393,44 @@ void pci_pme_active(struct pci_dev *dev, bool enable) ...@@ -1354,6 +1393,44 @@ void pci_pme_active(struct pci_dev *dev, bool enable)
pci_write_config_word(dev, dev->pm_cap + PCI_PM_CTRL, pmcsr); pci_write_config_word(dev, dev->pm_cap + PCI_PM_CTRL, pmcsr);
/* PCI (as opposed to PCIe) PME requires that the device have
its PME# line hooked up correctly. Not all hardware vendors
do this, so the PME never gets delivered and the device
remains asleep. The easiest way around this is to
periodically walk the list of suspended devices and check
whether any have their PME flag set. The assumption is that
we'll wake up often enough anyway that this won't be a huge
hit, and the power savings from the devices will still be a
win. */
if (pci_external_pme(dev)) {
struct pci_pme_device *pme_dev;
if (enable) {
pme_dev = kmalloc(sizeof(struct pci_pme_device),
GFP_KERNEL);
if (!pme_dev)
goto out;
pme_dev->dev = dev;
mutex_lock(&pci_pme_list_mutex);
list_add(&pme_dev->list, &pci_pme_list);
if (list_is_singular(&pci_pme_list))
schedule_delayed_work(&pci_pme_work,
msecs_to_jiffies(PME_TIMEOUT));
mutex_unlock(&pci_pme_list_mutex);
} else {
mutex_lock(&pci_pme_list_mutex);
list_for_each_entry(pme_dev, &pci_pme_list, list) {
if (pme_dev->dev == dev) {
list_del(&pme_dev->list);
kfree(pme_dev);
break;
}
}
mutex_unlock(&pci_pme_list_mutex);
}
}
out:
dev_printk(KERN_DEBUG, &dev->dev, "PME# %s\n", dev_printk(KERN_DEBUG, &dev->dev, "PME# %s\n",
enable ? "enabled" : "disabled"); enable ? "enabled" : "disabled");
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment