usb: hsic_hcd: fix failure to re-suspend after remote wakeup

A disabled IRQ that is configured as a wakeup interrupt will trigger
a system resume, however since it is disabled, its handler will never
get called and its status register is never cleared. Thus upon
subsequent suspends it causes the system to immediately resume.

This was happening in the EHCI HSIC controller, in which remote wakeup
signaling on the STROBE line is treated as a wakeup IRQ. After the
first successful wakeup sequence, when the HSIC bus goes back into low
power mode the lingering uncleared interrupt prevents a system suspend
from executing successfully. This is fixed by enabling the IRQ in the
system suspend_noirq callback. The IRQ is subsequently disabled in
either the IRQ handler or the resume callback, whichever occurs first.

CRs-fixed: 343414
Change-Id: Ifc1c267408e76b98b8d848abde6eb44df9e0cdc0
Signed-off-by: Jack Pham <jackp@codeaurora.org>
diff --git a/drivers/usb/host/ehci-msm-hsic.c b/drivers/usb/host/ehci-msm-hsic.c
index 795e7a8..2d43a78 100644
--- a/drivers/usb/host/ehci-msm-hsic.c
+++ b/drivers/usb/host/ehci-msm-hsic.c
@@ -24,7 +24,6 @@
 
 #include <linux/platform_device.h>
 #include <linux/clk.h>
-#include <linux/irq.h>
 #include <linux/err.h>
 #include <linux/wakelock.h>
 #include <linux/pm_runtime.h>
@@ -55,6 +54,7 @@
 	struct wake_lock	wlock;
 	int			peripheral_status_irq;
 	int			wakeup_irq;
+	bool			wakeup_irq_enabled;
 };
 
 static inline struct msm_hsic_hcd *hcd_to_hsic(struct usb_hcd *hcd)
@@ -750,6 +750,12 @@
 
 	dev_dbg(mehci->dev, "%s: hsic remote wakeup interrupt\n", __func__);
 
+	if (mehci->wakeup_irq_enabled) {
+		mehci->wakeup_irq_enabled = 0;
+		disable_irq_wake(irq);
+		disable_irq_nosync(irq);
+	}
+
 	return IRQ_HANDLED;
 }
 
@@ -878,7 +884,6 @@
 				"msm_hsic_wakeup", mehci);
 		if (!ret) {
 			disable_irq_nosync(mehci->wakeup_irq);
-			enable_irq_wake(mehci->wakeup_irq);
 		} else {
 			dev_err(&pdev->dev, "request_irq(%d) failed: %d\n",
 					mehci->wakeup_irq, ret);
@@ -923,8 +928,10 @@
 
 	if (mehci->peripheral_status_irq)
 		free_irq(mehci->peripheral_status_irq, mehci);
+
 	if (mehci->wakeup_irq) {
-		disable_irq_wake(mehci->wakeup_irq);
+		if (mehci->wakeup_irq_enabled)
+			disable_irq_wake(mehci->wakeup_irq);
 		free_irq(mehci->wakeup_irq, mehci);
 	}
 
@@ -959,7 +966,22 @@
 		enable_irq_wake(hcd->irq);
 
 	return msm_hsic_suspend(mehci);
+}
 
+static int msm_hsic_pm_suspend_noirq(struct device *dev)
+{
+	struct usb_hcd *hcd = dev_get_drvdata(dev);
+	struct msm_hsic_hcd *mehci = hcd_to_hsic(hcd);
+
+	dev_dbg(dev, "ehci-msm-hsic PM suspend_noirq\n");
+
+	if (device_may_wakeup(dev) && !mehci->wakeup_irq_enabled) {
+		enable_irq(mehci->wakeup_irq);
+		enable_irq_wake(mehci->wakeup_irq);
+		mehci->wakeup_irq_enabled = 1;
+	}
+
+	return 0;
 }
 
 static int msm_hsic_pm_resume(struct device *dev)
@@ -973,6 +995,12 @@
 	if (device_may_wakeup(dev))
 		disable_irq_wake(hcd->irq);
 
+	if (mehci->wakeup_irq_enabled) {
+		mehci->wakeup_irq_enabled = 0;
+		disable_irq_wake(mehci->wakeup_irq);
+		disable_irq_nosync(mehci->wakeup_irq);
+	}
+
 	ret = msm_hsic_resume(mehci);
 	if (ret)
 		return ret;
@@ -1016,6 +1044,7 @@
 #ifdef CONFIG_PM
 static const struct dev_pm_ops msm_hsic_dev_pm_ops = {
 	SET_SYSTEM_SLEEP_PM_OPS(msm_hsic_pm_suspend, msm_hsic_pm_resume)
+	.suspend_noirq = msm_hsic_pm_suspend_noirq,
 	SET_RUNTIME_PM_OPS(msm_hsic_runtime_suspend, msm_hsic_runtime_resume,
 				msm_hsic_runtime_idle)
 };