usb: host: tegra: rewrite usb suspend/resume routine.
authorJay Cheng <jacheng@nvidia.com>
Mon, 22 Nov 2010 16:03:52 +0000 (11:03 -0500)
committerBenoit Goby <benoit@android.com>
Mon, 22 Nov 2010 21:05:49 +0000 (13:05 -0800)
Tegra host controller will time the resume operation to clear the bit
when the port control state switches to HS or FS Idle. This behavior
is different from EHCI where the host controller driver is required
to set this bit to a zero after the resume duration is timed in the
driver.

Poll PORT_SUSPEND bit till the suspend is completed. Write PORT_RESUME to 0
to clear PORT_SUSPEND bit.

Disable disconnect detection during resume.

Change-Id: I30a45dc7e7a87773a93c128877d0f0827e5d44b7
Signed-off-by: Jay Cheng <jacheng@nvidia.com>
drivers/usb/host/ehci-tegra.c

index e566a97f21dc4312d47d8c45b3b883a898bd868a..e3b01b39fc3f2f96a9a9bc87153240aff06758d4 100644 (file)
@@ -50,6 +50,7 @@ struct tegra_ehci_hcd {
        struct otg_transceiver *transceiver;
        int host_resumed;
        int bus_suspended;
+       int port_resuming;
        struct tegra_ehci_context context;
        int power_down_on_bus_suspend;
 };
@@ -82,6 +83,7 @@ static int tegra_ehci_hub_control(
 )
 {
        struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+       struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller);
        u32 __iomem     *status_reg;
        u32             temp;
        unsigned long   flags;
@@ -94,7 +96,7 @@ static int tegra_ehci_hub_control(
         * that are write on clear, by writing back the register read value, so
         * USB_PORT_FEAT_ENABLE is handled by masking the set on clear bits
         */
-       if ((typeReq == ClearPortFeature) && (wValue == USB_PORT_FEAT_ENABLE)) {
+       if (typeReq == ClearPortFeature && wValue == USB_PORT_FEAT_ENABLE) {
                spin_lock_irqsave(&ehci->lock, flags);
                temp = ehci_readl(ehci, status_reg);
                ehci_writel(ehci, (temp & ~PORT_RWC_BITS) & ~PORT_PE, status_reg);
@@ -102,6 +104,85 @@ static int tegra_ehci_hub_control(
                return retval;
        }
 
+       if (typeReq == GetPortStatus) {
+               spin_lock_irqsave(&ehci->lock, flags);
+               temp = ehci_readl(ehci, status_reg);
+               if (tegra->port_resuming &&  !(temp & PORT_SUSPEND)) {
+                       /* resume completed */
+                       tegra->port_resuming = 0;
+                       tegra_usb_phy_postresume(tegra->phy);
+               }
+               spin_unlock_irqrestore(&ehci->lock, flags);
+       }
+
+       if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) {
+               spin_lock_irqsave(&ehci->lock, flags);
+               temp = ehci_readl(ehci, status_reg);
+               if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0)
+                       retval = -EPIPE;
+
+               /* After above check the port must be connected.
+                * Set appropriate bit thus could put phy into low power
+                * mode if we have hostpc feature
+                */
+               temp &= ~PORT_WKCONN_E;
+               temp |= PORT_WKDISC_E | PORT_WKOC_E;
+               ehci_writel(ehci, temp | PORT_SUSPEND, status_reg);
+               if (handshake(ehci, status_reg, PORT_SUSPEND,
+                                               PORT_SUSPEND, 5000))
+                       pr_err("%s: timeout waiting for PORT_SUSPEND\n", __func__);
+               spin_unlock_irqrestore(&ehci->lock, flags);
+               return retval;
+       }
+
+       /*
+        * Tegra host controller will time the resume operation to clear the bit
+        * when the port control state switches to HS or FS Idle. This behavior
+        * is different from EHCI where the host controller driver is required
+        * to set this bit to a zero after the resume duration is timed in the
+        * driver.
+        */
+       if (typeReq == ClearPortFeature && wValue == USB_PORT_FEAT_SUSPEND) {
+               spin_lock_irqsave(&ehci->lock, flags);
+               temp = ehci_readl(ehci, status_reg);
+               if ((temp & PORT_RESET) || !(temp & PORT_PE))
+                       retval = -EPIPE;
+
+               if (!(temp & PORT_SUSPEND)) {
+                       spin_unlock_irqrestore(&ehci->lock, flags);
+                       return retval;
+               }
+
+               tegra_usb_phy_preresume(tegra->phy);
+
+               /* reschedule root hub polling during resume signaling */
+               ehci->reset_done[wIndex-1] = jiffies + msecs_to_jiffies(25);
+               /* check the port again */
+               mod_timer(&ehci_to_hcd(ehci)->rh_timer,
+                               ehci->reset_done[wIndex-1]);
+
+               temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
+               /* start resume signalling */
+               ehci_writel(ehci, temp | PORT_RESUME, status_reg);
+
+               /* polling PORT_RESUME until the controller clear this bit */
+               if (handshake(ehci, status_reg, PORT_RESUME, 0, 20000))
+                       pr_err("%s: timeout waiting for PORT_RESUME\n", __func__);
+
+               /* write PORT_RESUME to 0 to clear PORT_SUSPEND bit */
+               temp &= ~(PORT_RESUME | PORT_SUSPEND);
+               ehci_writel(ehci, temp, status_reg);
+
+               /* polling PORT_SUSPEND until the controller clear this bit */
+               if (handshake(ehci, status_reg, PORT_SUSPEND, 0, 2000))
+                       pr_err("%s: timeout waiting for PORT_SUSPEND\n", __func__);
+
+               tegra->port_resuming = 1;
+
+               spin_unlock_irqrestore(&ehci->lock, flags);
+               return retval;
+       }
+
        /* Handle the hub control events here */
        return ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
 }
@@ -357,6 +438,8 @@ static int tegra_ehci_bus_resume(struct usb_hcd *hcd)
                tegra->bus_suspended = 0;
        }
 
+       tegra_usb_phy_preresume(tegra->phy);
+       tegra->port_resuming = 1;
        return ehci_bus_resume(hcd);
 }