| From c733b77475707cc3980542c86ee0ad5c841d544c Mon Sep 17 00:00:00 2001 |
| From: Huang Ying <ying.huang@intel.com> |
| Date: Wed, 26 Dec 2012 10:39:23 -0700 |
| Subject: PCI/PM: Do not suspend port if any subordinate device needs PME polling |
| |
| From: Huang Ying <ying.huang@intel.com> |
| |
| commit c733b77475707cc3980542c86ee0ad5c841d544c upstream. |
| |
| Ulrich reported that his USB3 cardreader does not work reliably when |
| connected to the USB3 port. It turns out that USB3 controller failed to |
| awaken when plugging in the USB3 cardreader. Further experiments found |
| that the USB3 host controller can only be awakened via polling, not via PME |
| interrupt. But if the PCIe port to which the USB3 host controller is |
| connected is suspended, we cannot poll the controller because its config |
| space is not accessible when the PCIe port is in a low power state. |
| |
| To solve the issue, the PCIe port will not be suspended if any subordinate |
| device needs PME polling. |
| |
| [bhelgaas: use bool consistently rather than mixing int/bool] |
| Reference: http://lkml.kernel.org/r/50841CCC.9030809@uli-eckhardt.de |
| Reported-by: Ulrich Eckhardt <usb@uli-eckhardt.de> |
| Tested-by: Sarah Sharp <sarah.a.sharp@linux.intel.com> |
| Signed-off-by: Huang Ying <ying.huang@intel.com> |
| Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> |
| Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/pci/pcie/portdrv_pci.c | 20 +++++++++++++++++++- |
| 1 file changed, 19 insertions(+), 1 deletion(-) |
| |
| --- a/drivers/pci/pcie/portdrv_pci.c |
| +++ b/drivers/pci/pcie/portdrv_pci.c |
| @@ -134,10 +134,28 @@ static int pcie_port_runtime_resume(stru |
| return 0; |
| } |
| |
| +static int pci_dev_pme_poll(struct pci_dev *pdev, void *data) |
| +{ |
| + bool *pme_poll = data; |
| + |
| + if (pdev->pme_poll) |
| + *pme_poll = true; |
| + return 0; |
| +} |
| + |
| static int pcie_port_runtime_idle(struct device *dev) |
| { |
| + struct pci_dev *pdev = to_pci_dev(dev); |
| + bool pme_poll = false; |
| + |
| + /* |
| + * If any subordinate device needs pme poll, we should keep |
| + * the port in D0, because we need port in D0 to poll it. |
| + */ |
| + pci_walk_bus(pdev->subordinate, pci_dev_pme_poll, &pme_poll); |
| /* Delay for a short while to prevent too frequent suspend/resume */ |
| - pm_schedule_suspend(dev, 10); |
| + if (!pme_poll) |
| + pm_schedule_suspend(dev, 10); |
| return -EBUSY; |
| } |
| #else |