msm: hsic: Retry port RESUME if unable to send SOFs within 3ms
Due to h/w bug in hsic controller, interrupts are disabled during
resume signalling (~20ms), commit: e404049ae, which may result in
below issues
1. Delayed MDP interrupts causing display stutters
2. Since default workqueue disables preemption for 20ms, the kernel
scheduler schedules out the worker thread for long time. It results
in MDM crash due to delayed response
To avoid busy loop, port resume is performed without disabling the
interrupts. Due to this if resume time constraint is not met then
don't start SOF and retry RESUME after some time. This delay (w/o SOF)
lets the device to enter SUSPEND state (after issuing remote wakeup)
and followed by RESUME after sometime. Also, Move the resume code to
hsic controller driver to avoid changes to ehci upstream code.
CRs-Fixed: 383752
Change-Id: Ic4768587bbb9a00d8cf495065ec9e14c46a5c1d4
Signed-off-by: Vamsi Krishna <vskrishn@codeaurora.org>
Signed-off-by: Manu Gautam <mgautam@codeaurora.org>
diff --git a/drivers/usb/host/ehci-msm-hsic.c b/drivers/usb/host/ehci-msm-hsic.c
index 86b2d45..9ef7e43 100644
--- a/drivers/usb/host/ehci-msm-hsic.c
+++ b/drivers/usb/host/ehci-msm-hsic.c
@@ -36,6 +36,8 @@
#include <linux/gpio.h>
#include <linux/spinlock.h>
#include <linux/irq.h>
+#include <linux/kthread.h>
+#include <linux/wait.h>
#include <mach/msm_bus.h>
#include <mach/clk.h>
@@ -50,6 +52,18 @@
#define USB_REG_END_OFFSET 0x250
static struct workqueue_struct *ehci_wq;
+struct ehci_timer {
+#define GPT_LD(p) ((p) & 0x00FFFFFF)
+ u32 gptimer0_ld;
+#define GPT_RUN BIT(31)
+#define GPT_RESET BIT(30)
+#define GPT_MODE BIT(24)
+#define GPT_CNT(p) ((p) & 0x00FFFFFF)
+ u32 gptimer0_ctrl;
+
+ u32 gptimer1_ld;
+ u32 gptimer1_ctrl;
+};
struct msm_hsic_hcd {
struct ehci_hcd ehci;
@@ -74,6 +88,13 @@
struct work_struct bus_vote_w;
bool bus_vote;
+
+ /* gp timer */
+ struct ehci_timer __iomem *timer;
+ struct completion gpt0_completion;
+ struct completion rt_completion;
+ int resume_status;
+ int resume_again;
};
struct msm_hsic_hcd *__mehci;
@@ -806,9 +827,12 @@
__func__, ret);
}
+#define STS_GPTIMER0_INTERRUPT BIT(24)
static irqreturn_t msm_hsic_irq(struct usb_hcd *hcd)
{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd);
+ u32 status;
if (atomic_read(&mehci->in_lpm)) {
disable_irq_nosync(hcd->irq);
@@ -818,14 +842,38 @@
return IRQ_HANDLED;
}
+ status = ehci_readl(ehci, &ehci->regs->status);
+
+ if (status & STS_GPTIMER0_INTERRUPT) {
+ int timeleft;
+
+ dbg_log_event(NULL, "FPR: gpt0_isr", 0);
+
+ timeleft = GPT_CNT(ehci_readl(ehci,
+ &mehci->timer->gptimer1_ctrl));
+ if (timeleft) {
+ ehci_writel(ehci, ehci_readl(ehci,
+ &ehci->regs->command) | CMD_RUN,
+ &ehci->regs->command);
+ } else
+ mehci->resume_again = 1;
+
+ dbg_log_event(NULL, "FPR: timeleft", timeleft);
+
+ complete(&mehci->gpt0_completion);
+ ehci_writel(ehci, STS_GPTIMER0_INTERRUPT, &ehci->regs->status);
+ }
+
return ehci_irq(hcd);
}
static int ehci_hsic_reset(struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd);
int retval;
+ mehci->timer = USB_HS_GPTIMER_BASE;
ehci->caps = USB_CAPLENGTH;
ehci->regs = USB_CAPLENGTH +
HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase));
@@ -875,10 +923,191 @@
return ehci_bus_suspend(hcd);
}
+#define RESUME_RETRY_LIMIT 3
+#define RESUME_SIGNAL_TIME_MS (21 * 999)
+#define RESUME_SIGNAL_TIME_SOF_MS (23 * 999)
+static int msm_hsic_resume_thread(void *data)
+{
+ struct msm_hsic_hcd *mehci = data;
+ struct usb_hcd *hcd = hsic_to_hcd(mehci);
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ u32 temp;
+ unsigned long resume_needed = 0;
+ int retry_cnt = 0;
+ int tight_resume = 0;
+
+ dbg_log_event(NULL, "Resume RH", 0);
+
+ /* keep delay between bus states */
+ if (time_before(jiffies, ehci->next_statechange))
+ usleep_range(5000, 5000);
+
+ spin_lock_irq(&ehci->lock);
+ if (!HCD_HW_ACCESSIBLE(hcd)) {
+ spin_unlock_irq(&ehci->lock);
+ mehci->resume_status = -ESHUTDOWN;
+ complete(&mehci->rt_completion);
+ return 0;
+ }
+
+ if (unlikely(ehci->debug)) {
+ if (!dbgp_reset_prep())
+ ehci->debug = NULL;
+ else
+ dbgp_external_startup();
+ }
+
+ /* at least some APM implementations will try to deliver
+ * IRQs right away, so delay them until we're ready.
+ */
+ ehci_writel(ehci, 0, &ehci->regs->intr_enable);
+
+ /* re-init operational registers */
+ ehci_writel(ehci, 0, &ehci->regs->segment);
+ ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list);
+ ehci_writel(ehci, (u32) ehci->async->qh_dma, &ehci->regs->async_next);
+
+ /*CMD_RUN will be set after, PORT_RESUME gets cleared*/
+ if (ehci->resume_sof_bug)
+ ehci->command &= ~CMD_RUN;
+
+ /* restore CMD_RUN, framelist size, and irq threshold */
+ ehci_writel(ehci, ehci->command, &ehci->regs->command);
+
+ /* manually resume the ports we suspended during bus_suspend() */
+resume_again:
+ if (retry_cnt >= RESUME_RETRY_LIMIT) {
+ pr_info("retry count(%d) reached max, resume in tight loop\n",
+ retry_cnt);
+ tight_resume = 1;
+ }
+
+
+ temp = ehci_readl(ehci, &ehci->regs->port_status[0]);
+ temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
+ if (test_bit(0, &ehci->bus_suspended) && (temp & PORT_SUSPEND)) {
+ temp |= PORT_RESUME;
+ set_bit(0, &resume_needed);
+ }
+ dbg_log_event(NULL, "FPR: Set", temp);
+ ehci_writel(ehci, temp, &ehci->regs->port_status[0]);
+
+ /* HSIC controller has a h/w bug due to which it can try to send SOFs
+ * (start of frames) during port resume resulting in phy lockup. HSIC hw
+ * controller in MSM clears FPR bit after driving the resume signal for
+ * 20ms. Workaround is to stop SOFs before driving resume and then start
+ * sending SOFs immediately. Need to send SOFs within 3ms of resume
+ * completion otherwise peripheral may enter undefined state. As
+ * usleep_range does not gurantee exact sleep time, GPTimer is used to
+ * to time the resume sequence. If driver exceeds allowable time SOFs,
+ * repeat the resume process.
+ */
+ if (ehci->resume_sof_bug && resume_needed) {
+ if (!tight_resume) {
+ mehci->resume_again = 0;
+ ehci_writel(ehci, GPT_LD(RESUME_SIGNAL_TIME_MS),
+ &mehci->timer->gptimer0_ld);
+ ehci_writel(ehci, GPT_RESET | GPT_RUN,
+ &mehci->timer->gptimer0_ctrl);
+ ehci_writel(ehci, INTR_MASK | STS_GPTIMER0_INTERRUPT,
+ &ehci->regs->intr_enable);
+
+ ehci_writel(ehci, GPT_LD(RESUME_SIGNAL_TIME_SOF_MS),
+ &mehci->timer->gptimer1_ld);
+ ehci_writel(ehci, GPT_RESET | GPT_RUN,
+ &mehci->timer->gptimer1_ctrl);
+
+ spin_unlock_irq(&ehci->lock);
+ wait_for_completion(&mehci->gpt0_completion);
+ spin_lock_irq(&ehci->lock);
+ } else {
+ dbg_log_event(NULL, "FPR: Tightloop", 0);
+ /* do the resume in a tight loop */
+ handshake(ehci, &ehci->regs->port_status[0],
+ PORT_RESUME, 0, 22 * 1000);
+ ehci_writel(ehci, ehci_readl(ehci,
+ &ehci->regs->command) | CMD_RUN,
+ &ehci->regs->command);
+ }
+
+ if (mehci->resume_again) {
+ int temp;
+
+ dbg_log_event(NULL, "FPR: Re-Resume", retry_cnt);
+ pr_info("FPR: retry count: %d\n", retry_cnt);
+ spin_unlock_irq(&ehci->lock);
+ temp = ehci_readl(ehci, &ehci->regs->port_status[0]);
+ temp &= ~PORT_RWC_BITS;
+ temp |= PORT_SUSPEND;
+ ehci_writel(ehci, temp, &ehci->regs->port_status[0]);
+ /* Keep the bus idle for 5ms so that peripheral
+ * can detect and initiate suspend
+ */
+ usleep_range(5000, 5000);
+ dbg_log_event(NULL,
+ "FPR: RResume",
+ ehci_readl(ehci, &ehci->regs->port_status[0]));
+ spin_lock_irq(&ehci->lock);
+ mehci->resume_again = 0;
+ retry_cnt++;
+ goto resume_again;
+ }
+ }
+
+ dbg_log_event(NULL, "FPR: RT-Done", 0);
+ mehci->resume_status = 1;
+ spin_unlock_irq(&ehci->lock);
+
+ complete(&mehci->rt_completion);
+
+ return 0;
+}
+
static int ehci_hsic_bus_resume(struct usb_hcd *hcd)
{
- dbg_log_event(NULL, "Resume RH", 0);
- return ehci_bus_resume(hcd);
+ struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd);
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ u32 temp;
+ struct task_struct *resume_thread = NULL;
+
+ mehci->resume_status = 0;
+ resume_thread = kthread_run(msm_hsic_resume_thread,
+ mehci, "hsic_resume_thread");
+ if (IS_ERR(resume_thread)) {
+ pr_err("Error creating resume thread:%lu\n",
+ PTR_ERR(resume_thread));
+ return PTR_ERR(resume_thread);
+ }
+
+ wait_for_completion(&mehci->rt_completion);
+
+ if (mehci->resume_status < 0)
+ return mehci->resume_status;
+
+ dbg_log_event(NULL, "FPR: Wokeup", 0);
+ spin_lock_irq(&ehci->lock);
+ (void) ehci_readl(ehci, &ehci->regs->command);
+
+ temp = 0;
+ if (ehci->async->qh_next.qh)
+ temp |= CMD_ASE;
+ if (ehci->periodic_sched)
+ temp |= CMD_PSE;
+ if (temp) {
+ ehci->command |= temp;
+ ehci_writel(ehci, ehci->command, &ehci->regs->command);
+ }
+
+ ehci->next_statechange = jiffies + msecs_to_jiffies(5);
+ hcd->state = HC_STATE_RUNNING;
+ ehci->rh_state = EHCI_RH_RUNNING;
+
+ /* Now we can safely re-enable irqs */
+ ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable);
+
+ spin_unlock_irq(&ehci->lock);
+
+ return 0;
}
static struct hc_driver msm_hsic_driver = {
@@ -1328,6 +1557,8 @@
goto deinit_clocks;
}
+ init_completion(&mehci->rt_completion);
+ init_completion(&mehci->gpt0_completion);
ret = msm_hsic_reset(mehci);
if (ret) {
dev_err(&pdev->dev, "unable to initialize PHY\n");