|
@@ -19,6 +19,7 @@
|
|
#include <linux/kernel.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/pci.h>
|
|
|
|
+#include <linux/pm_runtime.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/usb.h>
|
|
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/io.h>
|
|
@@ -37,6 +38,122 @@
|
|
|
|
|
|
/* PCI-based HCs are common, but plenty of non-PCI HCs are used too */
|
|
/* PCI-based HCs are common, but plenty of non-PCI HCs are used too */
|
|
|
|
|
|
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
|
|
+
|
|
|
|
+/* Coordinate handoffs between EHCI and companion controllers
|
|
|
|
+ * during system resume
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+static DEFINE_MUTEX(companions_mutex);
|
|
|
|
+
|
|
|
|
+#define CL_UHCI PCI_CLASS_SERIAL_USB_UHCI
|
|
|
|
+#define CL_OHCI PCI_CLASS_SERIAL_USB_OHCI
|
|
|
|
+#define CL_EHCI PCI_CLASS_SERIAL_USB_EHCI
|
|
|
|
+
|
|
|
|
+enum companion_action {
|
|
|
|
+ SET_HS_COMPANION, CLEAR_HS_COMPANION, WAIT_FOR_COMPANIONS
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static void companion_common(struct pci_dev *pdev, struct usb_hcd *hcd,
|
|
|
|
+ enum companion_action action)
|
|
|
|
+{
|
|
|
|
+ struct pci_dev *companion;
|
|
|
|
+ struct usb_hcd *companion_hcd;
|
|
|
|
+ unsigned int slot = PCI_SLOT(pdev->devfn);
|
|
|
|
+
|
|
|
|
+ /* Iterate through other PCI functions in the same slot.
|
|
|
|
+ * If pdev is OHCI or UHCI then we are looking for EHCI, and
|
|
|
|
+ * vice versa.
|
|
|
|
+ */
|
|
|
|
+ companion = NULL;
|
|
|
|
+ for (;;) {
|
|
|
|
+ companion = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, companion);
|
|
|
|
+ if (!companion)
|
|
|
|
+ break;
|
|
|
|
+ if (companion->bus != pdev->bus ||
|
|
|
|
+ PCI_SLOT(companion->devfn) != slot)
|
|
|
|
+ continue;
|
|
|
|
+
|
|
|
|
+ companion_hcd = pci_get_drvdata(companion);
|
|
|
|
+ if (!companion_hcd)
|
|
|
|
+ continue;
|
|
|
|
+
|
|
|
|
+ /* For SET_HS_COMPANION, store a pointer to the EHCI bus in
|
|
|
|
+ * the OHCI/UHCI companion bus structure.
|
|
|
|
+ * For CLEAR_HS_COMPANION, clear the pointer to the EHCI bus
|
|
|
|
+ * in the OHCI/UHCI companion bus structure.
|
|
|
|
+ * For WAIT_FOR_COMPANIONS, wait until the OHCI/UHCI
|
|
|
|
+ * companion controllers have fully resumed.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ if ((pdev->class == CL_OHCI || pdev->class == CL_UHCI) &&
|
|
|
|
+ companion->class == CL_EHCI) {
|
|
|
|
+ /* action must be SET_HS_COMPANION */
|
|
|
|
+ dev_dbg(&companion->dev, "HS companion for %s\n",
|
|
|
|
+ dev_name(&pdev->dev));
|
|
|
|
+ hcd->self.hs_companion = &companion_hcd->self;
|
|
|
|
+
|
|
|
|
+ } else if (pdev->class == CL_EHCI &&
|
|
|
|
+ (companion->class == CL_OHCI ||
|
|
|
|
+ companion->class == CL_UHCI)) {
|
|
|
|
+ switch (action) {
|
|
|
|
+ case SET_HS_COMPANION:
|
|
|
|
+ dev_dbg(&pdev->dev, "HS companion for %s\n",
|
|
|
|
+ dev_name(&companion->dev));
|
|
|
|
+ companion_hcd->self.hs_companion = &hcd->self;
|
|
|
|
+ break;
|
|
|
|
+ case CLEAR_HS_COMPANION:
|
|
|
|
+ companion_hcd->self.hs_companion = NULL;
|
|
|
|
+ break;
|
|
|
|
+ case WAIT_FOR_COMPANIONS:
|
|
|
|
+ device_pm_wait_for_dev(&pdev->dev,
|
|
|
|
+ &companion->dev);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void set_hs_companion(struct pci_dev *pdev, struct usb_hcd *hcd)
|
|
|
|
+{
|
|
|
|
+ mutex_lock(&companions_mutex);
|
|
|
|
+ dev_set_drvdata(&pdev->dev, hcd);
|
|
|
|
+ companion_common(pdev, hcd, SET_HS_COMPANION);
|
|
|
|
+ mutex_unlock(&companions_mutex);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void clear_hs_companion(struct pci_dev *pdev, struct usb_hcd *hcd)
|
|
|
|
+{
|
|
|
|
+ mutex_lock(&companions_mutex);
|
|
|
|
+ dev_set_drvdata(&pdev->dev, NULL);
|
|
|
|
+
|
|
|
|
+ /* If pdev is OHCI or UHCI, just clear its hs_companion pointer */
|
|
|
|
+ if (pdev->class == CL_OHCI || pdev->class == CL_UHCI)
|
|
|
|
+ hcd->self.hs_companion = NULL;
|
|
|
|
+
|
|
|
|
+ /* Otherwise search for companion buses and clear their pointers */
|
|
|
|
+ else
|
|
|
|
+ companion_common(pdev, hcd, CLEAR_HS_COMPANION);
|
|
|
|
+ mutex_unlock(&companions_mutex);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static void wait_for_companions(struct pci_dev *pdev, struct usb_hcd *hcd)
|
|
|
|
+{
|
|
|
|
+ /* Only EHCI controllers need to wait.
|
|
|
|
+ * No locking is needed because a controller cannot be resumed
|
|
|
|
+ * while one of its companions is getting unbound.
|
|
|
|
+ */
|
|
|
|
+ if (pdev->class == CL_EHCI)
|
|
|
|
+ companion_common(pdev, hcd, WAIT_FOR_COMPANIONS);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#else /* !CONFIG_PM_SLEEP */
|
|
|
|
+
|
|
|
|
+static inline void set_hs_companion(struct pci_dev *d, struct usb_hcd *h) {}
|
|
|
|
+static inline void clear_hs_companion(struct pci_dev *d, struct usb_hcd *h) {}
|
|
|
|
+static inline void wait_for_companions(struct pci_dev *d, struct usb_hcd *h) {}
|
|
|
|
+
|
|
|
|
+#endif /* !CONFIG_PM_SLEEP */
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
@@ -123,7 +240,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
|
|
if (region == PCI_ROM_RESOURCE) {
|
|
if (region == PCI_ROM_RESOURCE) {
|
|
dev_dbg(&dev->dev, "no i/o regions available\n");
|
|
dev_dbg(&dev->dev, "no i/o regions available\n");
|
|
retval = -EBUSY;
|
|
retval = -EBUSY;
|
|
- goto err1;
|
|
|
|
|
|
+ goto err2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -132,6 +249,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
|
|
retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED);
|
|
retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED);
|
|
if (retval != 0)
|
|
if (retval != 0)
|
|
goto err4;
|
|
goto err4;
|
|
|
|
+ set_hs_companion(dev, hcd);
|
|
return retval;
|
|
return retval;
|
|
|
|
|
|
err4:
|
|
err4:
|
|
@@ -142,6 +260,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
|
|
} else
|
|
} else
|
|
release_region(hcd->rsrc_start, hcd->rsrc_len);
|
|
release_region(hcd->rsrc_start, hcd->rsrc_len);
|
|
err2:
|
|
err2:
|
|
|
|
+ clear_hs_companion(dev, hcd);
|
|
usb_put_hcd(hcd);
|
|
usb_put_hcd(hcd);
|
|
err1:
|
|
err1:
|
|
pci_disable_device(dev);
|
|
pci_disable_device(dev);
|
|
@@ -180,6 +299,7 @@ void usb_hcd_pci_remove(struct pci_dev *dev)
|
|
} else {
|
|
} else {
|
|
release_region(hcd->rsrc_start, hcd->rsrc_len);
|
|
release_region(hcd->rsrc_start, hcd->rsrc_len);
|
|
}
|
|
}
|
|
|
|
+ clear_hs_companion(dev, hcd);
|
|
usb_put_hcd(hcd);
|
|
usb_put_hcd(hcd);
|
|
pci_disable_device(dev);
|
|
pci_disable_device(dev);
|
|
}
|
|
}
|
|
@@ -344,6 +464,11 @@ static int resume_common(struct device *dev, bool hibernated)
|
|
clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
|
|
clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
|
|
|
|
|
|
if (hcd->driver->pci_resume) {
|
|
if (hcd->driver->pci_resume) {
|
|
|
|
+ /* This call should be made only during system resume,
|
|
|
|
+ * not during runtime resume.
|
|
|
|
+ */
|
|
|
|
+ wait_for_companions(pci_dev, hcd);
|
|
|
|
+
|
|
retval = hcd->driver->pci_resume(hcd, hibernated);
|
|
retval = hcd->driver->pci_resume(hcd, hibernated);
|
|
if (retval) {
|
|
if (retval) {
|
|
dev_err(dev, "PCI post-resume error %d!\n", retval);
|
|
dev_err(dev, "PCI post-resume error %d!\n", retval);
|