USB: dwc3: Add support for host bus suspend

Add required support for host bus suspend for dwc3 driver. Also add
pm runtime callbacks for xhci platform device which will defer  putting
USB hardware in/out of low power mode to transciever driver. Whenever USB
core autosuspend timer expires, USB2 and USB3 roothubs are suspended, then
xhci platform device is runtime suspended. when xhci's runtime suspend
happens, it will call USB transciever's set_suspend callback which puts
USB hardware in low power mode.

When remote wakeup happens, dwc3 driver will bring USB hardware out of low
power mode. Then xhci stack will resume roothub port on which port change
is detected. Set skip resume flag to resume USB only on remote wakeup.

When micro A cable is disconnected during system suspend, xhci platform
device will be removed before PM resume of xhci device. Due to this,disable
depth counter mismatch happens and xhci device will not not be pm runtime
enabled on next cable connect. Fix this issue by calling pm_runtime_init
for xhci platform device.

CRs-Fixed: 422306
Change-Id: Ia06f792979d7bd3248ae40161978f9632ccd7808
Signed-off-by: Vijayavardhan Vennapusa <vvreddy@codeaurora.org>
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index 0b46082..6875b74 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -1,6 +1,7 @@
 ccflags-$(CONFIG_USB_DWC3_DEBUG)	:= -DDEBUG
 ccflags-$(CONFIG_USB_DWC3_VERBOSE)	+= -DVERBOSE_DEBUG
 ccflags-y += -Idrivers/usb/host
+ccflags-y += -Idrivers/base/power
 
 obj-$(CONFIG_USB_DWC3)			+= dwc3.o
 
diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c
index 1ee0828..3ae6f22 100644
--- a/drivers/usb/dwc3/dwc3-msm.c
+++ b/drivers/usb/dwc3/dwc3-msm.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -1459,6 +1459,7 @@
 {
 	int ret;
 	bool dcp;
+	bool host_bus_suspend;
 
 	dev_dbg(mdwc->dev, "%s: entering lpm\n", __func__);
 
@@ -1481,17 +1482,7 @@
 	}
 
 	dcp = mdwc->charger.chg_type == DWC3_DCP_CHARGER;
-
-	/* Sequence to put hardware in low power state:
-	 * 1. Set OTGDISABLE to disable OTG block in HSPHY (saves power)
-	 * 2. Clear charger detection control fields (performed above)
-	 * 3. SUSPEND PHY and turn OFF core clock after some delay
-	 * 4. Clear interrupt latch register and enable BSV, ID HV interrupts
-	 * 5. Enable PHY retention
-	 */
-	dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG, 0x1000, 0x1000);
-	dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG,
-						0xC00000, 0x800000);
+	host_bus_suspend = mdwc->host_mode == 1;
 
 	/* Sequence to put SSPHY in low power state:
 	 * 1. Clear REF_SS_PHY_EN in SS_PHY_CTRL_REG
@@ -1507,10 +1498,43 @@
 	usleep_range(1000, 1200);
 	clk_disable_unprepare(mdwc->ref_clk);
 
-	dwc3_msm_write_reg(mdwc->base, HS_PHY_IRQ_STAT_REG, 0xFFF);
-	dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG, 0x18000, 0x18000);
-	if (!dcp)
+	if (host_bus_suspend) {
+		/* Sequence for host bus suspend case:
+		 * 1. Set suspend and sleep bits in GUSB2PHYCONFIG reg
+		 * 2. Clear interrupt latch register and enable BSV, ID HV intr
+		 * 3. Enable DP and DM HV interrupts in ALT_INTERRUPT_EN_REG
+		 * 4. Enable PHY retention
+		 */
+		dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0),
+			dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)) |
+								0x00000140);
+		dwc3_msm_write_reg(mdwc->base, HS_PHY_IRQ_STAT_REG, 0xFFF);
+		if (mdwc->otg_xceiv && (!mdwc->ext_xceiv.otg_capability))
+			dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG,
+							 0x18000, 0x18000);
+		dwc3_msm_write_reg(mdwc->base, ALT_INTERRUPT_EN_REG, 0x00A);
 		dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG, 0x2, 0x0);
+		udelay(5);
+	} else {
+		/* Sequence to put hardware in low power state:
+		 * 1. Set OTGDISABLE to disable OTG block in HSPHY (saves power)
+		 * 2. Clear charger detection control fields (performed above)
+		 * 3. SUSPEND PHY and turn OFF core clock after some delay
+		 * 4. Clear interrupt latch register and enable BSV, ID HV intr
+		 * 5. Enable PHY retention
+		 */
+		dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG, 0x1000,
+									0x1000);
+		dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG,
+							0xC00000, 0x800000);
+		dwc3_msm_write_reg(mdwc->base, HS_PHY_IRQ_STAT_REG, 0xFFF);
+		if (mdwc->otg_xceiv && (!mdwc->ext_xceiv.otg_capability))
+			dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG,
+							0x18000, 0x18000);
+		if (!dcp)
+			dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG,
+								0x2, 0x0);
+	}
 
 	/* make sure above writes are completed before turning off clocks */
 	wmb();
@@ -1530,7 +1554,8 @@
 			dev_err(mdwc->dev, "Failed to reset bus bw vote\n");
 	}
 
-	if (mdwc->otg_xceiv && mdwc->ext_xceiv.otg_capability && !dcp)
+	if (mdwc->otg_xceiv && mdwc->ext_xceiv.otg_capability && !dcp &&
+							!host_bus_suspend)
 		dwc3_hsusb_ldo_enable(0);
 
 	dwc3_ssusb_ldo_enable(0);
@@ -1551,6 +1576,7 @@
 {
 	int ret;
 	bool dcp;
+	bool host_bus_suspend;
 
 	dev_dbg(mdwc->dev, "%s: exiting lpm\n", __func__);
 
@@ -1575,7 +1601,9 @@
 						__func__, ret);
 
 	dcp = mdwc->charger.chg_type == DWC3_DCP_CHARGER;
-	if (mdwc->otg_xceiv && mdwc->ext_xceiv.otg_capability && !dcp)
+	host_bus_suspend = mdwc->host_mode == 1;
+	if (mdwc->otg_xceiv && mdwc->ext_xceiv.otg_capability && !dcp &&
+							!host_bus_suspend)
 		dwc3_hsusb_ldo_enable(1);
 
 	dwc3_ssusb_ldo_enable(1);
@@ -1587,20 +1615,41 @@
 	clk_prepare_enable(mdwc->iface_clk);
 	clk_prepare_enable(mdwc->core_clk);
 
-	/* Disable HV interrupt */
-	dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG, 0x18000, 0x0);
-	/* Disable Retention */
-	dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG, 0x2, 0x2);
+	if (host_bus_suspend) {
+		/* Disable HV interrupt */
+		if (mdwc->otg_xceiv && (!mdwc->ext_xceiv.otg_capability))
+			dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG,
+							0x18000, 0x0);
+		/* Clear interrupt latch register */
+		dwc3_msm_write_reg(mdwc->base, HS_PHY_IRQ_STAT_REG, 0x000);
 
-	dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0),
-	      dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)) | 0xF0000000);
-	/* 10usec delay required before de-asserting PHY RESET */
-	udelay(10);
-	dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0),
-	      dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)) & 0x7FFFFFFF);
+		/* Disable DP and DM HV interrupt */
+		dwc3_msm_write_reg(mdwc->base, ALT_INTERRUPT_EN_REG, 0x000);
 
-	/* Bring PHY out of suspend */
-	dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG, 0xC00000, 0x0);
+		/* Disable Retention */
+		dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG, 0x2, 0x2);
+	} else {
+		/* Disable HV interrupt */
+		if (mdwc->otg_xceiv && (!mdwc->ext_xceiv.otg_capability))
+			dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG,
+								0x18000, 0x0);
+		/* Disable Retention */
+		dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG, 0x2, 0x2);
+
+		dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0),
+			dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)) |
+								 0xF0000000);
+		/* 10usec delay required before de-asserting PHY RESET */
+		udelay(10);
+		dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0),
+		      dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)) &
+								0x7FFFFFFF);
+
+		/* Bring PHY out of suspend */
+		dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG, 0xC00000,
+									0x0);
+
+	}
 
 	/* Assert SS PHY RESET */
 	dwc3_msm_write_readback(mdwc->base, SS_PHY_CTRL_REG, (1 << 7),
@@ -2186,22 +2235,24 @@
 	msm->charger.charging_disabled = of_property_read_bool(node,
 				"qcom,charging-disabled");
 
-	if (!msm->ext_xceiv.otg_capability) {
-		/* DWC3 has separate IRQ line for OTG events (ID/BSV etc.) */
-		msm->hs_phy_irq = platform_get_irq_byname(pdev, "hs_phy_irq");
-		if (msm->hs_phy_irq < 0) {
-			dev_dbg(&pdev->dev, "pget_irq for hs_phy_irq failed\n");
-			msm->hs_phy_irq = 0;
-		} else {
-			ret = request_irq(msm->hs_phy_irq, msm_dwc3_irq,
-					IRQF_TRIGGER_RISING, "msm_dwc3", msm);
-			if (ret) {
-				dev_err(&pdev->dev, "irqreq HSPHYINT failed\n");
-				goto disable_hs_ldo;
-			}
-			enable_irq_wake(msm->hs_phy_irq);
-		}
+	/*
+	 * DWC3 has separate IRQ line for OTG events (ID/BSV) and for
+	 * DP and DM linestate transitions during low power mode.
+	 */
+	msm->hs_phy_irq = platform_get_irq_byname(pdev, "hs_phy_irq");
+	if (msm->hs_phy_irq < 0) {
+		dev_dbg(&pdev->dev, "pget_irq for hs_phy_irq failed\n");
+		msm->hs_phy_irq = 0;
 	} else {
+		ret = request_irq(msm->hs_phy_irq, msm_dwc3_irq,
+				IRQF_TRIGGER_RISING, "msm_dwc3", msm);
+		if (ret) {
+			dev_err(&pdev->dev, "irqreq HSPHYINT failed\n");
+			goto disable_hs_ldo;
+		}
+		enable_irq_wake(msm->hs_phy_irq);
+	}
+	if (msm->ext_xceiv.otg_capability) {
 		/* Use ADC for ID pin detection */
 		queue_delayed_work(system_nrt_wq, &msm->init_adc_work, 0);
 		device_create_file(&pdev->dev, &dev_attr_adc_enable);
diff --git a/drivers/usb/dwc3/dwc3_otg.c b/drivers/usb/dwc3/dwc3_otg.c
index eb879e3..4980337 100644
--- a/drivers/usb/dwc3/dwc3_otg.c
+++ b/drivers/usb/dwc3/dwc3_otg.c
@@ -1,7 +1,7 @@
 /**
  * dwc3_otg.c - DesignWare USB3 DRD Controller OTG
  *
- * Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -55,6 +55,25 @@
 	}
 }
 
+static int dwc3_otg_set_suspend(struct usb_phy *phy, int suspend)
+{
+	struct usb_otg *otg = phy->otg;
+	struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg);
+
+	if (dotg->host_bus_suspend == suspend)
+		return 0;
+
+	dotg->host_bus_suspend = suspend;
+	if (suspend) {
+		pm_runtime_put_sync(phy->dev);
+	} else {
+		pm_runtime_get_noresume(phy->dev);
+		pm_runtime_resume(phy->dev);
+	}
+
+	return 0;
+}
+
 /**
  * dwc3_otg_set_host_power - Enable port power control for host operation
  *
@@ -149,6 +168,14 @@
 		 * anymore.
 		 */
 		dwc3_otg_set_host_regs(dotg);
+		/*
+		 * FIXME If micro A cable is disconnected during system suspend,
+		 * xhci platform device will be removed before runtime pm is
+		 * enabled for xhci device. Due to this, disable_depth becomes
+		 * greater than one and runtimepm is not enabled for next microA
+		 * connect. Fix this by calling pm_runtime_init for xhci device.
+		 */
+		pm_runtime_init(&dwc->xhci->dev);
 		ret = platform_device_add(dwc->xhci);
 		if (ret) {
 			dev_err(otg->phy->dev,
@@ -353,6 +380,9 @@
 			dev_dbg(phy->dev, "ext PHY_RESUME event received\n");
 			/* ext_xceiver would have taken h/w out of LPM by now */
 			ret = pm_runtime_get(phy->dev);
+			if ((phy->state == OTG_STATE_A_HOST) &&
+							dotg->host_bus_suspend)
+				dotg->host_bus_suspend = 0;
 			if (ret == -EACCES) {
 				/* pm_runtime_get may fail during system
 				   resume with -EACCES error */
@@ -852,6 +882,7 @@
 	dotg->otg.phy->otg = &dotg->otg;
 	dotg->otg.phy->dev = dwc->dev;
 	dotg->otg.phy->set_power = dwc3_otg_set_power;
+	dotg->otg.phy->set_suspend = dwc3_otg_set_suspend;
 
 	ret = usb_set_transceiver(dotg->otg.phy);
 	if (ret) {
diff --git a/drivers/usb/dwc3/dwc3_otg.h b/drivers/usb/dwc3/dwc3_otg.h
index 5a36a4f..07d6411 100644
--- a/drivers/usb/dwc3/dwc3_otg.h
+++ b/drivers/usb/dwc3/dwc3_otg.h
@@ -1,7 +1,7 @@
 /**
  * dwc3_otg.h - DesignWare USB3 DRD Controller OTG
  *
- * Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 and
@@ -20,6 +20,7 @@
 #include <linux/power_supply.h>
 
 #include <linux/usb/otg.h>
+#include "power.h"
 
 #define DWC3_IDEV_CHG_MAX 1500
 
@@ -48,6 +49,7 @@
 	unsigned long inputs;
 	struct power_supply	*psy;
 	struct completion	dwc3_xcvr_vbus_init;
+	int			host_bus_suspend;
 };
 
 /**
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 644a779..d6d8a76 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -55,7 +55,6 @@
 
 	dma_set_coherent_mask(&xhci->dev, dwc->dev->coherent_dma_mask);
 
-	xhci->dev.parent	= dwc->dev;
 	xhci->dev.dma_mask	= dwc->dev->dma_mask;
 	xhci->dev.dma_parms	= dwc->dev->dma_parms;
 
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index e55fed7..79dcf2f 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -122,6 +122,7 @@
 	if (!hcd)
 		return -ENOMEM;
 
+	hcd_to_bus(hcd)->skip_resume = true;
 	hcd->rsrc_start = res->start;
 	hcd->rsrc_len = resource_size(res);
 
@@ -153,6 +154,7 @@
 		goto dealloc_usb2_hcd;
 	}
 
+	hcd_to_bus(xhci->shared_hcd)->skip_resume = true;
 	/*
 	 * Set the xHCI pointer before xhci_plat_setup() (aka hcd_driver.reset)
 	 * is called by usb_add_hcd().
@@ -173,6 +175,8 @@
 			usb_put_transceiver(phy);
 			goto put_usb3_hcd;
 		}
+		pm_runtime_set_active(&pdev->dev);
+		pm_runtime_enable(&pdev->dev);
 	} else {
 		pm_runtime_no_callbacks(&pdev->dev);
 		pm_runtime_set_active(&pdev->dev);
@@ -205,6 +209,7 @@
 	struct usb_hcd	*hcd = platform_get_drvdata(dev);
 	struct xhci_hcd	*xhci = hcd_to_xhci(hcd);
 
+
 	usb_remove_hcd(xhci->shared_hcd);
 	usb_put_hcd(xhci->shared_hcd);
 
@@ -225,11 +230,74 @@
 	return 0;
 }
 
+#ifdef CONFIG_PM_RUNTIME
+static int xhci_msm_runtime_idle(struct device *dev)
+{
+	dev_dbg(dev, "xhci msm runtime idle\n");
+	return 0;
+}
+
+static int xhci_msm_runtime_suspend(struct device *dev)
+{
+	dev_dbg(dev, "xhci msm runtime suspend\n");
+	/*
+	 * Notify OTG about suspend.  It takes care of
+	 * putting the hardware in LPM.
+	 */
+	if (phy)
+		return usb_phy_set_suspend(phy, 1);
+
+	return 0;
+}
+
+static int xhci_msm_runtime_resume(struct device *dev)
+{
+	dev_dbg(dev, "xhci msm runtime resume\n");
+
+	if (phy)
+		return usb_phy_set_suspend(phy, 0);
+
+	return 0;
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int xhci_msm_pm_suspend(struct device *dev)
+{
+	dev_dbg(dev, "xhci-msm PM suspend\n");
+
+	if (phy)
+		return usb_phy_set_suspend(phy, 1);
+
+	return 0;
+}
+
+static int xhci_msm_pm_resume(struct device *dev)
+{
+	dev_dbg(dev, "xhci-msm PM resume\n");
+
+	if (pm_runtime_suspended(dev))
+		return 0;
+
+	if (phy)
+		return usb_phy_set_suspend(phy, 0);
+
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops xhci_msm_dev_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(xhci_msm_pm_suspend, xhci_msm_pm_resume)
+	SET_RUNTIME_PM_OPS(xhci_msm_runtime_suspend, xhci_msm_runtime_resume,
+				xhci_msm_runtime_idle)
+};
+
 static struct platform_driver usb_xhci_driver = {
 	.probe	= xhci_plat_probe,
 	.remove	= xhci_plat_remove,
 	.driver	= {
 		.name = "xhci-hcd",
+		.pm = &xhci_msm_dev_pm_ops,
 	},
 };
 MODULE_ALIAS("platform:xhci-hcd");