arm: tegra: usb_phy: Fix race condition in resume
Abhishek Shukla [Sun, 9 Dec 2012 02:50:35 +0000 (07:50 +0530)]
Resume could fail if remote wake is detected
by  PMC after controller has been put in suspend
during resume code. Restart bringing up host
controller as in case of remote wake if this hapens.

Bug 1179329

Change-Id: I7df4fcb73c565aedc4b22ff9cf229d3b50b99d15
Signed-off-by: Abhishek Shukla <abhisheks@nvidia.com>
Reviewed-on: http://git-master/r/169602
(cherry picked from commit c80deaefbf4c3b1e93dba6546e52bcd9fc72d0f0)
Reviewed-on: http://git-master/r/171255
Reviewed-by: Varun Colbert <vcolbert@nvidia.com>
Tested-by: Varun Colbert <vcolbert@nvidia.com>

arch/arm/mach-tegra/tegra11x_usb_phy.c
arch/arm/mach-tegra/tegra_usb_phy.h

index 7d5a009..a362712 100644 (file)
@@ -1083,6 +1083,7 @@ static int usb_phy_bringup_host_controller(struct tegra_usb_phy *phy)
        val |= USB_USBSTS_PCI;
        writel(val, base + USB_USBSTS);
 
+       phy->ctrlr_suspended = false;
        if (!phy->pmc_remote_wakeup) {
                /* Put controller in suspend mode by writing 1
                 * to SUSP bit of PORTSC */
@@ -1090,6 +1091,7 @@ static int usb_phy_bringup_host_controller(struct tegra_usb_phy *phy)
                if ((val & USB_PORTSC_PP) && (val & USB_PORTSC_PE)) {
                        val |= USB_PORTSC_SUSP;
                        writel(val, base + USB_PORTSC);
+                       phy->ctrlr_suspended = true;
                        /* Wait until port suspend completes */
                        if (usb_phy_reg_status_wait(base + USB_PORTSC,
                                USB_PORTSC_SUSP, USB_PORTSC_SUSP, 4000)) {
@@ -1958,6 +1960,21 @@ static void uhsic_phy_disable_pmc_bus_ctrl(struct tegra_usb_phy *phy)
        val &= ~EVENT_INT_ENB;
        writel(val, base + UHSIC_PMC_WAKEUP0);
 
+       /*
+        * If pmc wakeup is detected after putting controller in suspend
+        * in usb_phy_bringup_host_cotroller, restart bringing up host
+        * controller as in case of only pmc wakeup.
+        */
+       if (phy->pmc_remote_wakeup && phy->ctrlr_suspended) {
+               usb_phy_bringup_host_controller(phy);
+               if (usb_phy_reg_status_wait(base + USB_PORTSC,
+                       (USB_PORTSC_RESUME | USB_PORTSC_SUSP), 0,
+                               FPR_WAIT_TIME_US) < 0)
+                       pr_err("%s: timeout waiting for SUSPEND to clear\n",
+                               __func__);
+               phy->ctrlr_suspended = false;
+       }
+
        /* Disable PMC master mode by clearing MASTER_EN */
        val = readl(pmc_base + PMC_UHSIC_SLEEP_CFG(inst));
        val &= ~(UHSIC_MASTER_ENABLE(inst));
@@ -2056,8 +2073,11 @@ static void uhsic_phy_restore_end(struct tegra_usb_phy *phy)
 
        DBG("%s(%d)\n", __func__, __LINE__);
 
-       /* check whether we wake up from the remote resume */
-       if (phy->pmc_remote_wakeup) {
+       /*
+        * check whether we wake up from the remote wake detected before putting
+        * controller in suspend in usb_phy_bringup_host_controller.
+        */
+       if (!phy->ctrlr_suspended) {
                /* wait until FPR bit is set automatically on remote resume */
                do {
                        val = readl(base + USB_PORTSC);
@@ -2173,7 +2193,7 @@ static int uhsic_phy_irq(struct tegra_usb_phy *phy)
        /* check if there is any remote wake event */
        usb_phy_fence_read(phy);
        if (uhsic_phy_remotewake_detected(phy))
-               pr_info("%s: uhsic remote wake detected\n", __func__);
+               DBG("%s: uhsic remote wake detected\n", __func__);
        return IRQ_HANDLED;
 }
 
index ddcfdcb..ef7fd79 100644 (file)
@@ -106,6 +106,7 @@ struct tegra_usb_phy {
        bool bus_reseting;
        bool linkphy_init;
        bool hot_plug;
+       bool ctrlr_suspended;
 };
 
 int usb_phy_reg_status_wait(void __iomem *reg, u32 mask,