USB: EHCI: Add support to detect docking station connection and removal

This change implements the docking station detection by reading the status
of MPP9 on PMIC8921. With this change VREG_5V(USB port 3 VBUS) is turned on
only when docking station is connected to Liquid platform in order to save
power.

Change-Id: I4282b4273adabf8aeaf8faad43cd2cfff36eeac6
Signed-off-by: Hemant Kumar <hemantk@codeaurora.org>
diff --git a/drivers/usb/host/ehci-msm2.c b/drivers/usb/host/ehci-msm2.c
index 4f6fe3e..c9a020f 100644
--- a/drivers/usb/host/ehci-msm2.c
+++ b/drivers/usb/host/ehci-msm2.c
@@ -48,6 +48,7 @@
 	struct regulator			*hsusb_1p8;
 	struct regulator			*vbus;
 	bool					async_int;
+	bool					vbus_on;
 	atomic_t				in_lpm;
 	struct wake_lock			wlock;
 };
@@ -205,29 +206,6 @@
 }
 #endif
 
-static int msm_ehci_init_vbus(struct msm_hcd *mhcd, int init)
-{
-	struct usb_hcd *hcd = mhcd_to_hcd(mhcd);
-	struct msm_usb_host_platform_data *pdata;
-
-	if (!init) {
-		regulator_put(mhcd->vbus);
-		return 0;
-	}
-
-	mhcd->vbus = regulator_get(mhcd->dev, "vbus");
-	if (IS_ERR(mhcd->vbus)) {
-		pr_err("Unable to get vbus\n");
-		return -ENODEV;
-	}
-
-	pdata = mhcd->dev->platform_data;
-	if (pdata)
-		hcd->power_budget = pdata->power_budget;
-
-	return 0;
-}
-
 static void msm_ehci_vbus_power(struct msm_hcd *mhcd, bool on)
 {
 	int ret;
@@ -236,21 +214,86 @@
 		pr_err("vbus is NULL.");
 		return;
 	}
+
+	if (mhcd->vbus_on == on)
+		return;
+
 	if (on) {
 		ret = regulator_enable(mhcd->vbus);
 		if (ret) {
 			pr_err("unable to enable vbus\n");
 			return;
 		}
+		mhcd->vbus_on = true;
 	} else {
 		ret = regulator_disable(mhcd->vbus);
 		if (ret) {
 			pr_err("unable to disable vbus\n");
 			return;
 		}
+		mhcd->vbus_on = false;
 	}
 }
 
+static irqreturn_t msm_ehci_dock_connect_irq(int irq, void *data)
+{
+	const struct msm_usb_host_platform_data *pdata;
+	struct msm_hcd *mhcd = data;
+	struct usb_hcd *hcd = mhcd_to_hcd(mhcd);
+
+	pdata = mhcd->dev->platform_data;
+
+	if (atomic_read(&mhcd->in_lpm))
+		usb_hcd_resume_root_hub(hcd);
+
+	if (irq_read_line(pdata->dock_connect_irq)) {
+		dev_dbg(mhcd->dev, "%s:Dock removed disable vbus\n", __func__);
+		msm_ehci_vbus_power(mhcd, 0);
+	} else {
+		dev_dbg(mhcd->dev, "%s:Dock connected enable vbus\n", __func__);
+		msm_ehci_vbus_power(mhcd, 1);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int msm_ehci_init_vbus(struct msm_hcd *mhcd, int init)
+{
+	int rc = 0;
+	struct usb_hcd *hcd = mhcd_to_hcd(mhcd);
+	const struct msm_usb_host_platform_data *pdata;
+
+	pdata = mhcd->dev->platform_data;
+
+	if (!init) {
+		regulator_put(mhcd->vbus);
+		if (pdata && pdata->dock_connect_irq)
+			free_irq(pdata->dock_connect_irq, mhcd);
+		return rc;
+	}
+
+	mhcd->vbus = regulator_get(mhcd->dev, "vbus");
+	if (IS_ERR(mhcd->vbus)) {
+		pr_err("Unable to get vbus\n");
+		return -ENODEV;
+	}
+
+	if (pdata) {
+		hcd->power_budget = pdata->power_budget;
+
+		if (pdata->dock_connect_irq) {
+			rc = request_threaded_irq(pdata->dock_connect_irq, NULL,
+					msm_ehci_dock_connect_irq,
+					IRQF_TRIGGER_FALLING |
+					IRQF_TRIGGER_RISING |
+					IRQF_ONESHOT, "msm_ehci_host", mhcd);
+			if (!rc)
+				enable_irq_wake(pdata->dock_connect_irq);
+		}
+	}
+	return rc;
+}
+
 static int msm_ehci_ldo_enable(struct msm_hcd *mhcd, int on)
 {
 	int ret = 0;
@@ -772,6 +815,7 @@
 	struct usb_hcd *hcd;
 	struct resource *res;
 	struct msm_hcd *mhcd;
+	const struct msm_usb_host_platform_data *pdata;
 	int ret;
 
 	dev_dbg(&pdev->dev, "ehci_msm2 probe\n");
@@ -859,8 +903,10 @@
 		goto vbus_deinit;
 	}
 
-	/*TBD:for now enable vbus here*/
-	msm_ehci_vbus_power(mhcd, 1);
+	pdata = mhcd->dev->platform_data;
+	if (pdata && (!pdata->dock_connect_irq ||
+				!irq_read_line(pdata->dock_connect_irq)))
+		msm_ehci_vbus_power(mhcd, 1);
 
 	device_init_wakeup(&pdev->dev, 1);
 	wake_lock_init(&mhcd->wlock, WAKE_LOCK_SUSPEND, dev_name(&pdev->dev));
@@ -903,6 +949,7 @@
 	pm_runtime_set_suspended(&pdev->dev);
 
 	usb_remove_hcd(hcd);
+
 	msm_ehci_vbus_power(mhcd, 0);
 	msm_ehci_init_vbus(mhcd, 0);
 	msm_ehci_ldo_enable(mhcd, 0);