The Intel Panther Point chipsets contain an EHCI and xHCI host controller that shares some number of skew-dependent ports. These ports can be switched from the EHCI to the xHCI host (and vice versa) by a hardware MUX that is controlled by registers in the xHCI PCI configuration space. The USB 3.0 SuperSpeed terminations on the xHCI ports can be controlled separately from the USB 2.0 data wires. This switchover mechanism is there to support users who do a custom install of certain non-Linux operating systems that don't have official USB 3.0 support. By default, the ports are under EHCI, SuperSpeed terminations are off, and USB 3.0 devices will show up under the EHCI controller at reduced speeds. (This was more palatable for the marketing folks than having completely dead USB 3.0 ports if no xHCI drivers are available.) Users should be able to turn on xHCI by default through a BIOS option, but users are happiest when they don't have to change random BIOS settings. This patch introduces a driver method to switchover the ports from EHCI to xHCI before the EHCI driver finishes PCI enumeration. We want to switch the ports over before the USB core has the chance to enumerate devices under EHCI, or boot from USB mass storage will fail if the boot device connects under EHCI first, and then gets disconnected when the port switches over to xHCI. Originally, I had hoped I could do the port switchover in the xHCI driver. Unfortunately, I can't guarantee that the xHCI driver is loaded before the EHCI driver, because the two hosts are on different PCI slots. (The EHCI and UHCI/OHCI driver load issue is solved because the EHCI host is the highest numbered function on the same PCI slot as the companion controller, and Linux always enumerates the highest function first.) I looked at making the EHCI driver wait for the xHCI driver through some sort of semaphore in the USB core, but that fell apart when I started to consider failed xHCI PCI probes and the fact that you can't rely on the return value of request_module() to ensure xHCI driver load. Instead, the solution I came up was to make the EHCI driver switch over the ports in its PCI probe function. It's not pretty, since the EHCI driver has to search for the xHCI PCI device and then write into its PCI configuration space, but I think it's the simplest solution. This patch also introduces a module parameter that allows users to choose which ports they want to switch over, so that if a distro chooses to blacklist the xHCI driver, they can load the EHCI driver with the port switchover mask set to zero. A sysfs interface to switchover the ports on demand might be a good idea, but I'm not sure if we want to expose that to users, who may try to switchover active mass storage devices in the middle of transfers. The xHCI PCI configuration registers will be documented in the EDS-level chipset spec, which is not public yet. I have permission from legal and the Intel chipset group to release this patch early to allow good Linux support at product launch. I've tried to document the registers as much as possible, so please let me know if anything is unclear. Signed-off-by: Sarah Sharp <sarah.a.sharp@xxxxxxxxxxxxxxx> --- drivers/usb/host/ehci-hcd.c | 5 ++ drivers/usb/host/ehci-pci.c | 137 +++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/ehci.h | 1 + 3 files changed, 143 insertions(+), 0 deletions(-) diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index b435ed6..2e87296b 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -112,6 +112,11 @@ static unsigned int hird; module_param(hird, int, S_IRUGO); MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us\n"); +/* For Panther Point chipsets that can switch ports from EHCI to xHCI.*/ +static unsigned int switch_mask = 0xffffffff; +module_param(switch_mask, int, S_IRUGO); +MODULE_PARM_DESC(switch_mask, "Bitmap of ports to switch from EHCI to xHCI\n"); + #define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT) /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 660b80a..f74cef2 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -27,6 +27,122 @@ /*-------------------------------------------------------------------------*/ +#define USB_INTEL_XUSB2PR 0xD0 +#define USB_INTEL_XUSB2PRM 0xD4 +#define USB_INTEL_USB3_PSSEN 0xD8 +#define USB_INTEL_USB3PRM 0xDC + +static bool 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; +} + +static bool is_intel_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; +} + +/* + * 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 + * controller. Not all of the ports under the EHCI host controller may be + * switchable. + * + * The ports should be switched over to xHCI before the EHCI (re)starts the host + * controller. This avoids active devices under EHCI being disconnected during + * the port switchover, which could cause loss of data on USB storage devices, + * or failed boot when the root file system is on a USB mass storage device and + * is enumerated under EHCI first. + * + * Unfortunately, the registers to do the port switchover are in the xHCI host + * controller's config space, so we have to go searching for it on the PCI bus. + * Then we write into the xHC's PCI configuration space in some Intel-specific + * registers to switch the ports over. The USB 3.0 terminations and the USB 2.0 + * data wires are switched separately. We want to enable the SuperSpeed + * 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. + */ +static void enable_xhci_ports(struct ehci_hcd *ehci, struct pci_dev *xhci_pdev) +{ + u32 ports_available; + + if (!ehci->superspeed_companion) + return; + + /* Read USB3PRM, the USB 3.0 Port Routing Mask Register, to + * determine which USB 3.0 ports can be enabled on the xHCI host + * controller. Different skews may have different numbers of + * USB 3.0 ports, and each port can be switched individually. + */ + pci_read_config_dword(xhci_pdev, USB_INTEL_USB3PRM, + &ports_available); + /* The BIOS is supposed to set the mask register based on what skew it + * was written for, but just to be on the safe side... + */ + if (!ports_available) + ehci_warn(ehci, "WARNING: BIOS reports no USB 3.0 ports? " + "Email linux-usb@xxxxxxxxxxxxxxx\n"); + + /* Now write USB3_PSSEN, the USB 3.0 Port SuperSpeed Enable + * Register, to turn on SuperSpeed terminations for the + * available ports. + * + * Don't enable terminations for the USB 2.0 ports that we're + * not switching over. + */ + pci_write_config_dword(xhci_pdev, USB_INTEL_USB3_PSSEN, + cpu_to_le32(ports_available & + ((u32) switch_mask))); + + pci_read_config_dword(xhci_pdev, USB_INTEL_USB3_PSSEN, + &ports_available); + ehci_dbg(ehci, "USB 3.0 ports that are now enabled " + "under xHCI: 0x%x\n", ports_available); + + /* Read XUSB2PRM, the xHC USB 2.0 Port Routing Mask Register, + * to determine which USB 2.0 ports can be switched over. + */ + pci_read_config_dword(xhci_pdev, USB_INTEL_XUSB2PRM, + &ports_available); + if (!ports_available) + ehci_warn(ehci, "WARNING: BIOS reports no USB 2.0 ports? " + "Email linux-usb@xxxxxxxxxxxxxxx\n"); + + /* Now write XUSB2PR, the xHC USB 2.0 Port Routing Register, to + * switch the USB 2.0 power and data lines over to the xHCI + * host. + */ + pci_write_config_dword(xhci_pdev, USB_INTEL_XUSB2PR, + cpu_to_le32(ports_available & + ((u32) switch_mask))); + + pci_read_config_dword(xhci_pdev, USB_INTEL_XUSB2PR, + &ports_available); + ehci_dbg(ehci, "USB 2.0 ports that are now switched over " + "to xHCI: 0x%x\n", ports_available); + +} + +static void find_xhci_companion(struct pci_dev *pdev, + struct ehci_hcd *ehci) +{ + struct pci_dev *companion = NULL; + + printk(KERN_DEBUG "Performing EHCI to xHCI port switchover.\n"); + /* The xHCI and EHCI controllers are not on the same PCI slot */ + for_each_pci_dev(companion) { + if (!is_intel_switchable_xhci(companion)) + continue; + + ehci->superspeed_companion = companion; + return; + } +} + /* called after powerup, by probe or system-pm "wakeup" */ static int ehci_pci_reinit(struct ehci_hcd *ehci, struct pci_dev *pdev) { @@ -118,6 +234,10 @@ static int ehci_pci_setup(struct usb_hcd *hcd) ehci_info(ehci, "applying AMD SB700/SB800/Hudson-2/3 EHCI " "dummy qh workaround\n"); } + if (is_intel_switchable_ehci(pdev)) { + find_xhci_companion(pdev, ehci); + enable_xhci_ports(ehci, ehci->superspeed_companion); + } /* data structure init */ retval = ehci_init(hcd); @@ -353,6 +473,23 @@ 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. + * Writing a '1' to the port switchover registers should have no effect + * if the port was already switched over. + */ + if (is_intel_switchable_ehci(pdev)) + enable_xhci_ports(ehci, ehci->superspeed_companion); + // maybe restore FLADJ if (time_before(jiffies, ehci->next_statechange)) diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index bd6ff48..844774e 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -150,6 +150,7 @@ struct ehci_hcd { /* one per controller */ unsigned has_lpm:1; /* support link power management */ unsigned has_ppcd:1; /* support per-port change bits */ u8 sbrn; /* packed release number */ + struct pci_dev *superspeed_companion; /* irq statistics */ #ifdef EHCI_STATS -- 1.7.4.1 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html