summaryrefslogtreecommitdiff
path: root/drivers/usb/host/ehci-tegra.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/ehci-tegra.c')
-rw-r--r--drivers/usb/host/ehci-tegra.c85
1 files changed, 84 insertions, 1 deletions
diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c
index 003ae5f20e3a..8193c6edfca9 100644
--- a/drivers/usb/host/ehci-tegra.c
+++ b/drivers/usb/host/ehci-tegra.c
@@ -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);
}
@@ -360,6 +441,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);
}