Skip to content

Commit

Permalink
Intel xhci: refactor EHCI/xHCI port switching
Browse files Browse the repository at this point in the history
Make the Linux xHCI driver automatically try to switchover the EHCI ports to
xHCI when an Intel xHCI host is detected, and it also finds an Intel EHCI host.

This means we will no longer have to add Intel xHCI hosts to a quirks list when
the PCI device IDs change.  Simply continuing to add new Intel xHCI PCI device
IDs to the quirks list is not sustainable.

During suspend ports may be swicthed back to EHCI by BIOS and not properly
restored to xHCI at resume. Previously both EHCI and xHCI resume functions
switched ports back to XHCI, but it's enough to do it in xHCI only
because the hub driver doesn't start running again until after both hosts are resumed.

Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
  • Loading branch information
Mathias Nyman authored and Sarah Sharp committed Jul 23, 2013
1 parent 063ebeb commit 26b7679
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 80 deletions.
42 changes: 0 additions & 42 deletions drivers/usb/host/ehci-pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -315,53 +315,11 @@ static int ehci_pci_setup(struct usb_hcd *hcd)
* Also they depend on separate root hub suspend/resume.
*/

static bool usb_is_intel_switchable_ehci(struct pci_dev *pdev)
{
return pdev->class == PCI_CLASS_SERIAL_USB_EHCI &&
pdev->vendor == PCI_VENDOR_ID_INTEL &&
(pdev->device == 0x1E26 ||
pdev->device == 0x8C2D ||
pdev->device == 0x8C26 ||
pdev->device == 0x9C26);
}

static void ehci_enable_xhci_companion(void)
{
struct pci_dev *companion = NULL;

/* The xHCI and EHCI controllers are not on the same PCI slot */
for_each_pci_dev(companion) {
if (!usb_is_intel_switchable_xhci(companion))
continue;
usb_enable_xhci_ports(companion);
return;
}
}

static int ehci_pci_resume(struct usb_hcd *hcd, bool hibernated)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
struct pci_dev *pdev = to_pci_dev(hcd->self.controller);

/* The BIOS on systems with the Intel Panther Point chipset may or may
* not support xHCI natively. That means that during system resume, it
* may switch the ports back to EHCI so that users can use their
* keyboard to select a kernel from GRUB after resume from hibernate.
*
* The BIOS is supposed to remember whether the OS had xHCI ports
* enabled before resume, and switch the ports back to xHCI when the
* BIOS/OS semaphore is written, but we all know we can't trust BIOS
* writers.
*
* Unconditionally switch the ports back to xHCI after a system resume.
* We can't tell whether the EHCI or xHCI controller will be resumed
* first, so we have to do the port switchover in both drivers. Writing
* a '1' to the port switchover registers should have no effect if the
* port was already switched over.
*/
if (usb_is_intel_switchable_ehci(pdev))
ehci_enable_xhci_companion();

if (ehci_resume(hcd, hibernated) != 0)
(void) ehci_pci_reinit(ehci, pdev);
return 0;
Expand Down
48 changes: 18 additions & 30 deletions drivers/usb/host/pci-quirks.c
Original file line number Diff line number Diff line change
Expand Up @@ -735,32 +735,6 @@ static int handshake(void __iomem *ptr, u32 mask, u32 done,
return -ETIMEDOUT;
}

#define PCI_DEVICE_ID_INTEL_LYNX_POINT_XHCI 0x8C31
#define PCI_DEVICE_ID_INTEL_LYNX_POINT_LP_XHCI 0x9C31

bool usb_is_intel_ppt_switchable_xhci(struct pci_dev *pdev)
{
return pdev->class == PCI_CLASS_SERIAL_USB_XHCI &&
pdev->vendor == PCI_VENDOR_ID_INTEL &&
pdev->device == PCI_DEVICE_ID_INTEL_PANTHERPOINT_XHCI;
}

/* The Intel Lynx Point chipset also has switchable ports. */
bool usb_is_intel_lpt_switchable_xhci(struct pci_dev *pdev)
{
return pdev->class == PCI_CLASS_SERIAL_USB_XHCI &&
pdev->vendor == PCI_VENDOR_ID_INTEL &&
(pdev->device == PCI_DEVICE_ID_INTEL_LYNX_POINT_XHCI ||
pdev->device == PCI_DEVICE_ID_INTEL_LYNX_POINT_LP_XHCI);
}

bool usb_is_intel_switchable_xhci(struct pci_dev *pdev)
{
return usb_is_intel_ppt_switchable_xhci(pdev) ||
usb_is_intel_lpt_switchable_xhci(pdev);
}
EXPORT_SYMBOL_GPL(usb_is_intel_switchable_xhci);

/*
* Intel's Panther Point chipset has two host controllers (EHCI and xHCI) that
* share some number of ports. These ports can be switched between either
Expand All @@ -779,9 +753,23 @@ EXPORT_SYMBOL_GPL(usb_is_intel_switchable_xhci);
* terminations before switching the USB 2.0 wires over, so that USB 3.0
* devices connect at SuperSpeed, rather than at USB 2.0 speeds.
*/
void usb_enable_xhci_ports(struct pci_dev *xhci_pdev)
void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev)
{
u32 ports_available;
bool ehci_found = false;
struct pci_dev *companion = NULL;

/* make sure an intel EHCI controller exists */
for_each_pci_dev(companion) {
if (companion->class == PCI_CLASS_SERIAL_USB_EHCI &&
companion->vendor == PCI_VENDOR_ID_INTEL) {
ehci_found = true;
break;
}
}

if (!ehci_found)
return;

/* Don't switchover the ports if the user hasn't compiled the xHCI
* driver. Otherwise they will see "dead" USB ports that don't power
Expand Down Expand Up @@ -840,7 +828,7 @@ void usb_enable_xhci_ports(struct pci_dev *xhci_pdev)
dev_dbg(&xhci_pdev->dev, "USB 2.0 ports that are now switched over "
"to xHCI: 0x%x\n", ports_available);
}
EXPORT_SYMBOL_GPL(usb_enable_xhci_ports);
EXPORT_SYMBOL_GPL(usb_enable_intel_xhci_ports);

void usb_disable_xhci_ports(struct pci_dev *xhci_pdev)
{
Expand Down Expand Up @@ -921,8 +909,8 @@ static void quirk_usb_handoff_xhci(struct pci_dev *pdev)
writel(val, base + ext_cap_offset + XHCI_LEGACY_CONTROL_OFFSET);

hc_init:
if (usb_is_intel_switchable_xhci(pdev))
usb_enable_xhci_ports(pdev);
if (pdev->vendor == PCI_VENDOR_ID_INTEL)
usb_enable_intel_xhci_ports(pdev);

op_reg_base = base + XHCI_HC_LENGTH(readl(base));

Expand Down
3 changes: 1 addition & 2 deletions drivers/usb/host/pci-quirks.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ int usb_amd_find_chipset_info(void);
void usb_amd_dev_put(void);
void usb_amd_quirk_pll_disable(void);
void usb_amd_quirk_pll_enable(void);
bool usb_is_intel_switchable_xhci(struct pci_dev *pdev);
void usb_enable_xhci_ports(struct pci_dev *xhci_pdev);
void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev);
void usb_disable_xhci_ports(struct pci_dev *xhci_pdev);
void sb800_prefetch(struct device *dev, int on);
#else
Expand Down
14 changes: 8 additions & 6 deletions drivers/usb/host/xhci-pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -250,13 +250,15 @@ static int xhci_pci_resume(struct usb_hcd *hcd, bool hibernated)
* writers.
*
* Unconditionally switch the ports back to xHCI after a system resume.
* We can't tell whether the EHCI or xHCI controller will be resumed
* first, so we have to do the port switchover in both drivers. Writing
* a '1' to the port switchover registers should have no effect if the
* port was already switched over.
* It should not matter whether the EHCI or xHCI controller is
* resumed first. It's enough to do the switchover in xHCI because
* USB core won't notice anything as the hub driver doesn't start
* running again until after all the devices (including both EHCI and
* xHCI host controllers) have been resumed.
*/
if (usb_is_intel_switchable_xhci(pdev))
usb_enable_xhci_ports(pdev);

if (pdev->vendor == PCI_VENDOR_ID_INTEL)
usb_enable_intel_xhci_ports(pdev);

retval = xhci_resume(xhci, hibernated);
return retval;
Expand Down

0 comments on commit 26b7679

Please sign in to comment.