USB: dwc3: Add support for basic OTG in superspeed mode

For detecting SS devices in host mode, USB3 roothub port should
be in Rx.detect state. But if OTG control register is used for
role switching, USB3 roothub will be in Rx.disabled state. Hence
use global control register for role switching between Host and
peripheral. Also Fix the issues in role switching from Host to
peripheral by doing block reset and reinitialization of registers.

CRs-Fixed: 419210
Change-Id: Idbcd228824ce29667a20449865c822dc89aee7fa
Signed-off-by: Vijayavardhan Vennapusa <vvreddy@codeaurora.org>
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index c0b4b57..265a685 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -300,19 +300,7 @@
 	}
 }
 
-/* XHCI reset, resets other CORE registers as well, re-init those */
-void dwc3_post_host_reset_core_init(struct dwc3 *dwc)
-{
-	/*
-	 * XHCI reset clears EVENT buffer register as well, re-init
-	 * EVENT buffers and also do device specific re-initialization
-	 */
-	dwc3_event_buffers_setup(dwc);
-
-	dwc3_gadget_restart(dwc);
-}
-
-static void __devinit dwc3_cache_hwparams(struct dwc3 *dwc)
+static void dwc3_cache_hwparams(struct dwc3 *dwc)
 {
 	struct dwc3_hwparams	*parms = &dwc->hwparams;
 
@@ -333,7 +321,7 @@
  *
  * Returns 0 on success otherwise negative errno.
  */
-static int __devinit dwc3_core_init(struct dwc3 *dwc)
+static int dwc3_core_init(struct dwc3 *dwc)
 {
 	unsigned long		timeout;
 	u32			reg;
@@ -434,11 +422,13 @@
 		dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
 	}
 
-	ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE);
-	if (ret) {
-		dev_err(dwc->dev, "failed to allocate event buffers\n");
-		ret = -ENOMEM;
-		goto err1;
+	if (!dwc->ev_buffs) {
+		ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE);
+		if (ret) {
+			dev_err(dwc->dev, "failed to allocate event buffers\n");
+			ret = -ENOMEM;
+			goto err1;
+		}
 	}
 
 	ret = dwc3_event_buffers_setup(dwc);
@@ -462,6 +452,13 @@
 	dwc3_free_event_buffers(dwc);
 }
 
+/* XHCI reset, resets other CORE registers as well, re-init those */
+void dwc3_post_host_reset_core_init(struct dwc3 *dwc)
+{
+	dwc3_core_init(dwc);
+	dwc3_gadget_restart(dwc);
+}
+
 #define DWC3_ALIGN_MASK		(16 - 1)
 
 static int __devinit dwc3_probe(struct platform_device *pdev)
diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c
index e3906bc..cb1da06 100644
--- a/drivers/usb/dwc3/dwc3-msm.c
+++ b/drivers/usb/dwc3/dwc3-msm.c
@@ -40,6 +40,7 @@
 #include <mach/rpm-regulator-smd.h>
 #include <mach/msm_xo.h>
 #include <mach/msm_bus.h>
+#include <mach/clk.h>
 
 #include "dwc3_otg.h"
 #include "core.h"
@@ -1116,6 +1117,95 @@
 	return rc < 0 ? rc : 0;
 }
 
+static int dwc3_msm_link_clk_reset(bool assert)
+{
+	int ret = 0;
+	struct dwc3_msm *mdwc = context;
+
+	if (assert) {
+		/* Using asynchronous block reset to the hardware */
+		dev_dbg(mdwc->dev, "block_reset ASSERT\n");
+		clk_disable_unprepare(mdwc->ref_clk);
+		clk_disable_unprepare(mdwc->iface_clk);
+		clk_disable_unprepare(mdwc->core_clk);
+		ret = clk_reset(mdwc->core_clk, CLK_RESET_ASSERT);
+		if (ret)
+			dev_err(mdwc->dev, "dwc3 core_clk assert failed\n");
+	} else {
+		dev_dbg(mdwc->dev, "block_reset DEASSERT\n");
+		ret = clk_reset(mdwc->core_clk, CLK_RESET_DEASSERT);
+		ndelay(200);
+		clk_prepare_enable(mdwc->core_clk);
+		clk_prepare_enable(mdwc->ref_clk);
+		clk_prepare_enable(mdwc->iface_clk);
+		if (ret)
+			dev_err(mdwc->dev, "dwc3 core_clk deassert failed\n");
+	}
+
+	return ret;
+}
+
+/* Initialize QSCRATCH registers for HSPHY and SSPHY operation */
+static void dwc3_msm_qscratch_reg_init(struct dwc3_msm *msm)
+{
+	u32 data = 0;
+
+	/* SSPHY Initialization: Use ref_clk from pads and set its parameters */
+	dwc3_msm_write_reg(msm->base, SS_PHY_CTRL_REG, 0x10210002);
+	msleep(30);
+	/* Assert SSPHY reset */
+	dwc3_msm_write_reg(msm->base, SS_PHY_CTRL_REG, 0x10210082);
+	usleep_range(2000, 2200);
+	/* De-assert SSPHY reset - power and ref_clock must be ON */
+	dwc3_msm_write_reg(msm->base, SS_PHY_CTRL_REG, 0x10210002);
+	usleep_range(2000, 2200);
+	/* Ref clock must be stable now, enable ref clock for HS mode */
+	dwc3_msm_write_reg(msm->base, SS_PHY_CTRL_REG, 0x10210102);
+	usleep_range(2000, 2200);
+	/*
+	 * HSPHY Initialization: Enable UTMI clock and clamp enable HVINTs,
+	 * and disable RETENTION (power-on default is ENABLED)
+	 */
+	dwc3_msm_write_reg(msm->base, HS_PHY_CTRL_REG, 0x5220bb2);
+	usleep_range(2000, 2200);
+	/* Disable (bypass) VBUS and ID filters */
+	dwc3_msm_write_reg(msm->base, QSCRATCH_GENERAL_CFG, 0x78);
+
+	/*
+	 * WORKAROUND: There is SSPHY suspend bug due to which USB enumerates
+	 * in HS mode instead of SS mode. Workaround it by asserting
+	 * LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus mode
+	 */
+	data = dwc3_msm_ssusb_read_phycreg(msm->base, 0x102D);
+	data |= (1 << 7);
+	dwc3_msm_ssusb_write_phycreg(msm->base, 0x102D, data);
+
+	data = dwc3_msm_ssusb_read_phycreg(msm->base, 0x1010);
+	data &= ~0xFF0;
+	data |= 0x40;
+	dwc3_msm_ssusb_write_phycreg(msm->base, 0x1010, data);
+}
+
+static void dwc3_msm_block_reset(void)
+{
+	struct dwc3_msm *mdwc = context;
+	int ret  = 0;
+
+	ret = dwc3_msm_link_clk_reset(1);
+	if (ret)
+		return;
+
+	usleep_range(1000, 1200);
+	ret = dwc3_msm_link_clk_reset(0);
+	if (ret)
+		return;
+
+	usleep_range(10000, 12000);
+
+	/* Reinitialize QSCRATCH registers after block reset */
+	dwc3_msm_qscratch_reg_init(mdwc);
+}
+
 static void dwc3_chg_enable_secondary_det(struct dwc3_msm *mdwc)
 {
 	u32 chg_ctrl;
@@ -1777,7 +1867,6 @@
 	int ret = 0;
 	int len = 0;
 	u32 tmp[3];
-	u32 data = 0;
 
 	msm = devm_kzalloc(&pdev->dev, sizeof(*msm), GFP_KERNEL);
 	if (!msm) {
@@ -2006,40 +2095,7 @@
 	msm->resource_size = resource_size(res);
 	msm->dwc3 = dwc3;
 
-	/* SSPHY Initialization: Use ref_clk from pads and set its parameters */
-	dwc3_msm_write_reg(msm->base, SS_PHY_CTRL_REG, 0x10210002);
-	msleep(30);
-	/* Assert SSPHY reset */
-	dwc3_msm_write_reg(msm->base, SS_PHY_CTRL_REG, 0x10210082);
-	usleep_range(2000, 2200);
-	/* De-assert SSPHY reset - power and ref_clock must be ON */
-	dwc3_msm_write_reg(msm->base, SS_PHY_CTRL_REG, 0x10210002);
-	usleep_range(2000, 2200);
-	/* Ref clock must be stable now, enable ref clock for HS mode */
-	dwc3_msm_write_reg(msm->base, SS_PHY_CTRL_REG, 0x10210102);
-	usleep_range(2000, 2200);
-	/*
-	 * HSPHY Initialization: Enable UTMI clock and clamp enable HVINTs,
-	 * and disable RETENTION (power-on default is ENABLED)
-	 */
-	dwc3_msm_write_reg(msm->base, HS_PHY_CTRL_REG, 0x5220bb2);
-	usleep_range(2000, 2200);
-	/* Disable (bypass) VBUS and ID filters */
-	dwc3_msm_write_reg(msm->base, QSCRATCH_GENERAL_CFG, 0x78);
-
-	/*
-	 * WORKAROUND: There is SSPHY suspend bug due to which USB enumerates
-	 * in HS mode instead of SS mode. Workaround it by asserting
-	 * LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus mode
-	 */
-	data = dwc3_msm_ssusb_read_phycreg(msm->base, 0x102D);
-	data |= (1 << 7);
-	dwc3_msm_ssusb_write_phycreg(msm->base, 0x102D, data);
-
-	data = dwc3_msm_ssusb_read_phycreg(msm->base, 0x1010);
-	data &= ~0xFF0;
-	data |= 0x40;
-	dwc3_msm_ssusb_write_phycreg(msm->base, 0x1010, data);
+	dwc3_msm_qscratch_reg_init(msm);
 
 	pm_runtime_set_active(msm->dev);
 	pm_runtime_enable(msm->dev);
@@ -2122,6 +2178,8 @@
 			goto put_xcvr;
 		}
 
+		if (msm->ext_xceiv.otg_capability)
+			msm->ext_xceiv.ext_block_reset = dwc3_msm_block_reset;
 		ret = dwc3_set_ext_xceiv(msm->otg_xceiv->otg, &msm->ext_xceiv);
 		if (ret || !msm->ext_xceiv.notify_ext_events) {
 			dev_err(&pdev->dev, "failed to register xceiver: %d\n",
diff --git a/drivers/usb/dwc3/dwc3_otg.c b/drivers/usb/dwc3/dwc3_otg.c
index 01ba161..136cc5d 100644
--- a/drivers/usb/dwc3/dwc3_otg.c
+++ b/drivers/usb/dwc3/dwc3_otg.c
@@ -38,12 +38,21 @@
  */
 static void dwc3_otg_set_host_regs(struct dwc3_otg *dotg)
 {
-	u32 octl;
+	u32 reg;
+	struct dwc3 *dwc = dotg->dwc;
+	struct dwc3_ext_xceiv *ext_xceiv = dotg->ext_xceiv;
 
-	/* Set OCTL[6](PeriMode) to 0 (host) */
-	octl = dwc3_readl(dotg->regs, DWC3_OCTL);
-	octl &= ~DWC3_OTG_OCTL_PERIMODE;
-	dwc3_writel(dotg->regs, DWC3_OCTL, octl);
+	if (ext_xceiv && !ext_xceiv->otg_capability) {
+		/* Set OCTL[6](PeriMode) to 0 (host) */
+		reg = dwc3_readl(dotg->regs, DWC3_OCTL);
+		reg &= ~DWC3_OTG_OCTL_PERIMODE;
+		dwc3_writel(dotg->regs, DWC3_OCTL, reg);
+	} else {
+		reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+		reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG));
+		reg |= DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_HOST);
+		dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+	}
 }
 
 /**
@@ -76,17 +85,25 @@
  */
 static void dwc3_otg_set_peripheral_regs(struct dwc3_otg *dotg)
 {
-	u32 octl;
+	u32 reg;
+	struct dwc3 *dwc = dotg->dwc;
+	struct dwc3_ext_xceiv *ext_xceiv = dotg->ext_xceiv;
 
-	/* Set OCTL[6](PeriMode) to 1 (peripheral) */
-	octl = dwc3_readl(dotg->regs, DWC3_OCTL);
-	octl |= DWC3_OTG_OCTL_PERIMODE;
-	dwc3_writel(dotg->regs, DWC3_OCTL, octl);
-
-	/*
-	 * TODO: add more OTG registers writes for PERIPHERAL mode here,
-	 * see figure 12-19 B-device flow in dwc3 Synopsis spec
-	 */
+	if (ext_xceiv && !ext_xceiv->otg_capability) {
+		/* Set OCTL[6](PeriMode) to 1 (peripheral) */
+		reg = dwc3_readl(dotg->regs, DWC3_OCTL);
+		reg |= DWC3_OTG_OCTL_PERIMODE;
+		dwc3_writel(dotg->regs, DWC3_OCTL, reg);
+		/*
+		 * TODO: add more OTG registers writes for PERIPHERAL mode here,
+		 * see figure 12-19 B-device flow in dwc3 Synopsis spec
+		 */
+	} else {
+		reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+		reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG));
+		reg |= DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_DEVICE);
+		dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+	}
 }
 
 /**
@@ -100,6 +117,7 @@
 static int dwc3_otg_start_host(struct usb_otg *otg, int on)
 {
 	struct dwc3_otg *dotg = container_of(otg, struct dwc3_otg, otg);
+	struct dwc3_ext_xceiv *ext_xceiv = dotg->ext_xceiv;
 	struct dwc3 *dwc = dotg->dwc;
 	int ret = 0;
 
@@ -148,7 +166,8 @@
 		}
 
 		/* re-init OTG EVTEN register as XHCI reset clears it */
-		dwc3_otg_reset(dotg);
+		if (ext_xceiv && !ext_xceiv->otg_capability)
+			dwc3_otg_reset(dotg);
 	} else {
 		dev_dbg(otg->phy->dev, "%s: turn off host\n", __func__);
 
@@ -161,9 +180,15 @@
 		}
 		dwc3_otg_notify_host_mode(otg, on);
 
+		/* Do block reset for Host <-> peripheral switching to work */
+		if (ext_xceiv && ext_xceiv->otg_capability &&
+						ext_xceiv->ext_block_reset)
+			ext_xceiv->ext_block_reset();
+
 		/* re-init core and OTG register as XHCI reset clears it */
 		dwc3_post_host_reset_core_init(dwc);
-		dwc3_otg_reset(dotg);
+		if (ext_xceiv && !ext_xceiv->otg_capability)
+			dwc3_otg_reset(dotg);
 	}
 
 	return 0;
@@ -312,6 +337,7 @@
 	struct dwc3_ext_xceiv *ext_xceiv = dotg->ext_xceiv;
 	struct usb_phy *phy = dotg->otg.phy;
 	int ret = 0;
+	int work = 0;
 
 	if (event == DWC3_EVENT_PHY_RESUME) {
 		if (!pm_runtime_status_suspended(phy->dev)) {
@@ -332,19 +358,27 @@
 		}
 	} else if (event == DWC3_EVENT_XCEIV_STATE) {
 		if (ext_xceiv->id == DWC3_ID_FLOAT) {
-			dev_dbg(phy->dev, "XCVR: ID set\n");
-			set_bit(ID, &dotg->inputs);
+			if (!test_and_set_bit(ID, &dotg->inputs)) {
+				dev_dbg(phy->dev, "XCVR: ID set\n");
+				work = 1;
+			}
 		} else {
-			dev_dbg(phy->dev, "XCVR: ID clear\n");
-			clear_bit(ID, &dotg->inputs);
+			if (test_and_clear_bit(ID, &dotg->inputs)) {
+				dev_dbg(phy->dev, "XCVR: ID clear\n");
+				work = 1;
+			}
 		}
 
 		if (ext_xceiv->bsv) {
-			dev_dbg(phy->dev, "XCVR: BSV set\n");
-			set_bit(B_SESS_VLD, &dotg->inputs);
+			if (!test_and_set_bit(B_SESS_VLD, &dotg->inputs)) {
+				dev_dbg(phy->dev, "XCVR: BSV set\n");
+				work = 1;
+			}
 		} else {
-			dev_dbg(phy->dev, "XCVR: BSV clear\n");
-			clear_bit(B_SESS_VLD, &dotg->inputs);
+			if (test_and_clear_bit(B_SESS_VLD, &dotg->inputs)) {
+				dev_dbg(phy->dev, "XCVR: BSV clear\n");
+				work = 1;
+			}
 		}
 
 		if (!init) {
@@ -353,7 +387,8 @@
 			dev_dbg(phy->dev, "XCVR: BSV init complete\n");
 			return;
 		}
-		schedule_work(&dotg->sm_work);
+		if (work)
+			schedule_work(&dotg->sm_work);
 	}
 }
 
@@ -460,6 +495,7 @@
 	u32 osts, oevt_reg;
 	int ret = IRQ_NONE;
 	int handled_irqs = 0;
+	struct usb_phy *phy = dotg->otg.phy;
 
 	oevt_reg = dwc3_readl(dotg->regs, DWC3_OEVT);
 
@@ -475,23 +511,30 @@
 		 * function, switch from A to B or from B to A.
 		 */
 
-		if (osts & DWC3_OTG_OSTS_CONIDSTS)
-			set_bit(ID, &dotg->inputs);
-		else
-			clear_bit(ID, &dotg->inputs);
+		if (oevt_reg & DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT) {
+			if (osts & DWC3_OTG_OSTS_CONIDSTS) {
+				dev_dbg(phy->dev, "ID set\n");
+				set_bit(ID, &dotg->inputs);
+			} else {
+				dev_dbg(phy->dev, "ID clear\n");
+				clear_bit(ID, &dotg->inputs);
+			}
+			handled_irqs |= DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT;
+		}
 
-		if (osts & DWC3_OTG_OSTS_BSESVALID)
-			set_bit(B_SESS_VLD, &dotg->inputs);
-		else
-			clear_bit(B_SESS_VLD, &dotg->inputs);
+		if (oevt_reg & DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT) {
+			if (osts & DWC3_OTG_OSTS_BSESVALID) {
+				dev_dbg(phy->dev, "BSV set\n");
+				set_bit(B_SESS_VLD, &dotg->inputs);
+			} else {
+				dev_dbg(phy->dev, "BSV clear\n");
+				clear_bit(B_SESS_VLD, &dotg->inputs);
+			}
+			handled_irqs |= DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT;
+		}
 
 		schedule_work(&dotg->sm_work);
 
-		handled_irqs |= (oevt_reg & DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT) ?
-				DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT : 0;
-		handled_irqs |= (oevt_reg & DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT) ?
-				DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT : 0;
-
 		ret = IRQ_HANDLED;
 
 		/* Clear the interrupts we handled */
diff --git a/drivers/usb/dwc3/dwc3_otg.h b/drivers/usb/dwc3/dwc3_otg.h
index c93ce5f..5a36a4f 100644
--- a/drivers/usb/dwc3/dwc3_otg.h
+++ b/drivers/usb/dwc3/dwc3_otg.h
@@ -103,6 +103,8 @@
 	/* to notify OTG about LPM exit event, provided by OTG */
 	void	(*notify_ext_events)(struct usb_otg *otg,
 					enum dwc3_ext_events ext_event);
+	/* for block reset USB core */
+	void	(*ext_block_reset)(void);
 };
 
 /* for external transceiver driver */
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 3679191..c2bc3f3 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -1546,11 +1546,63 @@
 {
 	struct dwc3_ep		*dep;
 	int			ret = 0;
+	u32			reg;
 
-	/* reinitialize physical ep0-1 */
+	/* Enable all but Start and End of Frame IRQs */
+	reg = (DWC3_DEVTEN_EVNTOVERFLOWEN |
+			DWC3_DEVTEN_CMDCMPLTEN |
+			DWC3_DEVTEN_ERRTICERREN |
+			DWC3_DEVTEN_WKUPEVTEN |
+			DWC3_DEVTEN_ULSTCNGEN |
+			DWC3_DEVTEN_CONNECTDONEEN |
+			DWC3_DEVTEN_USBRSTEN |
+			DWC3_DEVTEN_DISCONNEVTEN);
+	dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
+
+	/* Enable USB2 LPM and automatic phy suspend only on recent versions */
+	if (dwc->revision >= DWC3_REVISION_194A) {
+		reg = dwc3_readl(dwc->regs, DWC3_DCFG);
+		reg |= DWC3_DCFG_LPM_CAP;
+		dwc3_writel(dwc->regs, DWC3_DCFG, reg);
+
+		reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+		reg &= ~(DWC3_DCTL_HIRD_THRES_MASK | DWC3_DCTL_L1_HIBER_EN);
+
+		/* TODO: This should be configurable */
+		reg |= DWC3_DCTL_HIRD_THRES(28);
+
+		dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+	}
+
+	reg = dwc3_readl(dwc->regs, DWC3_DCFG);
+	reg &= ~(DWC3_DCFG_SPEED_MASK);
+
+	/**
+	 * WORKAROUND: DWC3 revision < 2.20a have an issue
+	 * which would cause metastability state on Run/Stop
+	 * bit if we try to force the IP to USB2-only mode.
+	 *
+	 * Because of that, we cannot configure the IP to any
+	 * speed other than the SuperSpeed
+	 *
+	 * Refers to:
+	 *
+	 * STAR#9000525659: Clock Domain Crossing on DCTL in
+	 * USB 2.0 Mode
+	 */
+	if (dwc->revision < DWC3_REVISION_220A)
+		reg |= DWC3_DCFG_SUPERSPEED;
+	else
+		reg |= dwc->maximum_speed;
+	dwc3_writel(dwc->regs, DWC3_DCFG, reg);
+
+	dwc->start_config_issued = false;
+
+	/* Start with SuperSpeed Default */
+	dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
 
 	dwc->delayed_status = false;
-
+	/* reinitialize physical ep0-1 */
 	dep = dwc->eps[0];
 	dep->flags = 0;
 	ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false);