diff options
| -rw-r--r-- | drivers/usb/core/driver.c | 8 | ||||
| -rw-r--r-- | drivers/usb/core/hcd-pci.c | 127 | ||||
| -rw-r--r-- | include/linux/usb.h | 1 |
3 files changed, 135 insertions, 1 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 60a45f1e3a67..f2f055eb6831 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c | |||
| @@ -1022,6 +1022,14 @@ static int usb_resume_device(struct usb_device *udev, pm_message_t msg) | |||
| 1022 | goto done; | 1022 | goto done; |
| 1023 | } | 1023 | } |
| 1024 | 1024 | ||
| 1025 | /* Non-root devices on a full/low-speed bus must wait for their | ||
| 1026 | * companion high-speed root hub, in case a handoff is needed. | ||
| 1027 | */ | ||
| 1028 | if (!(msg.event & PM_EVENT_AUTO) && udev->parent && | ||
| 1029 | udev->bus->hs_companion) | ||
| 1030 | device_pm_wait_for_dev(&udev->dev, | ||
| 1031 | &udev->bus->hs_companion->root_hub->dev); | ||
| 1032 | |||
| 1025 | if (udev->quirks & USB_QUIRK_RESET_RESUME) | 1033 | if (udev->quirks & USB_QUIRK_RESET_RESUME) |
| 1026 | udev->reset_resume = 1; | 1034 | udev->reset_resume = 1; |
| 1027 | 1035 | ||
diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index 2dcf906df569..15286533c15a 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c | |||
| @@ -19,6 +19,7 @@ | |||
| 19 | #include <linux/kernel.h> | 19 | #include <linux/kernel.h> |
| 20 | #include <linux/module.h> | 20 | #include <linux/module.h> |
| 21 | #include <linux/pci.h> | 21 | #include <linux/pci.h> |
| 22 | #include <linux/pm_runtime.h> | ||
| 22 | #include <linux/usb.h> | 23 | #include <linux/usb.h> |
| 23 | 24 | ||
| 24 | #include <asm/io.h> | 25 | #include <asm/io.h> |
| @@ -37,6 +38,122 @@ | |||
| 37 | 38 | ||
| 38 | /* PCI-based HCs are common, but plenty of non-PCI HCs are used too */ | 39 | /* PCI-based HCs are common, but plenty of non-PCI HCs are used too */ |
| 39 | 40 | ||
| 41 | #ifdef CONFIG_PM_SLEEP | ||
| 42 | |||
| 43 | /* Coordinate handoffs between EHCI and companion controllers | ||
| 44 | * during system resume | ||
| 45 | */ | ||
| 46 | |||
| 47 | static DEFINE_MUTEX(companions_mutex); | ||
| 48 | |||
| 49 | #define CL_UHCI PCI_CLASS_SERIAL_USB_UHCI | ||
| 50 | #define CL_OHCI PCI_CLASS_SERIAL_USB_OHCI | ||
| 51 | #define CL_EHCI PCI_CLASS_SERIAL_USB_EHCI | ||
| 52 | |||
| 53 | enum companion_action { | ||
| 54 | SET_HS_COMPANION, CLEAR_HS_COMPANION, WAIT_FOR_COMPANIONS | ||
| 55 | }; | ||
| 56 | |||
| 57 | static void companion_common(struct pci_dev *pdev, struct usb_hcd *hcd, | ||
| 58 | enum companion_action action) | ||
| 59 | { | ||
| 60 | struct pci_dev *companion; | ||
| 61 | struct usb_hcd *companion_hcd; | ||
| 62 | unsigned int slot = PCI_SLOT(pdev->devfn); | ||
| 63 | |||
| 64 | /* Iterate through other PCI functions in the same slot. | ||
| 65 | * If pdev is OHCI or UHCI then we are looking for EHCI, and | ||
| 66 | * vice versa. | ||
| 67 | */ | ||
| 68 | companion = NULL; | ||
| 69 | for (;;) { | ||
| 70 | companion = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, companion); | ||
| 71 | if (!companion) | ||
| 72 | break; | ||
| 73 | if (companion->bus != pdev->bus || | ||
| 74 | PCI_SLOT(companion->devfn) != slot) | ||
| 75 | continue; | ||
| 76 | |||
| 77 | companion_hcd = pci_get_drvdata(companion); | ||
| 78 | if (!companion_hcd) | ||
| 79 | continue; | ||
| 80 | |||
| 81 | /* For SET_HS_COMPANION, store a pointer to the EHCI bus in | ||
| 82 | * the OHCI/UHCI companion bus structure. | ||
| 83 | * For CLEAR_HS_COMPANION, clear the pointer to the EHCI bus | ||
| 84 | * in the OHCI/UHCI companion bus structure. | ||
| 85 | * For WAIT_FOR_COMPANIONS, wait until the OHCI/UHCI | ||
| 86 | * companion controllers have fully resumed. | ||
| 87 | */ | ||
| 88 | |||
| 89 | if ((pdev->class == CL_OHCI || pdev->class == CL_UHCI) && | ||
| 90 | companion->class == CL_EHCI) { | ||
| 91 | /* action must be SET_HS_COMPANION */ | ||
| 92 | dev_dbg(&companion->dev, "HS companion for %s\n", | ||
| 93 | dev_name(&pdev->dev)); | ||
| 94 | hcd->self.hs_companion = &companion_hcd->self; | ||
| 95 | |||
| 96 | } else if (pdev->class == CL_EHCI && | ||
| 97 | (companion->class == CL_OHCI || | ||
| 98 | companion->class == CL_UHCI)) { | ||
| 99 | switch (action) { | ||
| 100 | case SET_HS_COMPANION: | ||
| 101 | dev_dbg(&pdev->dev, "HS companion for %s\n", | ||
| 102 | dev_name(&companion->dev)); | ||
| 103 | companion_hcd->self.hs_companion = &hcd->self; | ||
| 104 | break; | ||
| 105 | case CLEAR_HS_COMPANION: | ||
| 106 | companion_hcd->self.hs_companion = NULL; | ||
| 107 | break; | ||
| 108 | case WAIT_FOR_COMPANIONS: | ||
| 109 | device_pm_wait_for_dev(&pdev->dev, | ||
| 110 | &companion->dev); | ||
| 111 | break; | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | static void set_hs_companion(struct pci_dev *pdev, struct usb_hcd *hcd) | ||
| 118 | { | ||
| 119 | mutex_lock(&companions_mutex); | ||
| 120 | dev_set_drvdata(&pdev->dev, hcd); | ||
| 121 | companion_common(pdev, hcd, SET_HS_COMPANION); | ||
| 122 | mutex_unlock(&companions_mutex); | ||
| 123 | } | ||
| 124 | |||
| 125 | static void clear_hs_companion(struct pci_dev *pdev, struct usb_hcd *hcd) | ||
| 126 | { | ||
| 127 | mutex_lock(&companions_mutex); | ||
| 128 | dev_set_drvdata(&pdev->dev, NULL); | ||
| 129 | |||
| 130 | /* If pdev is OHCI or UHCI, just clear its hs_companion pointer */ | ||
| 131 | if (pdev->class == CL_OHCI || pdev->class == CL_UHCI) | ||
| 132 | hcd->self.hs_companion = NULL; | ||
| 133 | |||
| 134 | /* Otherwise search for companion buses and clear their pointers */ | ||
| 135 | else | ||
| 136 | companion_common(pdev, hcd, CLEAR_HS_COMPANION); | ||
| 137 | mutex_unlock(&companions_mutex); | ||
| 138 | } | ||
| 139 | |||
| 140 | static void wait_for_companions(struct pci_dev *pdev, struct usb_hcd *hcd) | ||
| 141 | { | ||
| 142 | /* Only EHCI controllers need to wait. | ||
| 143 | * No locking is needed because a controller cannot be resumed | ||
| 144 | * while one of its companions is getting unbound. | ||
| 145 | */ | ||
| 146 | if (pdev->class == CL_EHCI) | ||
| 147 | companion_common(pdev, hcd, WAIT_FOR_COMPANIONS); | ||
| 148 | } | ||
| 149 | |||
| 150 | #else /* !CONFIG_PM_SLEEP */ | ||
| 151 | |||
| 152 | static inline void set_hs_companion(struct pci_dev *d, struct usb_hcd *h) {} | ||
| 153 | static inline void clear_hs_companion(struct pci_dev *d, struct usb_hcd *h) {} | ||
| 154 | static inline void wait_for_companions(struct pci_dev *d, struct usb_hcd *h) {} | ||
| 155 | |||
| 156 | #endif /* !CONFIG_PM_SLEEP */ | ||
| 40 | 157 | ||
| 41 | /*-------------------------------------------------------------------------*/ | 158 | /*-------------------------------------------------------------------------*/ |
| 42 | 159 | ||
| @@ -123,7 +240,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) | |||
| 123 | if (region == PCI_ROM_RESOURCE) { | 240 | if (region == PCI_ROM_RESOURCE) { |
| 124 | dev_dbg(&dev->dev, "no i/o regions available\n"); | 241 | dev_dbg(&dev->dev, "no i/o regions available\n"); |
| 125 | retval = -EBUSY; | 242 | retval = -EBUSY; |
| 126 | goto err1; | 243 | goto err2; |
| 127 | } | 244 | } |
| 128 | } | 245 | } |
| 129 | 246 | ||
| @@ -132,6 +249,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) | |||
| 132 | retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED); | 249 | retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED); |
| 133 | if (retval != 0) | 250 | if (retval != 0) |
| 134 | goto err4; | 251 | goto err4; |
| 252 | set_hs_companion(dev, hcd); | ||
| 135 | return retval; | 253 | return retval; |
| 136 | 254 | ||
| 137 | err4: | 255 | err4: |
| @@ -142,6 +260,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) | |||
| 142 | } else | 260 | } else |
| 143 | release_region(hcd->rsrc_start, hcd->rsrc_len); | 261 | release_region(hcd->rsrc_start, hcd->rsrc_len); |
| 144 | err2: | 262 | err2: |
| 263 | clear_hs_companion(dev, hcd); | ||
| 145 | usb_put_hcd(hcd); | 264 | usb_put_hcd(hcd); |
| 146 | err1: | 265 | err1: |
| 147 | pci_disable_device(dev); | 266 | pci_disable_device(dev); |
| @@ -180,6 +299,7 @@ void usb_hcd_pci_remove(struct pci_dev *dev) | |||
| 180 | } else { | 299 | } else { |
| 181 | release_region(hcd->rsrc_start, hcd->rsrc_len); | 300 | release_region(hcd->rsrc_start, hcd->rsrc_len); |
| 182 | } | 301 | } |
| 302 | clear_hs_companion(dev, hcd); | ||
| 183 | usb_put_hcd(hcd); | 303 | usb_put_hcd(hcd); |
| 184 | pci_disable_device(dev); | 304 | pci_disable_device(dev); |
| 185 | } | 305 | } |
| @@ -344,6 +464,11 @@ static int resume_common(struct device *dev, bool hibernated) | |||
| 344 | clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); | 464 | clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); |
| 345 | 465 | ||
| 346 | if (hcd->driver->pci_resume) { | 466 | if (hcd->driver->pci_resume) { |
| 467 | /* This call should be made only during system resume, | ||
| 468 | * not during runtime resume. | ||
| 469 | */ | ||
| 470 | wait_for_companions(pci_dev, hcd); | ||
| 471 | |||
| 347 | retval = hcd->driver->pci_resume(hcd, hibernated); | 472 | retval = hcd->driver->pci_resume(hcd, hibernated); |
| 348 | if (retval) { | 473 | if (retval) { |
| 349 | dev_err(dev, "PCI post-resume error %d!\n", retval); | 474 | dev_err(dev, "PCI post-resume error %d!\n", retval); |
diff --git a/include/linux/usb.h b/include/linux/usb.h index d7ace1b80f09..332eaea61021 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h | |||
| @@ -339,6 +339,7 @@ struct usb_bus { | |||
| 339 | 339 | ||
| 340 | struct usb_devmap devmap; /* device address allocation map */ | 340 | struct usb_devmap devmap; /* device address allocation map */ |
| 341 | struct usb_device *root_hub; /* Root hub */ | 341 | struct usb_device *root_hub; /* Root hub */ |
| 342 | struct usb_bus *hs_companion; /* Companion EHCI bus, if any */ | ||
| 342 | struct list_head bus_list; /* list of busses */ | 343 | struct list_head bus_list; /* list of busses */ |
| 343 | 344 | ||
| 344 | int bandwidth_allocated; /* on this bus: how much of the time | 345 | int bandwidth_allocated; /* on this bus: how much of the time |
