usb: ehci: Avoid phy lockup due to SOFs during port reset
There could be race condition between h/w initiating sof
(start of frames) and finishing port reset. This can
cause hsic phy lockup, which results in enumeration failure.
Avoid this race condition by:
1. Moving to the old enumeration sequence where set_address is
initiated before get_device descriptor. This results in one
port reset instead of two port resets.
2. Halt usb controller before initiating port reset and start it after
port reset is complete. This is tricky because PORT_RESET bit
automatically clears when h/w completes the reset and failure to
start the controller within 3ms causes the peripheral device to
suspend resulting in enumeration failure. Hence, after setting
port reset bit forcefully drive strobe/data lines to reset using
ulpi interface and once port reset is complete, disable forceful
reset and start the controller in atomic context.
CRs-fixed: 364458, 359930
Change-Id: I49a2eac8043eb3001956c7ee9ead2c3a901524db
Signed-off-by: Vamsi Krishna <vskrishn@codeaurora.org>
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c
index caf86ca..3098fbe 100644
--- a/drivers/usb/host/ehci-hub.c
+++ b/drivers/usb/host/ehci-hub.c
@@ -823,7 +823,7 @@
u32 __iomem *status_reg = &ehci->regs->port_status[
(wIndex & 0xff) - 1];
u32 __iomem *hostpc_reg = NULL;
- u32 temp, temp1, status;
+ u32 temp, temp1, status, cmd = 0;
unsigned long flags;
int retval = 0;
unsigned selector;
@@ -1202,7 +1202,32 @@
ehci->reset_done [wIndex] = jiffies
+ msecs_to_jiffies (50);
}
+
+ if (ehci->reset_sof_bug && (temp & PORT_RESET)) {
+ cmd = ehci_readl(ehci, &ehci->regs->command);
+ cmd &= ~CMD_RUN;
+ ehci_writel(ehci, cmd, &ehci->regs->command);
+ if (handshake(ehci, &ehci->regs->status,
+ STS_HALT, STS_HALT, 16 * 125))
+ ehci_info(ehci,
+ "controller halt failed\n");
+ }
ehci_writel(ehci, temp, status_reg);
+ if (ehci->reset_sof_bug && (temp & PORT_RESET)
+ && hcd->driver->enable_ulpi_control) {
+ hcd->driver->enable_ulpi_control(hcd,
+ PORT_RESET);
+ spin_unlock_irqrestore(&ehci->lock, flags);
+ usleep_range(50000, 55000);
+ if (handshake(ehci, status_reg,
+ PORT_RESET, 0, 10 * 1000))
+ ehci_info(ehci,
+ "failed to clear reset\n");
+ spin_lock_irqsave(&ehci->lock, flags);
+ hcd->driver->disable_ulpi_control(hcd);
+ cmd |= CMD_RUN;
+ ehci_writel(ehci, cmd, &ehci->regs->command);
+ }
break;
/* For downstream facing ports (these): one hub port is put
diff --git a/drivers/usb/host/ehci-msm-hsic.c b/drivers/usb/host/ehci-msm-hsic.c
index 1c38bc9..a95198c 100644
--- a/drivers/usb/host/ehci-msm-hsic.c
+++ b/drivers/usb/host/ehci-msm-hsic.c
@@ -328,6 +328,29 @@
}
+static int ulpi_read(struct msm_hsic_hcd *mehci, u32 reg)
+{
+ struct usb_hcd *hcd = hsic_to_hcd(mehci);
+ unsigned long timeout;
+
+ /* initiate read operation */
+ writel_relaxed(ULPI_RUN | ULPI_READ | ULPI_ADDR(reg),
+ USB_ULPI_VIEWPORT);
+
+ /* wait for completion */
+ timeout = jiffies + usecs_to_jiffies(ULPI_IO_TIMEOUT_USEC);
+ while (readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_RUN) {
+ if (time_after(jiffies, timeout)) {
+ dev_err(mehci->dev, "ulpi_read: timeout %08x\n",
+ readl_relaxed(USB_ULPI_VIEWPORT));
+ return -ETIMEDOUT;
+ }
+ udelay(1);
+ }
+
+ return ULPI_DATA_READ(readl_relaxed(USB_ULPI_VIEWPORT));
+}
+
static int ulpi_write(struct msm_hsic_hcd *mehci, u32 val, u32 reg)
{
struct usb_hcd *hcd = hsic_to_hcd(mehci);
@@ -354,6 +377,37 @@
return 0;
}
+#define HSIC_DBG1 0X38
+#define ULPI_MANUAL_ENABLE BIT(4)
+#define ULPI_LINESTATE_DATA BIT(5)
+#define ULPI_LINESTATE_STROBE BIT(6)
+static void ehci_msm_enable_ulpi_control(struct usb_hcd *hcd, u32 linestate)
+{
+ struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd);
+ int val;
+
+ switch (linestate) {
+ case PORT_RESET:
+ val = ulpi_read(mehci, HSIC_DBG1);
+ val |= ULPI_MANUAL_ENABLE;
+ val &= ~(ULPI_LINESTATE_DATA | ULPI_LINESTATE_STROBE);
+ ulpi_write(mehci, val, HSIC_DBG1);
+ break;
+ default:
+ pr_info("%s: Unknown linestate:%0x\n", __func__, linestate);
+ }
+}
+
+static void ehci_msm_disable_ulpi_control(struct usb_hcd *hcd)
+{
+ struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd);
+ int val;
+
+ val = ulpi_read(mehci, HSIC_DBG1);
+ val &= ~ULPI_MANUAL_ENABLE;
+ ulpi_write(mehci, val, HSIC_DBG1);
+}
+
static int msm_hsic_config_gpios(struct msm_hsic_hcd *mehci, int gpio_en)
{
int rc = 0;
@@ -788,7 +842,7 @@
* generic hardware linkage
*/
.irq = msm_hsic_irq,
- .flags = HCD_USB2 | HCD_MEMORY,
+ .flags = HCD_USB2 | HCD_MEMORY | HCD_OLD_ENUM,
.reset = ehci_hsic_reset,
.start = ehci_run,
@@ -825,6 +879,9 @@
.bus_resume = ehci_hsic_bus_resume,
.log_urb_complete = dbg_log_event,
+
+ .enable_ulpi_control = ehci_msm_enable_ulpi_control,
+ .disable_ulpi_control = ehci_msm_disable_ulpi_control,
};
static int msm_hsic_init_clocks(struct msm_hsic_hcd *mehci, u32 init)
@@ -1187,6 +1244,7 @@
mehci->dev = &pdev->dev;
mehci->ehci.susp_sof_bug = 1;
+ mehci->ehci.reset_sof_bug = 1;
mehci->ehci.max_log2_irq_thresh = 6;
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 6afb70b..5754170 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -152,6 +152,7 @@
unsigned has_synopsys_hc_bug:1; /* Synopsys HC */
unsigned frame_index_bug:1; /* MosChip (AKA NetMos) */
unsigned susp_sof_bug:1; /*Chip Idea HC*/
+ unsigned reset_sof_bug:1; /*Chip Idea HC*/
/* required for usb32 quirk */
#define OHCI_CTRL_HCFS (3 << 6)