All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/2] usb: dwc3: Add dual-role support using OTG core
@ 2018-02-27 11:29 Roger Quadros
  0 siblings, 0 replies; 11+ messages in thread
From: Roger Quadros @ 2018-02-27 11:29 UTC (permalink / raw)
  To: balbi; +Cc: linux-usb, linux-kernel, Roger Quadros

Hi Felipe,

Some platforms (e.g. TI's AM437x) don't have USB ID pin state available
over GPIO/extcon but need to rely on the DWC3 core's OTG block to
get the ID pin state instead.

This series implements simple dual-role functionality using DWC3's OTG block.
Debugfs 'mode' override is also functional so user can switch
between "otg", "host" or "device" modes for debug.

Although system suspend/resume isn't working yet in mainline for AM437x,
I've tested this series for system suspend/resume using a local tree.

Roger Quadros (2):
  usb: dwc3: core.h: add some register definitions
  usb: dwc3: add dual role support using OTG block

 drivers/usb/dwc3/core.c |  67 ++++++-
 drivers/usb/dwc3/core.h | 111 +++++++++++
 drivers/usb/dwc3/drd.c  | 489 ++++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 639 insertions(+), 28 deletions(-)

-- 
cheers,
-roger

Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki. Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [v2,1/2] usb: dwc3: core.h: add some register definitions
  2018-02-27 11:29 [PATCH v2 0/2] usb: dwc3: Add dual-role support using OTG core Roger Quadros
@ 2018-02-27 11:29 ` Roger Quadros
  -1 siblings, 0 replies; 11+ messages in thread
From: Roger Quadros @ 2018-02-27 11:29 UTC (permalink / raw)
  To: balbi; +Cc: linux-usb, linux-kernel, Roger Quadros

Add OTG and GHWPARAMS6 register definitions

Signed-off-by: Roger Quadros <rogerq@ti.com>
---
 drivers/usb/dwc3/core.h | 82 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)

diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 860d2bc..0d4c698 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -201,6 +201,15 @@
 #define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS	BIT(28)
 #define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW	BIT(24)
 
+/* Global Status Register */
+#define DWC3_GSTS_OTG_IP	BIT(10)
+#define DWC3_GSTS_BC_IP		BIT(9)
+#define DWC3_GSTS_ADP_IP	BIT(8)
+#define DWC3_GSTS_HOST_IP	BIT(7)
+#define DWC3_GSTS_DEVICE_IP	BIT(6)
+#define DWC3_GSTS_CSR_TIMEOUT	BIT(5)
+#define DWC3_GSTS_BUS_ERR_ADDR_VLD	BIT(4)
+
 /* Global USB2 PHY Configuration Register */
 #define DWC3_GUSB2PHYCFG_PHYSOFTRST	BIT(31)
 #define DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS	BIT(30)
@@ -286,6 +295,11 @@
 #define DWC3_MAX_HIBER_SCRATCHBUFS		15
 
 /* Global HWPARAMS6 Register */
+#define DWC3_GHWPARAMS6_BCSUPPORT		BIT(14)
+#define DWC3_GHWPARAMS6_OTG3SUPPORT		BIT(13)
+#define DWC3_GHWPARAMS6_ADPSUPPORT		BIT(12)
+#define DWC3_GHWPARAMS6_HNPSUPPORT		BIT(11)
+#define DWC3_GHWPARAMS6_SRPSUPPORT		BIT(10)
 #define DWC3_GHWPARAMS6_EN_FPGA			BIT(7)
 
 /* Global HWPARAMS7 Register */
@@ -467,6 +481,74 @@
 #define DWC3_DEV_IMOD_INTERVAL_SHIFT	0
 #define DWC3_DEV_IMOD_INTERVAL_MASK	(0xffff << 0)
 
+/* OTG Configuration Register */
+#define DWC3_OCFG_DISPWRCUTTOFF		BIT(5)
+#define DWC3_OCFG_HIBDISMASK		BIT(4)
+#define DWC3_OCFG_SFTRSTMASK		BIT(3)
+#define DWC3_OCFG_OTGVERSION		BIT(2)
+#define DWC3_OCFG_HNPCAP		BIT(1)
+#define DWC3_OCFG_SRPCAP		BIT(0)
+
+/* OTG CTL Register */
+#define DWC3_OCTL_OTG3GOERR		BIT(7)
+#define DWC3_OCTL_PERIMODE		BIT(6)
+#define DWC3_OCTL_PRTPWRCTL		BIT(5)
+#define DWC3_OCTL_HNPREQ		BIT(4)
+#define DWC3_OCTL_SESREQ		BIT(3)
+#define DWC3_OCTL_TERMSELIDPULSE	BIT(2)
+#define DWC3_OCTL_DEVSETHNPEN		BIT(1)
+#define DWC3_OCTL_HSTSETHNPEN		BIT(0)
+
+/* OTG Event Register */
+#define DWC3_OEVT_DEVICEMODE		BIT(31)
+#define DWC3_OEVT_XHCIRUNSTPSET		BIT(27)
+#define DWC3_OEVT_DEVRUNSTPSET		BIT(26)
+#define DWC3_OEVT_HIBENTRY		BIT(25)
+#define DWC3_OEVT_CONIDSTSCHNG		BIT(24)
+#define DWC3_OEVT_HRRCONFNOTIF		BIT(23)
+#define DWC3_OEVT_HRRINITNOTIF		BIT(22)
+#define DWC3_OEVT_ADEVIDLE		BIT(21)
+#define DWC3_OEVT_ADEVBHOSTEND		BIT(20)
+#define DWC3_OEVT_ADEVHOST		BIT(19)
+#define DWC3_OEVT_ADEVHNPCHNG		BIT(18)
+#define DWC3_OEVT_ADEVSRPDET		BIT(17)
+#define DWC3_OEVT_ADEVSESSENDDET	BIT(16)
+#define DWC3_OEVT_BDEVBHOSTEND		BIT(11)
+#define DWC3_OEVT_BDEVHNPCHNG		BIT(10)
+#define DWC3_OEVT_BDEVSESSVLDDET	BIT(9)
+#define DWC3_OEVT_BDEVVBUSCHNG		BIT(8)
+#define DWC3_OEVT_BSESSVLD		BIT(3)
+#define DWC3_OEVT_HSTNEGSTS		BIT(2)
+#define DWC3_OEVT_SESREQSTS		BIT(1)
+#define DWC3_OEVT_ERROR			BIT(0)
+
+/* OTG Event Enable Register */
+#define DWC3_OEVTEN_XHCIRUNSTPSETEN	BIT(27)
+#define DWC3_OEVTEN_DEVRUNSTPSETEN	BIT(26)
+#define DWC3_OEVTEN_HIBENTRYEN		BIT(25)
+#define DWC3_OEVTEN_CONIDSTSCHNGEN	BIT(24)
+#define DWC3_OEVTEN_HRRCONFNOTIFEN	BIT(23)
+#define DWC3_OEVTEN_HRRINITNOTIFEN	BIT(22)
+#define DWC3_OEVTEN_ADEVIDLEEN		BIT(21)
+#define DWC3_OEVTEN_ADEVBHOSTENDEN	BIT(20)
+#define DWC3_OEVTEN_ADEVHOSTEN		BIT(19)
+#define DWC3_OEVTEN_ADEVHNPCHNGEN	BIT(18)
+#define DWC3_OEVTEN_ADEVSRPDETEN	BIT(17)
+#define DWC3_OEVTEN_ADEVSESSENDDETEN	BIT(16)
+#define DWC3_OEVTEN_BDEVBHOSTENDEN	BIT(11)
+#define DWC3_OEVTEN_BDEVHNPCHNGEN	BIT(10)
+#define DWC3_OEVTEN_BDEVSESSVLDDETEN	BIT(9)
+#define DWC3_OEVTEN_BDEVVBUSCHNGEN	BIT(8)
+
+/* OTG Status Register */
+#define DWC3_OSTS_DEVRUNSTP		BIT(13)
+#define DWC3_OSTS_XHCIRUNSTP		BIT(12)
+#define DWC3_OSTS_PERIPHERALSTATE	BIT(4)
+#define DWC3_OSTS_XHCIPRTPOWER		BIT(3)
+#define DWC3_OSTS_BSESVLD		BIT(2)
+#define DWC3_OSTS_VBUSVLD		BIT(1)
+#define DWC3_OSTS_CONIDSTS		BIT(0)
+
 /* Structures */
 
 struct dwc3_trb;

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 1/2] usb: dwc3: core.h: add some register definitions
@ 2018-02-27 11:29 ` Roger Quadros
  0 siblings, 0 replies; 11+ messages in thread
From: Roger Quadros @ 2018-02-27 11:29 UTC (permalink / raw)
  To: balbi; +Cc: linux-usb, linux-kernel, Roger Quadros

Add OTG and GHWPARAMS6 register definitions

Signed-off-by: Roger Quadros <rogerq@ti.com>
---
 drivers/usb/dwc3/core.h | 82 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)

diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 860d2bc..0d4c698 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -201,6 +201,15 @@
 #define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS	BIT(28)
 #define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW	BIT(24)
 
+/* Global Status Register */
+#define DWC3_GSTS_OTG_IP	BIT(10)
+#define DWC3_GSTS_BC_IP		BIT(9)
+#define DWC3_GSTS_ADP_IP	BIT(8)
+#define DWC3_GSTS_HOST_IP	BIT(7)
+#define DWC3_GSTS_DEVICE_IP	BIT(6)
+#define DWC3_GSTS_CSR_TIMEOUT	BIT(5)
+#define DWC3_GSTS_BUS_ERR_ADDR_VLD	BIT(4)
+
 /* Global USB2 PHY Configuration Register */
 #define DWC3_GUSB2PHYCFG_PHYSOFTRST	BIT(31)
 #define DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS	BIT(30)
@@ -286,6 +295,11 @@
 #define DWC3_MAX_HIBER_SCRATCHBUFS		15
 
 /* Global HWPARAMS6 Register */
+#define DWC3_GHWPARAMS6_BCSUPPORT		BIT(14)
+#define DWC3_GHWPARAMS6_OTG3SUPPORT		BIT(13)
+#define DWC3_GHWPARAMS6_ADPSUPPORT		BIT(12)
+#define DWC3_GHWPARAMS6_HNPSUPPORT		BIT(11)
+#define DWC3_GHWPARAMS6_SRPSUPPORT		BIT(10)
 #define DWC3_GHWPARAMS6_EN_FPGA			BIT(7)
 
 /* Global HWPARAMS7 Register */
@@ -467,6 +481,74 @@
 #define DWC3_DEV_IMOD_INTERVAL_SHIFT	0
 #define DWC3_DEV_IMOD_INTERVAL_MASK	(0xffff << 0)
 
+/* OTG Configuration Register */
+#define DWC3_OCFG_DISPWRCUTTOFF		BIT(5)
+#define DWC3_OCFG_HIBDISMASK		BIT(4)
+#define DWC3_OCFG_SFTRSTMASK		BIT(3)
+#define DWC3_OCFG_OTGVERSION		BIT(2)
+#define DWC3_OCFG_HNPCAP		BIT(1)
+#define DWC3_OCFG_SRPCAP		BIT(0)
+
+/* OTG CTL Register */
+#define DWC3_OCTL_OTG3GOERR		BIT(7)
+#define DWC3_OCTL_PERIMODE		BIT(6)
+#define DWC3_OCTL_PRTPWRCTL		BIT(5)
+#define DWC3_OCTL_HNPREQ		BIT(4)
+#define DWC3_OCTL_SESREQ		BIT(3)
+#define DWC3_OCTL_TERMSELIDPULSE	BIT(2)
+#define DWC3_OCTL_DEVSETHNPEN		BIT(1)
+#define DWC3_OCTL_HSTSETHNPEN		BIT(0)
+
+/* OTG Event Register */
+#define DWC3_OEVT_DEVICEMODE		BIT(31)
+#define DWC3_OEVT_XHCIRUNSTPSET		BIT(27)
+#define DWC3_OEVT_DEVRUNSTPSET		BIT(26)
+#define DWC3_OEVT_HIBENTRY		BIT(25)
+#define DWC3_OEVT_CONIDSTSCHNG		BIT(24)
+#define DWC3_OEVT_HRRCONFNOTIF		BIT(23)
+#define DWC3_OEVT_HRRINITNOTIF		BIT(22)
+#define DWC3_OEVT_ADEVIDLE		BIT(21)
+#define DWC3_OEVT_ADEVBHOSTEND		BIT(20)
+#define DWC3_OEVT_ADEVHOST		BIT(19)
+#define DWC3_OEVT_ADEVHNPCHNG		BIT(18)
+#define DWC3_OEVT_ADEVSRPDET		BIT(17)
+#define DWC3_OEVT_ADEVSESSENDDET	BIT(16)
+#define DWC3_OEVT_BDEVBHOSTEND		BIT(11)
+#define DWC3_OEVT_BDEVHNPCHNG		BIT(10)
+#define DWC3_OEVT_BDEVSESSVLDDET	BIT(9)
+#define DWC3_OEVT_BDEVVBUSCHNG		BIT(8)
+#define DWC3_OEVT_BSESSVLD		BIT(3)
+#define DWC3_OEVT_HSTNEGSTS		BIT(2)
+#define DWC3_OEVT_SESREQSTS		BIT(1)
+#define DWC3_OEVT_ERROR			BIT(0)
+
+/* OTG Event Enable Register */
+#define DWC3_OEVTEN_XHCIRUNSTPSETEN	BIT(27)
+#define DWC3_OEVTEN_DEVRUNSTPSETEN	BIT(26)
+#define DWC3_OEVTEN_HIBENTRYEN		BIT(25)
+#define DWC3_OEVTEN_CONIDSTSCHNGEN	BIT(24)
+#define DWC3_OEVTEN_HRRCONFNOTIFEN	BIT(23)
+#define DWC3_OEVTEN_HRRINITNOTIFEN	BIT(22)
+#define DWC3_OEVTEN_ADEVIDLEEN		BIT(21)
+#define DWC3_OEVTEN_ADEVBHOSTENDEN	BIT(20)
+#define DWC3_OEVTEN_ADEVHOSTEN		BIT(19)
+#define DWC3_OEVTEN_ADEVHNPCHNGEN	BIT(18)
+#define DWC3_OEVTEN_ADEVSRPDETEN	BIT(17)
+#define DWC3_OEVTEN_ADEVSESSENDDETEN	BIT(16)
+#define DWC3_OEVTEN_BDEVBHOSTENDEN	BIT(11)
+#define DWC3_OEVTEN_BDEVHNPCHNGEN	BIT(10)
+#define DWC3_OEVTEN_BDEVSESSVLDDETEN	BIT(9)
+#define DWC3_OEVTEN_BDEVVBUSCHNGEN	BIT(8)
+
+/* OTG Status Register */
+#define DWC3_OSTS_DEVRUNSTP		BIT(13)
+#define DWC3_OSTS_XHCIRUNSTP		BIT(12)
+#define DWC3_OSTS_PERIPHERALSTATE	BIT(4)
+#define DWC3_OSTS_XHCIPRTPOWER		BIT(3)
+#define DWC3_OSTS_BSESVLD		BIT(2)
+#define DWC3_OSTS_VBUSVLD		BIT(1)
+#define DWC3_OSTS_CONIDSTS		BIT(0)
+
 /* Structures */
 
 struct dwc3_trb;
-- 
cheers,
-roger

Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki. Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [v2,2/2] usb: dwc3: add dual role support using OTG block
  2018-02-27 11:29 [PATCH v2 0/2] usb: dwc3: Add dual-role support using OTG core Roger Quadros
@ 2018-02-27 11:30 ` Roger Quadros
  -1 siblings, 0 replies; 11+ messages in thread
From: Roger Quadros @ 2018-02-27 11:30 UTC (permalink / raw)
  To: balbi; +Cc: linux-usb, linux-kernel, Roger Quadros

This is useful on platforms (e.g. TI AM437x) that don't
have ID available on a GPIO but do have the OTG block.

We can obtain the ID state via the OTG block and use it
for dual-role switching.

Signed-off-by: Roger Quadros <rogerq@ti.com>
---
 drivers/usb/dwc3/core.c |  67 ++++++-
 drivers/usb/dwc3/core.h |  29 +++
 drivers/usb/dwc3/drd.c  | 489 ++++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 557 insertions(+), 28 deletions(-)

diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index df4569d..397a4d4 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -89,10 +89,7 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc)
 	return 0;
 }
 
-static void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
-static int dwc3_event_buffers_setup(struct dwc3 *dwc);
-
-static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
+void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
 {
 	u32 reg;
 
@@ -110,16 +107,19 @@ static void __dwc3_set_mode(struct work_struct *work)
 	unsigned long flags;
 	int ret;
 
-	if (!dwc->desired_dr_role)
+	if (dwc->dr_mode != USB_DR_MODE_OTG)
 		return;
 
-	if (dwc->desired_dr_role == dwc->current_dr_role)
+	if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_OTG)
+		dwc3_otg_update(dwc, 0);
+
+	if (!dwc->desired_dr_role)
 		return;
 
-	if (dwc->dr_mode != USB_DR_MODE_OTG)
+	if (dwc->desired_dr_role == dwc->current_dr_role)
 		return;
 
-	if (dwc->desired_dr_role == DWC3_GCTL_PRTCAP_OTG)
+	if (dwc->desired_dr_role == DWC3_GCTL_PRTCAP_OTG && dwc->edev)
 		return;
 
 	switch (dwc->current_dr_role) {
@@ -130,6 +130,13 @@ static void __dwc3_set_mode(struct work_struct *work)
 		dwc3_gadget_exit(dwc);
 		dwc3_event_buffers_cleanup(dwc);
 		break;
+	case DWC3_GCTL_PRTCAP_OTG:
+		dwc3_otg_exit(dwc);
+		spin_lock_irqsave(&dwc->lock, flags);
+		dwc->desired_otg_role = DWC3_OTG_ROLE_IDLE;
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		dwc3_otg_update(dwc, 1);
+		break;
 	default:
 		break;
 	}
@@ -165,9 +172,14 @@ static void __dwc3_set_mode(struct work_struct *work)
 		if (ret)
 			dev_err(dwc->dev, "failed to initialize peripheral\n");
 		break;
+	case DWC3_GCTL_PRTCAP_OTG:
+		dwc3_otg_init(dwc);
+		dwc3_otg_update(dwc, 0);
+		break;
 	default:
 		break;
 	}
+
 }
 
 void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
@@ -351,7 +363,7 @@ static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length)
  *
  * Returns 0 on success otherwise negative errno.
  */
-static int dwc3_event_buffers_setup(struct dwc3 *dwc)
+int dwc3_event_buffers_setup(struct dwc3 *dwc)
 {
 	struct dwc3_event_buffer	*evt;
 
@@ -368,7 +380,7 @@ static int dwc3_event_buffers_setup(struct dwc3 *dwc)
 	return 0;
 }
 
-static void dwc3_event_buffers_cleanup(struct dwc3 *dwc)
+void dwc3_event_buffers_cleanup(struct dwc3 *dwc)
 {
 	struct dwc3_event_buffer	*evt;
 
@@ -1329,6 +1341,20 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
 		if (!PMSG_IS_AUTO(msg))
 			dwc3_core_exit(dwc);
 		break;
+	case DWC3_GCTL_PRTCAP_OTG:
+		/* do nothing during runtime_suspend */
+		if (PMSG_IS_AUTO(msg))
+			break;
+
+		if (dwc->current_otg_role == DWC3_OTG_ROLE_DEVICE) {
+			spin_lock_irqsave(&dwc->lock, flags);
+			dwc3_gadget_suspend(dwc);
+			spin_unlock_irqrestore(&dwc->lock, flags);
+		}
+
+		dwc3_otg_exit(dwc);
+		dwc3_core_exit(dwc);
+		break;
 	default:
 		/* do nothing */
 		break;
@@ -1360,6 +1386,27 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg)
 				return ret;
 		}
 		break;
+	case DWC3_GCTL_PRTCAP_OTG:
+		/* nothing to do on runtime_resume */
+		if (PMSG_IS_AUTO(msg))
+			break;
+
+		ret = dwc3_core_init(dwc);
+		if (ret)
+			return ret;
+
+		dwc3_set_prtcap(dwc, dwc->current_dr_role);
+
+		dwc3_otg_init(dwc);
+		if (dwc->current_otg_role == DWC3_OTG_ROLE_HOST) {
+			dwc3_otg_host_init(dwc);
+		} else if (dwc->current_otg_role == DWC3_OTG_ROLE_DEVICE) {
+			spin_lock_irqsave(&dwc->lock, flags);
+			dwc3_gadget_resume(dwc);
+			spin_unlock_irqrestore(&dwc->lock, flags);
+		}
+
+		break;
 	default:
 		/* do nothing */
 		break;
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 0d4c698..09243a6 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -58,6 +58,11 @@
 #define DWC3_DEVICE_EVENT_CMD_CMPL		10
 #define DWC3_DEVICE_EVENT_OVERFLOW		11
 
+/* Controller's role while using the OTG block */
+#define DWC3_OTG_ROLE_IDLE	0
+#define DWC3_OTG_ROLE_HOST	1
+#define DWC3_OTG_ROLE_DEVICE	2
+
 #define DWC3_GEVNTCOUNT_MASK	0xfffc
 #define DWC3_GEVNTCOUNT_EHB	BIT(31)
 #define DWC3_GSNPSID_MASK	0xffff0000
@@ -863,6 +868,10 @@ struct dwc3_scratchpad_array {
  * @regs_size: address space size
  * @fladj: frame length adjustment
  * @irq_gadget: peripheral controller's IRQ number
+ * @otg_irq: IRQ number for OTG IRQs
+ * @current_otg_role: current role of operation while using the OTG block
+ * @desired_otg_role: desired role of operation while using the OTG block
+ * @otg_restart_host: flag that OTG controller needs to restart host
  * @nr_scratch: number of scratch buffers
  * @u1u2: only used on revisions <1.83a for workaround
  * @maximum_speed: maximum speed requested (mainly for testing purposes)
@@ -996,6 +1005,10 @@ struct dwc3 {
 
 	u32			fladj;
 	u32			irq_gadget;
+	u32			otg_irq;
+	u32			current_otg_role;
+	u32			desired_otg_role;
+	bool			otg_restart_host;
 	u32			nr_scratch;
 	u32			u1u2;
 	u32			maximum_speed;
@@ -1257,6 +1270,7 @@ struct dwc3_gadget_ep_cmd_params {
 #define DWC3_HAS_OTG			BIT(3)
 
 /* prototypes */
+void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode);
 void dwc3_set_mode(struct dwc3 *dwc, u32 mode);
 u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type);
 
@@ -1274,6 +1288,9 @@ static inline bool dwc3_is_usb31(struct dwc3 *dwc)
 
 bool dwc3_has_imod(struct dwc3 *dwc);
 
+int dwc3_event_buffers_setup(struct dwc3 *dwc);
+void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
+
 #if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
 int dwc3_host_init(struct dwc3 *dwc);
 void dwc3_host_exit(struct dwc3 *dwc);
@@ -1317,11 +1334,23 @@ static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc,
 #if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
 int dwc3_drd_init(struct dwc3 *dwc);
 void dwc3_drd_exit(struct dwc3 *dwc);
+void dwc3_otg_init(struct dwc3 *dwc);
+void dwc3_otg_exit(struct dwc3 *dwc);
+void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus);
+void dwc3_otg_host_init(struct dwc3 *dwc);
 #else
 static inline int dwc3_drd_init(struct dwc3 *dwc)
 { return 0; }
 static inline void dwc3_drd_exit(struct dwc3 *dwc)
 { }
+static inline void dwc3_otg_init(struct dwc3 *dwc)
+{ }
+static inline void dwc3_otg_exit(struct dwc3 *dwc)
+{ }
+static inline void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus)
+{ }
+static inline void dwc3_otg_host_init(struct dwc3 *dwc)
+{ }
 #endif
 
 /* power management interface */
diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c
index cc8ab9a..1d8c557 100644
--- a/drivers/usb/dwc3/drd.c
+++ b/drivers/usb/dwc3/drd.c
@@ -8,22 +8,423 @@
  */
 
 #include <linux/extcon.h>
+#include <linux/platform_device.h>
 
 #include "debug.h"
 #include "core.h"
 #include "gadget.h"
 
-static void dwc3_drd_update(struct dwc3 *dwc)
+static void dwc3_otg_disable_events(struct dwc3 *dwc, u32 disable_mask)
+{
+	u32 reg = dwc3_readl(dwc->regs, DWC3_OEVTEN);
+
+	reg &= ~(disable_mask);
+	dwc3_writel(dwc->regs, DWC3_OEVTEN, reg);
+}
+
+static void dwc3_otg_enable_events(struct dwc3 *dwc, u32 enable_mask)
+{
+	u32 reg = dwc3_readl(dwc->regs, DWC3_OEVTEN);
+
+	reg |= (enable_mask);
+	dwc3_writel(dwc->regs, DWC3_OEVTEN, reg);
+}
+
+static void dwc3_otg_clear_events(struct dwc3 *dwc)
+{
+	u32 reg = dwc3_readl(dwc->regs, DWC3_OEVT);
+
+	dwc3_writel(dwc->regs, DWC3_OEVTEN, reg);
+}
+
+#define DWC3_OTG_ALL_EVENTS	(DWC3_OEVTEN_XHCIRUNSTPSETEN | \
+		DWC3_OEVTEN_DEVRUNSTPSETEN | DWC3_OEVTEN_HIBENTRYEN | \
+		DWC3_OEVTEN_CONIDSTSCHNGEN | DWC3_OEVTEN_HRRCONFNOTIFEN | \
+		DWC3_OEVTEN_HRRINITNOTIFEN | DWC3_OEVTEN_ADEVIDLEEN | \
+		DWC3_OEVTEN_ADEVBHOSTENDEN | DWC3_OEVTEN_ADEVHOSTEN | \
+		DWC3_OEVTEN_ADEVHNPCHNGEN | DWC3_OEVTEN_ADEVSRPDETEN | \
+		DWC3_OEVTEN_ADEVSESSENDDETEN | DWC3_OEVTEN_BDEVBHOSTENDEN | \
+		DWC3_OEVTEN_BDEVHNPCHNGEN | DWC3_OEVTEN_BDEVSESSVLDDETEN | \
+		DWC3_OEVTEN_BDEVVBUSCHNGEN)
+
+static irqreturn_t dwc3_otg_thread_irq(int irq, void *_dwc)
 {
+	struct dwc3 *dwc = _dwc;
+
+	spin_lock(&dwc->lock);
+	if (dwc->otg_restart_host) {
+		dwc3_otg_host_init(dwc);
+		dwc->otg_restart_host = 0;
+	}
+
+	spin_unlock(&dwc->lock);
+
+	dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t dwc3_otg_irq(int irq, void *_dwc)
+{
+	u32 reg;
+	struct dwc3 *dwc = _dwc;
+	irqreturn_t ret = IRQ_NONE;
+
+	reg = dwc3_readl(dwc->regs, DWC3_OEVT);
+	if (reg) {
+		/* ignore non OTG events, we can't disable them in OEVTEN */
+		if (!(reg & DWC3_OTG_ALL_EVENTS)) {
+			dwc3_writel(dwc->regs, DWC3_OEVT, reg);
+			return IRQ_NONE;
+		}
+
+		if (dwc->current_otg_role == DWC3_OTG_ROLE_HOST &&
+		    !(reg & DWC3_OEVT_DEVICEMODE))
+			dwc->otg_restart_host = 1;
+		dwc3_writel(dwc->regs, DWC3_OEVT, reg);
+		ret = IRQ_WAKE_THREAD;
+	}
+
+	return ret;
+}
+
+static void dwc3_otgregs_init(struct dwc3 *dwc)
+{
+	u32 reg;
+
+	/*
+	 * Prevent host/device reset from resetting OTG core.
+	 * If we don't do this then xhci_reset (USBCMD.HCRST) will reset
+	 * the signal outputs sent to the PHY, the OTG FSM logic of the
+	 * core and also the resets to the VBUS filters inside the core.
+	 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCFG);
+	reg |= DWC3_OCFG_SFTRSTMASK;
+	dwc3_writel(dwc->regs, DWC3_OCFG, reg);
+
+	/* Disable hibernation for simplicity */
+	reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+	reg &= ~DWC3_GCTL_GBLHIBERNATIONEN;
+	dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+
+	/*
+	 * Initialize OTG registers as per
+	 * Figure 11-4 OTG Driver Overall Programming Flow
+	 */
+	/* OCFG.SRPCap = 0, OCFG.HNPCap = 0 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCFG);
+	reg &= ~(DWC3_OCFG_SRPCAP | DWC3_OCFG_HNPCAP);
+	dwc3_writel(dwc->regs, DWC3_OCFG, reg);
+	/* OEVT = FFFF */
+	dwc3_otg_clear_events(dwc);
+	/* OEVTEN = 0 */
+	dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS);
+	/* OEVTEN.ConIDStsChngEn = 1. Instead we enable all events */
+	dwc3_otg_enable_events(dwc, DWC3_OTG_ALL_EVENTS);
+	/*
+	 * OCTL.PeriMode = 1, OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0,
+	 * OCTL.HNPReq = 0
+	 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+	reg |= DWC3_OCTL_PERIMODE;
+	reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN |
+		 DWC3_OCTL_HNPREQ);
+	dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+}
+
+static int dwc3_otg_get_irq(struct dwc3 *dwc)
+{
+	struct platform_device *dwc3_pdev = to_platform_device(dwc->dev);
+	int irq;
+
+	irq = platform_get_irq_byname(dwc3_pdev, "otg");
+	if (irq > 0)
+		goto out;
+
+	if (irq == -EPROBE_DEFER)
+		goto out;
+
+	irq = platform_get_irq_byname(dwc3_pdev, "dwc_usb3");
+	if (irq > 0)
+		goto out;
+
+	if (irq == -EPROBE_DEFER)
+		goto out;
+
+	irq = platform_get_irq(dwc3_pdev, 0);
+	if (irq > 0)
+		goto out;
+
+	if (irq != -EPROBE_DEFER)
+		dev_err(dwc->dev, "missing OTG IRQ\n");
+
+	if (!irq)
+		irq = -EINVAL;
+
+out:
+	return irq;
+}
+
+void dwc3_otg_init(struct dwc3 *dwc)
+{
+	u32 reg;
+
+	/*
+	 * As per Figure 11-4 OTG Driver Overall Programming Flow,
+	 * block "Initialize GCTL for OTG operation".
+	 */
+	/* GCTL.PrtCapDir=2'b11 */
+	dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG);
+	/* GUSB2PHYCFG0.SusPHY=0 */
+	reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
+	reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
+	dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
+
+	/* Initialize OTG registers */
+	dwc3_otgregs_init(dwc);
+}
+
+void dwc3_otg_exit(struct dwc3 *dwc)
+{
+	/* disable all OTG IRQs */
+	dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS);
+	/* clear all events */
+	dwc3_otg_clear_events(dwc);
+}
+
+/* should be called before Host controller driver is started */
+void dwc3_otg_host_init(struct dwc3 *dwc)
+{
+	u32 reg;
+
+	/* As per Figure 11-10 A-Device Flow Diagram */
+	/* OCFG.HNPCap = 0, OCFG.SRPCap = 0. Already 0 */
+
+	/*
+	 * OCTL.PeriMode=0, OCTL.TermSelDLPulse = 0,
+	 * OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0
+	 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+	reg &= ~(DWC3_OCTL_PERIMODE | DWC3_OCTL_TERMSELIDPULSE |
+			DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN);
+	dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+
+	/*
+	 * OCFG.DisPrtPwrCutoff = 0/1
+	 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCFG);
+	reg &= ~DWC3_OCFG_DISPWRCUTTOFF;
+	dwc3_writel(dwc->regs, DWC3_OCFG, reg);
+
+	/*
+	 * OCFG.SRPCap = 1, OCFG.HNPCap = GHWPARAMS6.HNP_CAP
+	 * We don't want SRP/HNP for simple dual-role so leave
+	 * these disabled.
+	 */
+
+	/*
+	 * OEVTEN.OTGADevHostEvntEn = 1
+	 * OEVTEN.OTGADevSessEndDetEvntEn = 1
+	 * We don't want HNP/role-swap so leave these disabled.
+	 */
+
+	/* GUSB2PHYCFG.ULPIAutoRes = 1/0, GUSB2PHYCFG.SusPHY = 1 */
+	if (!dwc->dis_u2_susphy_quirk) {
+		reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
+		reg |= DWC3_GUSB2PHYCFG_SUSPHY;
+		dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
+	}
+
+	/* Set Port Power to enable VBUS: OCTL.PrtPwrCtl = 1 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+	reg |= DWC3_OCTL_PRTPWRCTL;
+	dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+}
+
+/* should be called after Host controller driver is stopped */
+static void dwc3_otg_host_exit(struct dwc3 *dwc)
+{
+	u32 reg;
+
+	/*
+	 * Exit from A-device flow as per
+	 * Figure 11-4 OTG Driver Overall Programming Flow
+	 */
+
+	/*
+	 * OEVTEN.OTGADevBHostEndEvntEn=0, OEVTEN.OTGADevHNPChngEvntEn=0
+	 * OEVTEN.OTGADevSessEndDetEvntEn=0,
+	 * OEVTEN.OTGADevHostEvntEn = 0
+	 * But we don't disable any OTG events
+	 */
+
+	/* OCTL.HstSetHNPEn = 0, OCTL.PrtPwrCtl=0 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+	reg &= ~(DWC3_OCTL_HSTSETHNPEN | DWC3_OCTL_PRTPWRCTL);
+	dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+}
+
+/* should be called before the gadget controller driver is started */
+static void dwc3_otg_device_init(struct dwc3 *dwc)
+{
+	u32 reg;
+
+	/* As per Figure 11-20 B-Device Flow Diagram */
+
+	/*
+	 * OCFG.HNPCap = GHWPARAMS6.HNP_CAP, OCFG.SRPCap = 1
+	 * but we keep them 0 for simple dual-role operation.
+	 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCFG);
+	/* OCFG.OTGSftRstMsk = 0/1 */
+	reg |= DWC3_OCFG_SFTRSTMASK;
+	dwc3_writel(dwc->regs, DWC3_OCFG, reg);
+	/*
+	 * OCTL.PeriMode = 1
+	 * OCTL.TermSelDLPulse = 0/1, OCTL.HNPReq = 0
+	 * OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0
+	 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+	reg |= DWC3_OCTL_PERIMODE;
+	reg &= ~(DWC3_OCTL_TERMSELIDPULSE | DWC3_OCTL_HNPREQ |
+			DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN);
+	dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+	/* OEVTEN.OTGBDevSesVldDetEvntEn = 1 */
+	dwc3_otg_enable_events(dwc, DWC3_OEVTEN_BDEVSESSVLDDETEN);
+	/* GUSB2PHYCFG.ULPIAutoRes = 0, GUSB2PHYCFG0.SusPHY = 1 */
+	if (!dwc->dis_u2_susphy_quirk) {
+		reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
+		reg |= DWC3_GUSB2PHYCFG_SUSPHY;
+		dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
+	}
+	/* GCTL.GblHibernationEn = 0. Already 0. */
+}
+
+/* should be called after the gadget controller driver is stopped */
+static void dwc3_otg_device_exit(struct dwc3 *dwc)
+{
+	u32 reg;
+
+	/*
+	 * Exit from B-device flow as per
+	 * Figure 11-4 OTG Driver Overall Programming Flow
+	 */
+
+	/*
+	 * OEVTEN.OTGBDevHNPChngEvntEn = 0
+	 * OEVTEN.OTGBDevVBusChngEvntEn = 0
+	 * OEVTEN.OTGBDevBHostEndEvntEn = 0
+	 */
+	dwc3_otg_disable_events(dwc, DWC3_OEVTEN_BDEVHNPCHNGEN |
+				DWC3_OEVTEN_BDEVVBUSCHNGEN |
+				DWC3_OEVTEN_BDEVBHOSTENDEN);
+
+	/* OCTL.DevSetHNPEn = 0, OCTL.HNPReq = 0, OCTL.PeriMode=1 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+	reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HNPREQ);
+	reg |= DWC3_OCTL_PERIMODE;
+	dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+}
+
+void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus)
+{
+	int ret;
+	u32 reg;
 	int id;
+	unsigned long flags;
 
-	id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
-	if (id < 0)
-		id = 0;
+	if (dwc->dr_mode != USB_DR_MODE_OTG)
+		return;
 
-	dwc3_set_mode(dwc, id ?
-		      DWC3_GCTL_PRTCAP_HOST :
-		      DWC3_GCTL_PRTCAP_DEVICE);
+	/* don't do anything if debug user changed role to not OTG */
+	if (dwc->current_dr_role != DWC3_GCTL_PRTCAP_OTG)
+		return;
+
+	if (!ignore_idstatus) {
+		reg = dwc3_readl(dwc->regs, DWC3_OSTS);
+		id = !!(reg & DWC3_OSTS_CONIDSTS);
+
+		dwc->desired_otg_role = id ? DWC3_OTG_ROLE_DEVICE :
+					DWC3_OTG_ROLE_HOST;
+	}
+
+	if (dwc->desired_otg_role == dwc->current_otg_role)
+		return;
+
+	switch (dwc->current_otg_role) {
+	case DWC3_OTG_ROLE_HOST:
+		dwc3_host_exit(dwc);
+		spin_lock_irqsave(&dwc->lock, flags);
+		dwc3_otg_host_exit(dwc);
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		break;
+	case DWC3_OTG_ROLE_DEVICE:
+		dwc3_gadget_exit(dwc);
+		spin_lock_irqsave(&dwc->lock, flags);
+		dwc3_event_buffers_cleanup(dwc);
+		dwc3_otg_device_exit(dwc);
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		break;
+	default:
+		break;
+	}
+
+	spin_lock_irqsave(&dwc->lock, flags);
+
+	dwc->current_otg_role = dwc->desired_otg_role;
+
+	spin_unlock_irqrestore(&dwc->lock, flags);
+
+	switch (dwc->desired_otg_role) {
+	case DWC3_OTG_ROLE_HOST:
+		spin_lock_irqsave(&dwc->lock, flags);
+		dwc3_otgregs_init(dwc);
+		dwc3_otg_host_init(dwc);
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		ret = dwc3_host_init(dwc);
+		if (ret) {
+			dev_err(dwc->dev, "failed to initialize host\n");
+		} else {
+			if (dwc->usb2_phy)
+				otg_set_vbus(dwc->usb2_phy->otg, true);
+			if (dwc->usb2_generic_phy)
+				phy_set_mode(dwc->usb2_generic_phy,
+					     PHY_MODE_USB_HOST);
+		}
+		break;
+	case DWC3_OTG_ROLE_DEVICE:
+		spin_lock_irqsave(&dwc->lock, flags);
+		dwc3_otgregs_init(dwc);
+		dwc3_otg_device_init(dwc);
+		dwc3_event_buffers_setup(dwc);
+		spin_unlock_irqrestore(&dwc->lock, flags);
+
+		if (dwc->usb2_phy)
+			otg_set_vbus(dwc->usb2_phy->otg, false);
+		if (dwc->usb2_generic_phy)
+			phy_set_mode(dwc->usb2_generic_phy,
+				     PHY_MODE_USB_DEVICE);
+		ret = dwc3_gadget_init(dwc);
+		if (ret)
+			dev_err(dwc->dev, "failed to initialize peripheral\n");
+		break;
+	default:
+		break;
+	}
+}
+
+static void dwc3_drd_update(struct dwc3 *dwc)
+{
+	int id;
+
+	if (dwc->edev) {
+		id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
+		if (id < 0)
+			id = 0;
+		dwc3_set_mode(dwc, id ?
+			      DWC3_GCTL_PRTCAP_HOST :
+			      DWC3_GCTL_PRTCAP_DEVICE);
+	}
 }
 
 static int dwc3_drd_notifier(struct notifier_block *nb,
@@ -40,11 +441,11 @@ static int dwc3_drd_notifier(struct notifier_block *nb,
 
 int dwc3_drd_init(struct dwc3 *dwc)
 {
-	int ret;
+	int ret, irq;
 
-	if (dwc->dev->of_node) {
-		if (of_property_read_bool(dwc->dev->of_node, "extcon"))
-			dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0);
+	if (dwc->dev->of_node &&
+	    of_property_read_bool(dwc->dev->of_node, "extcon")) {
+		dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0);
 
 		if (IS_ERR(dwc->edev))
 			return PTR_ERR(dwc->edev);
@@ -56,19 +457,71 @@ int dwc3_drd_init(struct dwc3 *dwc)
 			dev_err(dwc->dev, "couldn't register cable notifier\n");
 			return ret;
 		}
-	}
 
-	dwc3_drd_update(dwc);
+		dwc3_drd_update(dwc);
+	} else {
+		dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG);
+		dwc->current_dr_role = DWC3_GCTL_PRTCAP_OTG;
+
+		/* use OTG block to get ID event */
+		irq = dwc3_otg_get_irq(dwc);
+		if (irq < 0)
+			return irq;
+
+		dwc->otg_irq = irq;
+
+		/* disable all OTG IRQs */
+		dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS);
+		/* clear all events */
+		dwc3_otg_clear_events(dwc);
+
+		ret = request_threaded_irq(dwc->otg_irq, dwc3_otg_irq,
+					   dwc3_otg_thread_irq,
+					   IRQF_SHARED, "dwc3-otg", dwc);
+		if (ret) {
+			dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
+				dwc->otg_irq, ret);
+			ret = -ENODEV;
+			return ret;
+		}
+
+		dwc3_otg_init(dwc);
+		dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
+	}
 
 	return 0;
 }
 
 void dwc3_drd_exit(struct dwc3 *dwc)
 {
-	extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
-				   &dwc->edev_nb);
+	unsigned long flags;
+
+	if (dwc->edev)
+		extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
+					   &dwc->edev_nb);
+
+	cancel_work_sync(&dwc->drd_work);
+
+	/* debug user might have changed role, clean based on current role */
+	switch (dwc->current_dr_role) {
+	case DWC3_GCTL_PRTCAP_HOST:
+		dwc3_host_exit(dwc);
+		break;
+	case DWC3_GCTL_PRTCAP_DEVICE:
+		dwc3_gadget_exit(dwc);
+		dwc3_event_buffers_cleanup(dwc);
+		break;
+	case DWC3_GCTL_PRTCAP_OTG:
+		dwc3_otg_exit(dwc);
+		spin_lock_irqsave(&dwc->lock, flags);
+		dwc->desired_otg_role = DWC3_OTG_ROLE_IDLE;
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		dwc3_otg_update(dwc, 1);
+		break;
+	default:
+		break;
+	}
 
-	dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
-	flush_work(&dwc->drd_work);
-	dwc3_gadget_exit(dwc);
+	if (!dwc->edev)
+		free_irq(dwc->otg_irq, dwc);
 }

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v2 2/2] usb: dwc3: add dual role support using OTG block
@ 2018-02-27 11:30 ` Roger Quadros
  0 siblings, 0 replies; 11+ messages in thread
From: Roger Quadros @ 2018-02-27 11:30 UTC (permalink / raw)
  To: balbi; +Cc: linux-usb, linux-kernel, Roger Quadros

This is useful on platforms (e.g. TI AM437x) that don't
have ID available on a GPIO but do have the OTG block.

We can obtain the ID state via the OTG block and use it
for dual-role switching.

Signed-off-by: Roger Quadros <rogerq@ti.com>
---
 drivers/usb/dwc3/core.c |  67 ++++++-
 drivers/usb/dwc3/core.h |  29 +++
 drivers/usb/dwc3/drd.c  | 489 ++++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 557 insertions(+), 28 deletions(-)

diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index df4569d..397a4d4 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -89,10 +89,7 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc)
 	return 0;
 }
 
-static void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
-static int dwc3_event_buffers_setup(struct dwc3 *dwc);
-
-static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
+void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
 {
 	u32 reg;
 
@@ -110,16 +107,19 @@ static void __dwc3_set_mode(struct work_struct *work)
 	unsigned long flags;
 	int ret;
 
-	if (!dwc->desired_dr_role)
+	if (dwc->dr_mode != USB_DR_MODE_OTG)
 		return;
 
-	if (dwc->desired_dr_role == dwc->current_dr_role)
+	if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_OTG)
+		dwc3_otg_update(dwc, 0);
+
+	if (!dwc->desired_dr_role)
 		return;
 
-	if (dwc->dr_mode != USB_DR_MODE_OTG)
+	if (dwc->desired_dr_role == dwc->current_dr_role)
 		return;
 
-	if (dwc->desired_dr_role == DWC3_GCTL_PRTCAP_OTG)
+	if (dwc->desired_dr_role == DWC3_GCTL_PRTCAP_OTG && dwc->edev)
 		return;
 
 	switch (dwc->current_dr_role) {
@@ -130,6 +130,13 @@ static void __dwc3_set_mode(struct work_struct *work)
 		dwc3_gadget_exit(dwc);
 		dwc3_event_buffers_cleanup(dwc);
 		break;
+	case DWC3_GCTL_PRTCAP_OTG:
+		dwc3_otg_exit(dwc);
+		spin_lock_irqsave(&dwc->lock, flags);
+		dwc->desired_otg_role = DWC3_OTG_ROLE_IDLE;
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		dwc3_otg_update(dwc, 1);
+		break;
 	default:
 		break;
 	}
@@ -165,9 +172,14 @@ static void __dwc3_set_mode(struct work_struct *work)
 		if (ret)
 			dev_err(dwc->dev, "failed to initialize peripheral\n");
 		break;
+	case DWC3_GCTL_PRTCAP_OTG:
+		dwc3_otg_init(dwc);
+		dwc3_otg_update(dwc, 0);
+		break;
 	default:
 		break;
 	}
+
 }
 
 void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
@@ -351,7 +363,7 @@ static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length)
  *
  * Returns 0 on success otherwise negative errno.
  */
-static int dwc3_event_buffers_setup(struct dwc3 *dwc)
+int dwc3_event_buffers_setup(struct dwc3 *dwc)
 {
 	struct dwc3_event_buffer	*evt;
 
@@ -368,7 +380,7 @@ static int dwc3_event_buffers_setup(struct dwc3 *dwc)
 	return 0;
 }
 
-static void dwc3_event_buffers_cleanup(struct dwc3 *dwc)
+void dwc3_event_buffers_cleanup(struct dwc3 *dwc)
 {
 	struct dwc3_event_buffer	*evt;
 
@@ -1329,6 +1341,20 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
 		if (!PMSG_IS_AUTO(msg))
 			dwc3_core_exit(dwc);
 		break;
+	case DWC3_GCTL_PRTCAP_OTG:
+		/* do nothing during runtime_suspend */
+		if (PMSG_IS_AUTO(msg))
+			break;
+
+		if (dwc->current_otg_role == DWC3_OTG_ROLE_DEVICE) {
+			spin_lock_irqsave(&dwc->lock, flags);
+			dwc3_gadget_suspend(dwc);
+			spin_unlock_irqrestore(&dwc->lock, flags);
+		}
+
+		dwc3_otg_exit(dwc);
+		dwc3_core_exit(dwc);
+		break;
 	default:
 		/* do nothing */
 		break;
@@ -1360,6 +1386,27 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg)
 				return ret;
 		}
 		break;
+	case DWC3_GCTL_PRTCAP_OTG:
+		/* nothing to do on runtime_resume */
+		if (PMSG_IS_AUTO(msg))
+			break;
+
+		ret = dwc3_core_init(dwc);
+		if (ret)
+			return ret;
+
+		dwc3_set_prtcap(dwc, dwc->current_dr_role);
+
+		dwc3_otg_init(dwc);
+		if (dwc->current_otg_role == DWC3_OTG_ROLE_HOST) {
+			dwc3_otg_host_init(dwc);
+		} else if (dwc->current_otg_role == DWC3_OTG_ROLE_DEVICE) {
+			spin_lock_irqsave(&dwc->lock, flags);
+			dwc3_gadget_resume(dwc);
+			spin_unlock_irqrestore(&dwc->lock, flags);
+		}
+
+		break;
 	default:
 		/* do nothing */
 		break;
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 0d4c698..09243a6 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -58,6 +58,11 @@
 #define DWC3_DEVICE_EVENT_CMD_CMPL		10
 #define DWC3_DEVICE_EVENT_OVERFLOW		11
 
+/* Controller's role while using the OTG block */
+#define DWC3_OTG_ROLE_IDLE	0
+#define DWC3_OTG_ROLE_HOST	1
+#define DWC3_OTG_ROLE_DEVICE	2
+
 #define DWC3_GEVNTCOUNT_MASK	0xfffc
 #define DWC3_GEVNTCOUNT_EHB	BIT(31)
 #define DWC3_GSNPSID_MASK	0xffff0000
@@ -863,6 +868,10 @@ struct dwc3_scratchpad_array {
  * @regs_size: address space size
  * @fladj: frame length adjustment
  * @irq_gadget: peripheral controller's IRQ number
+ * @otg_irq: IRQ number for OTG IRQs
+ * @current_otg_role: current role of operation while using the OTG block
+ * @desired_otg_role: desired role of operation while using the OTG block
+ * @otg_restart_host: flag that OTG controller needs to restart host
  * @nr_scratch: number of scratch buffers
  * @u1u2: only used on revisions <1.83a for workaround
  * @maximum_speed: maximum speed requested (mainly for testing purposes)
@@ -996,6 +1005,10 @@ struct dwc3 {
 
 	u32			fladj;
 	u32			irq_gadget;
+	u32			otg_irq;
+	u32			current_otg_role;
+	u32			desired_otg_role;
+	bool			otg_restart_host;
 	u32			nr_scratch;
 	u32			u1u2;
 	u32			maximum_speed;
@@ -1257,6 +1270,7 @@ struct dwc3_gadget_ep_cmd_params {
 #define DWC3_HAS_OTG			BIT(3)
 
 /* prototypes */
+void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode);
 void dwc3_set_mode(struct dwc3 *dwc, u32 mode);
 u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type);
 
@@ -1274,6 +1288,9 @@ static inline bool dwc3_is_usb31(struct dwc3 *dwc)
 
 bool dwc3_has_imod(struct dwc3 *dwc);
 
+int dwc3_event_buffers_setup(struct dwc3 *dwc);
+void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
+
 #if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
 int dwc3_host_init(struct dwc3 *dwc);
 void dwc3_host_exit(struct dwc3 *dwc);
@@ -1317,11 +1334,23 @@ static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc,
 #if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
 int dwc3_drd_init(struct dwc3 *dwc);
 void dwc3_drd_exit(struct dwc3 *dwc);
+void dwc3_otg_init(struct dwc3 *dwc);
+void dwc3_otg_exit(struct dwc3 *dwc);
+void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus);
+void dwc3_otg_host_init(struct dwc3 *dwc);
 #else
 static inline int dwc3_drd_init(struct dwc3 *dwc)
 { return 0; }
 static inline void dwc3_drd_exit(struct dwc3 *dwc)
 { }
+static inline void dwc3_otg_init(struct dwc3 *dwc)
+{ }
+static inline void dwc3_otg_exit(struct dwc3 *dwc)
+{ }
+static inline void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus)
+{ }
+static inline void dwc3_otg_host_init(struct dwc3 *dwc)
+{ }
 #endif
 
 /* power management interface */
diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c
index cc8ab9a..1d8c557 100644
--- a/drivers/usb/dwc3/drd.c
+++ b/drivers/usb/dwc3/drd.c
@@ -8,22 +8,423 @@
  */
 
 #include <linux/extcon.h>
+#include <linux/platform_device.h>
 
 #include "debug.h"
 #include "core.h"
 #include "gadget.h"
 
-static void dwc3_drd_update(struct dwc3 *dwc)
+static void dwc3_otg_disable_events(struct dwc3 *dwc, u32 disable_mask)
+{
+	u32 reg = dwc3_readl(dwc->regs, DWC3_OEVTEN);
+
+	reg &= ~(disable_mask);
+	dwc3_writel(dwc->regs, DWC3_OEVTEN, reg);
+}
+
+static void dwc3_otg_enable_events(struct dwc3 *dwc, u32 enable_mask)
+{
+	u32 reg = dwc3_readl(dwc->regs, DWC3_OEVTEN);
+
+	reg |= (enable_mask);
+	dwc3_writel(dwc->regs, DWC3_OEVTEN, reg);
+}
+
+static void dwc3_otg_clear_events(struct dwc3 *dwc)
+{
+	u32 reg = dwc3_readl(dwc->regs, DWC3_OEVT);
+
+	dwc3_writel(dwc->regs, DWC3_OEVTEN, reg);
+}
+
+#define DWC3_OTG_ALL_EVENTS	(DWC3_OEVTEN_XHCIRUNSTPSETEN | \
+		DWC3_OEVTEN_DEVRUNSTPSETEN | DWC3_OEVTEN_HIBENTRYEN | \
+		DWC3_OEVTEN_CONIDSTSCHNGEN | DWC3_OEVTEN_HRRCONFNOTIFEN | \
+		DWC3_OEVTEN_HRRINITNOTIFEN | DWC3_OEVTEN_ADEVIDLEEN | \
+		DWC3_OEVTEN_ADEVBHOSTENDEN | DWC3_OEVTEN_ADEVHOSTEN | \
+		DWC3_OEVTEN_ADEVHNPCHNGEN | DWC3_OEVTEN_ADEVSRPDETEN | \
+		DWC3_OEVTEN_ADEVSESSENDDETEN | DWC3_OEVTEN_BDEVBHOSTENDEN | \
+		DWC3_OEVTEN_BDEVHNPCHNGEN | DWC3_OEVTEN_BDEVSESSVLDDETEN | \
+		DWC3_OEVTEN_BDEVVBUSCHNGEN)
+
+static irqreturn_t dwc3_otg_thread_irq(int irq, void *_dwc)
 {
+	struct dwc3 *dwc = _dwc;
+
+	spin_lock(&dwc->lock);
+	if (dwc->otg_restart_host) {
+		dwc3_otg_host_init(dwc);
+		dwc->otg_restart_host = 0;
+	}
+
+	spin_unlock(&dwc->lock);
+
+	dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t dwc3_otg_irq(int irq, void *_dwc)
+{
+	u32 reg;
+	struct dwc3 *dwc = _dwc;
+	irqreturn_t ret = IRQ_NONE;
+
+	reg = dwc3_readl(dwc->regs, DWC3_OEVT);
+	if (reg) {
+		/* ignore non OTG events, we can't disable them in OEVTEN */
+		if (!(reg & DWC3_OTG_ALL_EVENTS)) {
+			dwc3_writel(dwc->regs, DWC3_OEVT, reg);
+			return IRQ_NONE;
+		}
+
+		if (dwc->current_otg_role == DWC3_OTG_ROLE_HOST &&
+		    !(reg & DWC3_OEVT_DEVICEMODE))
+			dwc->otg_restart_host = 1;
+		dwc3_writel(dwc->regs, DWC3_OEVT, reg);
+		ret = IRQ_WAKE_THREAD;
+	}
+
+	return ret;
+}
+
+static void dwc3_otgregs_init(struct dwc3 *dwc)
+{
+	u32 reg;
+
+	/*
+	 * Prevent host/device reset from resetting OTG core.
+	 * If we don't do this then xhci_reset (USBCMD.HCRST) will reset
+	 * the signal outputs sent to the PHY, the OTG FSM logic of the
+	 * core and also the resets to the VBUS filters inside the core.
+	 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCFG);
+	reg |= DWC3_OCFG_SFTRSTMASK;
+	dwc3_writel(dwc->regs, DWC3_OCFG, reg);
+
+	/* Disable hibernation for simplicity */
+	reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+	reg &= ~DWC3_GCTL_GBLHIBERNATIONEN;
+	dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+
+	/*
+	 * Initialize OTG registers as per
+	 * Figure 11-4 OTG Driver Overall Programming Flow
+	 */
+	/* OCFG.SRPCap = 0, OCFG.HNPCap = 0 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCFG);
+	reg &= ~(DWC3_OCFG_SRPCAP | DWC3_OCFG_HNPCAP);
+	dwc3_writel(dwc->regs, DWC3_OCFG, reg);
+	/* OEVT = FFFF */
+	dwc3_otg_clear_events(dwc);
+	/* OEVTEN = 0 */
+	dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS);
+	/* OEVTEN.ConIDStsChngEn = 1. Instead we enable all events */
+	dwc3_otg_enable_events(dwc, DWC3_OTG_ALL_EVENTS);
+	/*
+	 * OCTL.PeriMode = 1, OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0,
+	 * OCTL.HNPReq = 0
+	 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+	reg |= DWC3_OCTL_PERIMODE;
+	reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN |
+		 DWC3_OCTL_HNPREQ);
+	dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+}
+
+static int dwc3_otg_get_irq(struct dwc3 *dwc)
+{
+	struct platform_device *dwc3_pdev = to_platform_device(dwc->dev);
+	int irq;
+
+	irq = platform_get_irq_byname(dwc3_pdev, "otg");
+	if (irq > 0)
+		goto out;
+
+	if (irq == -EPROBE_DEFER)
+		goto out;
+
+	irq = platform_get_irq_byname(dwc3_pdev, "dwc_usb3");
+	if (irq > 0)
+		goto out;
+
+	if (irq == -EPROBE_DEFER)
+		goto out;
+
+	irq = platform_get_irq(dwc3_pdev, 0);
+	if (irq > 0)
+		goto out;
+
+	if (irq != -EPROBE_DEFER)
+		dev_err(dwc->dev, "missing OTG IRQ\n");
+
+	if (!irq)
+		irq = -EINVAL;
+
+out:
+	return irq;
+}
+
+void dwc3_otg_init(struct dwc3 *dwc)
+{
+	u32 reg;
+
+	/*
+	 * As per Figure 11-4 OTG Driver Overall Programming Flow,
+	 * block "Initialize GCTL for OTG operation".
+	 */
+	/* GCTL.PrtCapDir=2'b11 */
+	dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG);
+	/* GUSB2PHYCFG0.SusPHY=0 */
+	reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
+	reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
+	dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
+
+	/* Initialize OTG registers */
+	dwc3_otgregs_init(dwc);
+}
+
+void dwc3_otg_exit(struct dwc3 *dwc)
+{
+	/* disable all OTG IRQs */
+	dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS);
+	/* clear all events */
+	dwc3_otg_clear_events(dwc);
+}
+
+/* should be called before Host controller driver is started */
+void dwc3_otg_host_init(struct dwc3 *dwc)
+{
+	u32 reg;
+
+	/* As per Figure 11-10 A-Device Flow Diagram */
+	/* OCFG.HNPCap = 0, OCFG.SRPCap = 0. Already 0 */
+
+	/*
+	 * OCTL.PeriMode=0, OCTL.TermSelDLPulse = 0,
+	 * OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0
+	 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+	reg &= ~(DWC3_OCTL_PERIMODE | DWC3_OCTL_TERMSELIDPULSE |
+			DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN);
+	dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+
+	/*
+	 * OCFG.DisPrtPwrCutoff = 0/1
+	 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCFG);
+	reg &= ~DWC3_OCFG_DISPWRCUTTOFF;
+	dwc3_writel(dwc->regs, DWC3_OCFG, reg);
+
+	/*
+	 * OCFG.SRPCap = 1, OCFG.HNPCap = GHWPARAMS6.HNP_CAP
+	 * We don't want SRP/HNP for simple dual-role so leave
+	 * these disabled.
+	 */
+
+	/*
+	 * OEVTEN.OTGADevHostEvntEn = 1
+	 * OEVTEN.OTGADevSessEndDetEvntEn = 1
+	 * We don't want HNP/role-swap so leave these disabled.
+	 */
+
+	/* GUSB2PHYCFG.ULPIAutoRes = 1/0, GUSB2PHYCFG.SusPHY = 1 */
+	if (!dwc->dis_u2_susphy_quirk) {
+		reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
+		reg |= DWC3_GUSB2PHYCFG_SUSPHY;
+		dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
+	}
+
+	/* Set Port Power to enable VBUS: OCTL.PrtPwrCtl = 1 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+	reg |= DWC3_OCTL_PRTPWRCTL;
+	dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+}
+
+/* should be called after Host controller driver is stopped */
+static void dwc3_otg_host_exit(struct dwc3 *dwc)
+{
+	u32 reg;
+
+	/*
+	 * Exit from A-device flow as per
+	 * Figure 11-4 OTG Driver Overall Programming Flow
+	 */
+
+	/*
+	 * OEVTEN.OTGADevBHostEndEvntEn=0, OEVTEN.OTGADevHNPChngEvntEn=0
+	 * OEVTEN.OTGADevSessEndDetEvntEn=0,
+	 * OEVTEN.OTGADevHostEvntEn = 0
+	 * But we don't disable any OTG events
+	 */
+
+	/* OCTL.HstSetHNPEn = 0, OCTL.PrtPwrCtl=0 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+	reg &= ~(DWC3_OCTL_HSTSETHNPEN | DWC3_OCTL_PRTPWRCTL);
+	dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+}
+
+/* should be called before the gadget controller driver is started */
+static void dwc3_otg_device_init(struct dwc3 *dwc)
+{
+	u32 reg;
+
+	/* As per Figure 11-20 B-Device Flow Diagram */
+
+	/*
+	 * OCFG.HNPCap = GHWPARAMS6.HNP_CAP, OCFG.SRPCap = 1
+	 * but we keep them 0 for simple dual-role operation.
+	 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCFG);
+	/* OCFG.OTGSftRstMsk = 0/1 */
+	reg |= DWC3_OCFG_SFTRSTMASK;
+	dwc3_writel(dwc->regs, DWC3_OCFG, reg);
+	/*
+	 * OCTL.PeriMode = 1
+	 * OCTL.TermSelDLPulse = 0/1, OCTL.HNPReq = 0
+	 * OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0
+	 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+	reg |= DWC3_OCTL_PERIMODE;
+	reg &= ~(DWC3_OCTL_TERMSELIDPULSE | DWC3_OCTL_HNPREQ |
+			DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN);
+	dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+	/* OEVTEN.OTGBDevSesVldDetEvntEn = 1 */
+	dwc3_otg_enable_events(dwc, DWC3_OEVTEN_BDEVSESSVLDDETEN);
+	/* GUSB2PHYCFG.ULPIAutoRes = 0, GUSB2PHYCFG0.SusPHY = 1 */
+	if (!dwc->dis_u2_susphy_quirk) {
+		reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
+		reg |= DWC3_GUSB2PHYCFG_SUSPHY;
+		dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
+	}
+	/* GCTL.GblHibernationEn = 0. Already 0. */
+}
+
+/* should be called after the gadget controller driver is stopped */
+static void dwc3_otg_device_exit(struct dwc3 *dwc)
+{
+	u32 reg;
+
+	/*
+	 * Exit from B-device flow as per
+	 * Figure 11-4 OTG Driver Overall Programming Flow
+	 */
+
+	/*
+	 * OEVTEN.OTGBDevHNPChngEvntEn = 0
+	 * OEVTEN.OTGBDevVBusChngEvntEn = 0
+	 * OEVTEN.OTGBDevBHostEndEvntEn = 0
+	 */
+	dwc3_otg_disable_events(dwc, DWC3_OEVTEN_BDEVHNPCHNGEN |
+				DWC3_OEVTEN_BDEVVBUSCHNGEN |
+				DWC3_OEVTEN_BDEVBHOSTENDEN);
+
+	/* OCTL.DevSetHNPEn = 0, OCTL.HNPReq = 0, OCTL.PeriMode=1 */
+	reg = dwc3_readl(dwc->regs, DWC3_OCTL);
+	reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HNPREQ);
+	reg |= DWC3_OCTL_PERIMODE;
+	dwc3_writel(dwc->regs, DWC3_OCTL, reg);
+}
+
+void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus)
+{
+	int ret;
+	u32 reg;
 	int id;
+	unsigned long flags;
 
-	id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
-	if (id < 0)
-		id = 0;
+	if (dwc->dr_mode != USB_DR_MODE_OTG)
+		return;
 
-	dwc3_set_mode(dwc, id ?
-		      DWC3_GCTL_PRTCAP_HOST :
-		      DWC3_GCTL_PRTCAP_DEVICE);
+	/* don't do anything if debug user changed role to not OTG */
+	if (dwc->current_dr_role != DWC3_GCTL_PRTCAP_OTG)
+		return;
+
+	if (!ignore_idstatus) {
+		reg = dwc3_readl(dwc->regs, DWC3_OSTS);
+		id = !!(reg & DWC3_OSTS_CONIDSTS);
+
+		dwc->desired_otg_role = id ? DWC3_OTG_ROLE_DEVICE :
+					DWC3_OTG_ROLE_HOST;
+	}
+
+	if (dwc->desired_otg_role == dwc->current_otg_role)
+		return;
+
+	switch (dwc->current_otg_role) {
+	case DWC3_OTG_ROLE_HOST:
+		dwc3_host_exit(dwc);
+		spin_lock_irqsave(&dwc->lock, flags);
+		dwc3_otg_host_exit(dwc);
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		break;
+	case DWC3_OTG_ROLE_DEVICE:
+		dwc3_gadget_exit(dwc);
+		spin_lock_irqsave(&dwc->lock, flags);
+		dwc3_event_buffers_cleanup(dwc);
+		dwc3_otg_device_exit(dwc);
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		break;
+	default:
+		break;
+	}
+
+	spin_lock_irqsave(&dwc->lock, flags);
+
+	dwc->current_otg_role = dwc->desired_otg_role;
+
+	spin_unlock_irqrestore(&dwc->lock, flags);
+
+	switch (dwc->desired_otg_role) {
+	case DWC3_OTG_ROLE_HOST:
+		spin_lock_irqsave(&dwc->lock, flags);
+		dwc3_otgregs_init(dwc);
+		dwc3_otg_host_init(dwc);
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		ret = dwc3_host_init(dwc);
+		if (ret) {
+			dev_err(dwc->dev, "failed to initialize host\n");
+		} else {
+			if (dwc->usb2_phy)
+				otg_set_vbus(dwc->usb2_phy->otg, true);
+			if (dwc->usb2_generic_phy)
+				phy_set_mode(dwc->usb2_generic_phy,
+					     PHY_MODE_USB_HOST);
+		}
+		break;
+	case DWC3_OTG_ROLE_DEVICE:
+		spin_lock_irqsave(&dwc->lock, flags);
+		dwc3_otgregs_init(dwc);
+		dwc3_otg_device_init(dwc);
+		dwc3_event_buffers_setup(dwc);
+		spin_unlock_irqrestore(&dwc->lock, flags);
+
+		if (dwc->usb2_phy)
+			otg_set_vbus(dwc->usb2_phy->otg, false);
+		if (dwc->usb2_generic_phy)
+			phy_set_mode(dwc->usb2_generic_phy,
+				     PHY_MODE_USB_DEVICE);
+		ret = dwc3_gadget_init(dwc);
+		if (ret)
+			dev_err(dwc->dev, "failed to initialize peripheral\n");
+		break;
+	default:
+		break;
+	}
+}
+
+static void dwc3_drd_update(struct dwc3 *dwc)
+{
+	int id;
+
+	if (dwc->edev) {
+		id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
+		if (id < 0)
+			id = 0;
+		dwc3_set_mode(dwc, id ?
+			      DWC3_GCTL_PRTCAP_HOST :
+			      DWC3_GCTL_PRTCAP_DEVICE);
+	}
 }
 
 static int dwc3_drd_notifier(struct notifier_block *nb,
@@ -40,11 +441,11 @@ static int dwc3_drd_notifier(struct notifier_block *nb,
 
 int dwc3_drd_init(struct dwc3 *dwc)
 {
-	int ret;
+	int ret, irq;
 
-	if (dwc->dev->of_node) {
-		if (of_property_read_bool(dwc->dev->of_node, "extcon"))
-			dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0);
+	if (dwc->dev->of_node &&
+	    of_property_read_bool(dwc->dev->of_node, "extcon")) {
+		dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0);
 
 		if (IS_ERR(dwc->edev))
 			return PTR_ERR(dwc->edev);
@@ -56,19 +457,71 @@ int dwc3_drd_init(struct dwc3 *dwc)
 			dev_err(dwc->dev, "couldn't register cable notifier\n");
 			return ret;
 		}
-	}
 
-	dwc3_drd_update(dwc);
+		dwc3_drd_update(dwc);
+	} else {
+		dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG);
+		dwc->current_dr_role = DWC3_GCTL_PRTCAP_OTG;
+
+		/* use OTG block to get ID event */
+		irq = dwc3_otg_get_irq(dwc);
+		if (irq < 0)
+			return irq;
+
+		dwc->otg_irq = irq;
+
+		/* disable all OTG IRQs */
+		dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS);
+		/* clear all events */
+		dwc3_otg_clear_events(dwc);
+
+		ret = request_threaded_irq(dwc->otg_irq, dwc3_otg_irq,
+					   dwc3_otg_thread_irq,
+					   IRQF_SHARED, "dwc3-otg", dwc);
+		if (ret) {
+			dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
+				dwc->otg_irq, ret);
+			ret = -ENODEV;
+			return ret;
+		}
+
+		dwc3_otg_init(dwc);
+		dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
+	}
 
 	return 0;
 }
 
 void dwc3_drd_exit(struct dwc3 *dwc)
 {
-	extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
-				   &dwc->edev_nb);
+	unsigned long flags;
+
+	if (dwc->edev)
+		extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
+					   &dwc->edev_nb);
+
+	cancel_work_sync(&dwc->drd_work);
+
+	/* debug user might have changed role, clean based on current role */
+	switch (dwc->current_dr_role) {
+	case DWC3_GCTL_PRTCAP_HOST:
+		dwc3_host_exit(dwc);
+		break;
+	case DWC3_GCTL_PRTCAP_DEVICE:
+		dwc3_gadget_exit(dwc);
+		dwc3_event_buffers_cleanup(dwc);
+		break;
+	case DWC3_GCTL_PRTCAP_OTG:
+		dwc3_otg_exit(dwc);
+		spin_lock_irqsave(&dwc->lock, flags);
+		dwc->desired_otg_role = DWC3_OTG_ROLE_IDLE;
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		dwc3_otg_update(dwc, 1);
+		break;
+	default:
+		break;
+	}
 
-	dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
-	flush_work(&dwc->drd_work);
-	dwc3_gadget_exit(dwc);
+	if (!dwc->edev)
+		free_irq(dwc->otg_irq, dwc);
 }
-- 
cheers,
-roger

Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki. Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [v2,2/2] usb: dwc3: add dual role support using OTG block
  2018-02-27 11:30 ` [PATCH v2 2/2] " Roger Quadros
@ 2018-03-08 10:39 ` Felipe Balbi
  -1 siblings, 0 replies; 11+ messages in thread
From: Felipe Balbi @ 2018-03-08 10:39 UTC (permalink / raw)
  To: Roger Quadros; +Cc: linux-usb, linux-kernel

Roger Quadros <rogerq@ti.com> writes:

> This is useful on platforms (e.g. TI AM437x) that don't
> have ID available on a GPIO but do have the OTG block.
>
> We can obtain the ID state via the OTG block and use it
> for dual-role switching.
>
> Signed-off-by: Roger Quadros <rogerq@ti.com>

patch one applied fine to testing/next. But not this one:

checking file drivers/usb/dwc3/core.c
Hunk #2 FAILED at 107.
Hunk #3 succeeded at 122 (offset -5 lines).
Hunk #4 succeeded at 166 (offset -3 lines).
Hunk #5 succeeded at 365 (offset 5 lines).
Hunk #6 succeeded at 382 (offset 5 lines).
Hunk #7 succeeded at 1193 with fuzz 2 (offset -145 lines).
Hunk #8 FAILED at 1383.
2 out of 8 hunks FAILED
checking file drivers/usb/dwc3/core.h
Hunk #2 succeeded at 901 (offset 33 lines).
Hunk #3 succeeded at 1040 (offset 35 lines).
Hunk #4 succeeded at 1316 (offset 46 lines).
Hunk #5 succeeded at 1334 (offset 46 lines).
Hunk #6 succeeded at 1380 (offset 46 lines).
checking file drivers/usb/dwc3/drd.c

care to rebase?

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 2/2] usb: dwc3: add dual role support using OTG block
@ 2018-03-08 10:39 ` Felipe Balbi
  0 siblings, 0 replies; 11+ messages in thread
From: Felipe Balbi @ 2018-03-08 10:39 UTC (permalink / raw)
  To: Roger Quadros; +Cc: linux-usb, linux-kernel, Roger Quadros

[-- Attachment #1: Type: text/plain, Size: 1052 bytes --]

Roger Quadros <rogerq@ti.com> writes:

> This is useful on platforms (e.g. TI AM437x) that don't
> have ID available on a GPIO but do have the OTG block.
>
> We can obtain the ID state via the OTG block and use it
> for dual-role switching.
>
> Signed-off-by: Roger Quadros <rogerq@ti.com>

patch one applied fine to testing/next. But not this one:

checking file drivers/usb/dwc3/core.c
Hunk #2 FAILED at 107.
Hunk #3 succeeded at 122 (offset -5 lines).
Hunk #4 succeeded at 166 (offset -3 lines).
Hunk #5 succeeded at 365 (offset 5 lines).
Hunk #6 succeeded at 382 (offset 5 lines).
Hunk #7 succeeded at 1193 with fuzz 2 (offset -145 lines).
Hunk #8 FAILED at 1383.
2 out of 8 hunks FAILED
checking file drivers/usb/dwc3/core.h
Hunk #2 succeeded at 901 (offset 33 lines).
Hunk #3 succeeded at 1040 (offset 35 lines).
Hunk #4 succeeded at 1316 (offset 46 lines).
Hunk #5 succeeded at 1334 (offset 46 lines).
Hunk #6 succeeded at 1380 (offset 46 lines).
checking file drivers/usb/dwc3/drd.c

care to rebase?

-- 
balbi

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [v2,2/2] usb: dwc3: add dual role support using OTG block
  2018-03-08 10:39 ` [PATCH v2 2/2] " Felipe Balbi
@ 2018-03-08 12:31 ` Roger Quadros
  -1 siblings, 0 replies; 11+ messages in thread
From: Roger Quadros @ 2018-03-08 12:31 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-usb, linux-kernel

Felipe,

On 08/03/18 12:39, Felipe Balbi wrote:
> Roger Quadros <rogerq@ti.com> writes:
> 
>> This is useful on platforms (e.g. TI AM437x) that don't
>> have ID available on a GPIO but do have the OTG block.
>>
>> We can obtain the ID state via the OTG block and use it
>> for dual-role switching.
>>
>> Signed-off-by: Roger Quadros <rogerq@ti.com>
> 
> patch one applied fine to testing/next. But not this one:

That's because you will need this commit from v4.16-rc3

c4a5153e87fd	("usb: dwc3: core: Power-off core/PHYs on system_suspend in host mode")

Could you please apply that and let me know if it works?

> 
> checking file drivers/usb/dwc3/core.c
> Hunk #2 FAILED at 107.
> Hunk #3 succeeded at 122 (offset -5 lines).
> Hunk #4 succeeded at 166 (offset -3 lines).
> Hunk #5 succeeded at 365 (offset 5 lines).
> Hunk #6 succeeded at 382 (offset 5 lines).
> Hunk #7 succeeded at 1193 with fuzz 2 (offset -145 lines).
> Hunk #8 FAILED at 1383.
> 2 out of 8 hunks FAILED
> checking file drivers/usb/dwc3/core.h
> Hunk #2 succeeded at 901 (offset 33 lines).
> Hunk #3 succeeded at 1040 (offset 35 lines).
> Hunk #4 succeeded at 1316 (offset 46 lines).
> Hunk #5 succeeded at 1334 (offset 46 lines).
> Hunk #6 succeeded at 1380 (offset 46 lines).
> checking file drivers/usb/dwc3/drd.c
> 
> care to rebase?
>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 2/2] usb: dwc3: add dual role support using OTG block
@ 2018-03-08 12:31 ` Roger Quadros
  0 siblings, 0 replies; 11+ messages in thread
From: Roger Quadros @ 2018-03-08 12:31 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-usb, linux-kernel

Felipe,

On 08/03/18 12:39, Felipe Balbi wrote:
> Roger Quadros <rogerq@ti.com> writes:
> 
>> This is useful on platforms (e.g. TI AM437x) that don't
>> have ID available on a GPIO but do have the OTG block.
>>
>> We can obtain the ID state via the OTG block and use it
>> for dual-role switching.
>>
>> Signed-off-by: Roger Quadros <rogerq@ti.com>
> 
> patch one applied fine to testing/next. But not this one:

That's because you will need this commit from v4.16-rc3

c4a5153e87fd	("usb: dwc3: core: Power-off core/PHYs on system_suspend in host mode")

Could you please apply that and let me know if it works?

> 
> checking file drivers/usb/dwc3/core.c
> Hunk #2 FAILED at 107.
> Hunk #3 succeeded at 122 (offset -5 lines).
> Hunk #4 succeeded at 166 (offset -3 lines).
> Hunk #5 succeeded at 365 (offset 5 lines).
> Hunk #6 succeeded at 382 (offset 5 lines).
> Hunk #7 succeeded at 1193 with fuzz 2 (offset -145 lines).
> Hunk #8 FAILED at 1383.
> 2 out of 8 hunks FAILED
> checking file drivers/usb/dwc3/core.h
> Hunk #2 succeeded at 901 (offset 33 lines).
> Hunk #3 succeeded at 1040 (offset 35 lines).
> Hunk #4 succeeded at 1316 (offset 46 lines).
> Hunk #5 succeeded at 1334 (offset 46 lines).
> Hunk #6 succeeded at 1380 (offset 46 lines).
> checking file drivers/usb/dwc3/drd.c
> 
> care to rebase?
> 

-- 
cheers,
-roger

Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki. Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [v2,2/2] usb: dwc3: add dual role support using OTG block
  2018-03-08 12:31 ` [PATCH v2 2/2] " Roger Quadros
@ 2018-03-08 13:12 ` Felipe Balbi
  -1 siblings, 0 replies; 11+ messages in thread
From: Felipe Balbi @ 2018-03-08 13:12 UTC (permalink / raw)
  To: Roger Quadros; +Cc: linux-usb, linux-kernel

Hi,

Roger Quadros <rogerq@ti.com> writes:
> Felipe,
>
> On 08/03/18 12:39, Felipe Balbi wrote:
>> Roger Quadros <rogerq@ti.com> writes:
>> 
>>> This is useful on platforms (e.g. TI AM437x) that don't
>>> have ID available on a GPIO but do have the OTG block.
>>>
>>> We can obtain the ID state via the OTG block and use it
>>> for dual-role switching.
>>>
>>> Signed-off-by: Roger Quadros <rogerq@ti.com>
>> 
>> patch one applied fine to testing/next. But not this one:
>
> That's because you will need this commit from v4.16-rc3
>
> c4a5153e87fd	("usb: dwc3: core: Power-off core/PHYs on system_suspend in host mode")
>
> Could you please apply that and let me know if it works?

indeed, applied fine. thanks

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v2 2/2] usb: dwc3: add dual role support using OTG block
@ 2018-03-08 13:12 ` Felipe Balbi
  0 siblings, 0 replies; 11+ messages in thread
From: Felipe Balbi @ 2018-03-08 13:12 UTC (permalink / raw)
  To: Roger Quadros; +Cc: linux-usb, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 752 bytes --]


Hi,

Roger Quadros <rogerq@ti.com> writes:
> Felipe,
>
> On 08/03/18 12:39, Felipe Balbi wrote:
>> Roger Quadros <rogerq@ti.com> writes:
>> 
>>> This is useful on platforms (e.g. TI AM437x) that don't
>>> have ID available on a GPIO but do have the OTG block.
>>>
>>> We can obtain the ID state via the OTG block and use it
>>> for dual-role switching.
>>>
>>> Signed-off-by: Roger Quadros <rogerq@ti.com>
>> 
>> patch one applied fine to testing/next. But not this one:
>
> That's because you will need this commit from v4.16-rc3
>
> c4a5153e87fd	("usb: dwc3: core: Power-off core/PHYs on system_suspend in host mode")
>
> Could you please apply that and let me know if it works?

indeed, applied fine. thanks

-- 
balbi

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 832 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2018-03-08 13:13 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2018-03-08 13:12 [v2,2/2] usb: dwc3: add dual role support using OTG block Felipe Balbi
2018-03-08 13:12 ` [PATCH v2 2/2] " Felipe Balbi
  -- strict thread matches above, loose matches on Subject: below --
2018-03-08 12:31 [v2,2/2] " Roger Quadros
2018-03-08 12:31 ` [PATCH v2 2/2] " Roger Quadros
2018-03-08 10:39 [v2,2/2] " Felipe Balbi
2018-03-08 10:39 ` [PATCH v2 2/2] " Felipe Balbi
2018-02-27 11:30 [v2,2/2] " Roger Quadros
2018-02-27 11:30 ` [PATCH v2 2/2] " Roger Quadros
2018-02-27 11:29 [v2,1/2] usb: dwc3: core.h: add some register definitions Roger Quadros
2018-02-27 11:29 ` [PATCH v2 1/2] " Roger Quadros
2018-02-27 11:29 [PATCH v2 0/2] usb: dwc3: Add dual-role support using OTG core Roger Quadros

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.