[RFC 2/4] Intel xhci: Support EHCI/xHCI port switching.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux