USB: EHCI, OHCI: handover changes

This patch (as887) changes the way ehci-hcd and ohci-hcd handle a loss
of VBUS power during suspend.  In order for the USB-persist facility
to work correctly, it is necessary for low- and full-speed devices
attached to a high-speed port to be handed back to the companion
controller during resume processing.

This entails three changes: adding code to ehci-hcd to perform the
handover, removing code from ohci-hcd to turn off ports during
root-hub reinit, and adding code to ohci-hcd to turn on ports during
PCI controller resume.  (Other bus glue resume methods for platforms
supporting high-speed controllers would need a similar change, if any
existed.)

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c
index f4d301b..3e80de7 100644
--- a/drivers/usb/host/ehci-hub.c
+++ b/drivers/usb/host/ehci-hub.c
@@ -28,6 +28,87 @@
 
 /*-------------------------------------------------------------------------*/
 
+#ifdef	CONFIG_USB_PERSIST
+
+static int ehci_hub_control(
+	struct usb_hcd	*hcd,
+	u16		typeReq,
+	u16		wValue,
+	u16		wIndex,
+	char		*buf,
+	u16		wLength
+);
+
+/* After a power loss, ports that were owned by the companion must be
+ * reset so that the companion can still own them.
+ */
+static void ehci_handover_companion_ports(struct ehci_hcd *ehci)
+{
+	u32 __iomem	*reg;
+	u32		status;
+	int		port;
+	__le32		buf;
+	struct usb_hcd	*hcd = ehci_to_hcd(ehci);
+
+	if (!ehci->owned_ports)
+		return;
+
+	/* Give the connections some time to appear */
+	msleep(20);
+
+	port = HCS_N_PORTS(ehci->hcs_params);
+	while (port--) {
+		if (test_bit(port, &ehci->owned_ports)) {
+			reg = &ehci->regs->port_status[port];
+			status = ehci_readl(ehci, reg);
+
+			/* Port already owned by companion? */
+			if (status & PORT_OWNER)
+				clear_bit(port, &ehci->owned_ports);
+			else
+				ehci_hub_control(hcd, SetPortFeature,
+						USB_PORT_FEAT_RESET, port + 1,
+						NULL, 0);
+		}
+	}
+
+	if (!ehci->owned_ports)
+		return;
+	msleep(90);		/* Wait for resets to complete */
+
+	port = HCS_N_PORTS(ehci->hcs_params);
+	while (port--) {
+		if (test_bit(port, &ehci->owned_ports)) {
+			ehci_hub_control(hcd, GetPortStatus,
+					0, port + 1,
+					(char *) &buf, sizeof(buf));
+
+			/* The companion should now own the port,
+			 * but if something went wrong the port must not
+			 * remain enabled.
+			 */
+			reg = &ehci->regs->port_status[port];
+			status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
+			if (status & PORT_OWNER)
+				ehci_writel(ehci, status | PORT_CSC, reg);
+			else {
+				ehci_dbg(ehci, "failed handover port %d: %x\n",
+						port + 1, status);
+				ehci_writel(ehci, status & ~PORT_PE, reg);
+			}
+		}
+	}
+
+	ehci->owned_ports = 0;
+}
+
+#else	/* CONFIG_USB_PERSIST */
+
+static inline void ehci_handover_companion_ports(struct ehci_hcd *ehci)
+{ }
+
+#endif
+
 #ifdef	CONFIG_PM
 
 static int ehci_bus_suspend (struct usb_hcd *hcd)
@@ -60,14 +141,16 @@
 	 * then manually resume them in the bus_resume() routine.
 	 */
 	ehci->bus_suspended = 0;
+	ehci->owned_ports = 0;
 	while (port--) {
 		u32 __iomem	*reg = &ehci->regs->port_status [port];
 		u32		t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
 		u32		t2 = t1;
 
 		/* keep track of which ports we suspend */
-		if ((t1 & PORT_PE) && !(t1 & PORT_OWNER) &&
-				!(t1 & PORT_SUSPEND)) {
+		if (t1 & PORT_OWNER)
+			set_bit(port, &ehci->owned_ports);
+		else if ((t1 & PORT_PE) && !(t1 & PORT_SUSPEND)) {
 			t2 |= PORT_SUSPEND;
 			set_bit(port, &ehci->bus_suspended);
 		}
@@ -108,6 +191,7 @@
 {
 	struct ehci_hcd		*ehci = hcd_to_ehci (hcd);
 	u32			temp;
+	u32			power_okay;
 	int			i;
 
 	if (time_before (jiffies, ehci->next_statechange))
@@ -120,8 +204,9 @@
 	 * the last user of the controller, not reset/pm hardware keeping
 	 * state we gave to it.
 	 */
-	temp = ehci_readl(ehci, &ehci->regs->intr_enable);
-	ehci_dbg(ehci, "resume root hub%s\n", temp ? "" : " after power loss");
+	power_okay = ehci_readl(ehci, &ehci->regs->intr_enable);
+	ehci_dbg(ehci, "resume root hub%s\n",
+			power_okay ? "" : " after power loss");
 
 	/* at least some APM implementations will try to deliver
 	 * IRQs right away, so delay them until we're ready.
@@ -184,6 +269,9 @@
 	ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable);
 
 	spin_unlock_irq (&ehci->lock);
+
+	if (!power_okay)
+		ehci_handover_companion_ports(ehci);
 	return 0;
 }
 
@@ -448,7 +536,8 @@
 ) {
 	struct ehci_hcd	*ehci = hcd_to_ehci (hcd);
 	int		ports = HCS_N_PORTS (ehci->hcs_params);
-	u32 __iomem	*status_reg = &ehci->regs->port_status[wIndex - 1];
+	u32 __iomem	*status_reg = &ehci->regs->port_status[
+				(wIndex & 0xff) - 1];
 	u32		temp, status;
 	unsigned long	flags;
 	int		retval = 0;