Skip to content

Commit 17f9340

Browse files
ADESTMfelipebalbi
authored andcommitted
usb: dwc2: override PHY input signals with usb role switch support
This patch adds support for usb role switch to dwc2, by using overriding control of the PHY voltage valid and ID input signals. iddig signal (ID) can be overridden: - when setting GUSBCFG_FORCEHOSTMODE, iddig input pin is overridden with 1; - when setting GUSBCFG_FORCEDEVMODE, iddig input pin is overridden with 0. avalid/bvalid/vbusvalid signals can be overridden respectively with: - GOTGCTL_AVALOEN + GOTGCTL_AVALOVAL - GOTGCTL_BVALOEN + GOTGCTL_BVALOVAL - GOTGCTL_VBVALEN + GOTGCTL_VBVALOVAL It is possible to determine valid sessions thanks to usb role switch: - if USB_ROLE_NONE then !avalid && !bvalid && !vbusvalid - if USB_ROLE_DEVICE then !avalid && bvalid && vbusvalid - if USB_ROLE_HOST then avalid && !bvalid && vbusvalid Acked-by: Minas Harutyunyan <[email protected]> Acked-by: Martin Blumenstingl <[email protected]> Signed-off-by: Amelie Delaunay <[email protected]> Signed-off-by: Felipe Balbi <[email protected]>
1 parent 14793fa commit 17f9340

File tree

6 files changed

+210
-4
lines changed

6 files changed

+210
-4
lines changed

drivers/usb/dwc2/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ config USB_DWC2
55
depends on HAS_DMA
66
depends on USB || USB_GADGET
77
depends on HAS_IOMEM
8+
select USB_ROLE_SWITCH
89
help
910
Say Y here if your system has a Dual Role Hi-Speed USB
1011
controller based on the DesignWare HSOTG IP Core.

drivers/usb/dwc2/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ ccflags-$(CONFIG_USB_DWC2_DEBUG) += -DDEBUG
33
ccflags-$(CONFIG_USB_DWC2_VERBOSE) += -DVERBOSE_DEBUG
44

55
obj-$(CONFIG_USB_DWC2) += dwc2.o
6-
dwc2-y := core.o core_intr.o platform.o
6+
dwc2-y := core.o core_intr.o platform.o drd.o
77
dwc2-y += params.o
88

99
ifneq ($(filter y,$(CONFIG_USB_DWC2_HOST) $(CONFIG_USB_DWC2_DUAL_ROLE)),)

drivers/usb/dwc2/core.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,7 @@ struct dwc2_hregs_backup {
860860
* - USB_DR_MODE_PERIPHERAL
861861
* - USB_DR_MODE_HOST
862862
* - USB_DR_MODE_OTG
863+
* @role_sw: usb_role_switch handle
863864
* @hcd_enabled: Host mode sub-driver initialization indicator.
864865
* @gadget_enabled: Peripheral mode sub-driver initialization indicator.
865866
* @ll_hw_enabled: Status of low-level hardware resources.
@@ -1054,6 +1055,7 @@ struct dwc2_hsotg {
10541055
struct dwc2_core_params params;
10551056
enum usb_otg_state op_state;
10561057
enum usb_dr_mode dr_mode;
1058+
struct usb_role_switch *role_sw;
10571059
unsigned int hcd_enabled:1;
10581060
unsigned int gadget_enabled:1;
10591061
unsigned int ll_hw_enabled:1;
@@ -1376,6 +1378,11 @@ static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg)
13761378
return (dwc2_readl(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) == 0;
13771379
}
13781380

1381+
int dwc2_drd_init(struct dwc2_hsotg *hsotg);
1382+
void dwc2_drd_suspend(struct dwc2_hsotg *hsotg);
1383+
void dwc2_drd_resume(struct dwc2_hsotg *hsotg);
1384+
void dwc2_drd_exit(struct dwc2_hsotg *hsotg);
1385+
13791386
/*
13801387
* Dump core registers and SPRAM
13811388
*/
@@ -1392,6 +1399,7 @@ int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2);
13921399
int dwc2_gadget_init(struct dwc2_hsotg *hsotg);
13931400
void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2,
13941401
bool reset);
1402+
void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg);
13951403
void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg);
13961404
void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2);
13971405
int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode);
@@ -1417,6 +1425,7 @@ static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg)
14171425
{ return 0; }
14181426
static inline void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2,
14191427
bool reset) {}
1428+
static inline void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) {}
14201429
static inline void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) {}
14211430
static inline void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2) {}
14221431
static inline int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg,

drivers/usb/dwc2/drd.c

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* drd.c - DesignWare USB2 DRD Controller Dual-role support
4+
*
5+
* Copyright (C) 2020 STMicroelectronics
6+
*
7+
* Author(s): Amelie Delaunay <[email protected]>
8+
*/
9+
10+
#include <linux/iopoll.h>
11+
#include <linux/platform_device.h>
12+
#include <linux/usb/role.h>
13+
#include "core.h"
14+
15+
static void dwc2_ovr_init(struct dwc2_hsotg *hsotg)
16+
{
17+
unsigned long flags;
18+
u32 gotgctl;
19+
20+
spin_lock_irqsave(&hsotg->lock, flags);
21+
22+
gotgctl = dwc2_readl(hsotg, GOTGCTL);
23+
gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN;
24+
gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS;
25+
gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
26+
dwc2_writel(hsotg, gotgctl, GOTGCTL);
27+
28+
dwc2_force_mode(hsotg, false);
29+
30+
spin_unlock_irqrestore(&hsotg->lock, flags);
31+
}
32+
33+
static int dwc2_ovr_avalid(struct dwc2_hsotg *hsotg, bool valid)
34+
{
35+
u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
36+
37+
/* Check if A-Session is already in the right state */
38+
if ((valid && (gotgctl & GOTGCTL_ASESVLD)) ||
39+
(!valid && !(gotgctl & GOTGCTL_ASESVLD)))
40+
return -EALREADY;
41+
42+
if (valid)
43+
gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL;
44+
else
45+
gotgctl &= ~(GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
46+
dwc2_writel(hsotg, gotgctl, GOTGCTL);
47+
48+
return 0;
49+
}
50+
51+
static int dwc2_ovr_bvalid(struct dwc2_hsotg *hsotg, bool valid)
52+
{
53+
u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
54+
55+
/* Check if B-Session is already in the right state */
56+
if ((valid && (gotgctl & GOTGCTL_BSESVLD)) ||
57+
(!valid && !(gotgctl & GOTGCTL_BSESVLD)))
58+
return -EALREADY;
59+
60+
if (valid)
61+
gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL;
62+
else
63+
gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL);
64+
dwc2_writel(hsotg, gotgctl, GOTGCTL);
65+
66+
return 0;
67+
}
68+
69+
static int dwc2_drd_role_sw_set(struct usb_role_switch *sw, enum usb_role role)
70+
{
71+
struct dwc2_hsotg *hsotg = usb_role_switch_get_drvdata(sw);
72+
unsigned long flags;
73+
int already = 0;
74+
75+
/* Skip session not in line with dr_mode */
76+
if ((role == USB_ROLE_DEVICE && hsotg->dr_mode == USB_DR_MODE_HOST) ||
77+
(role == USB_ROLE_HOST && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL))
78+
return -EINVAL;
79+
80+
#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \
81+
IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
82+
/* Skip session if core is in test mode */
83+
if (role == USB_ROLE_NONE && hsotg->test_mode) {
84+
dev_dbg(hsotg->dev, "Core is in test mode\n");
85+
return -EBUSY;
86+
}
87+
#endif
88+
89+
spin_lock_irqsave(&hsotg->lock, flags);
90+
91+
if (role == USB_ROLE_HOST) {
92+
already = dwc2_ovr_avalid(hsotg, true);
93+
} else if (role == USB_ROLE_DEVICE) {
94+
already = dwc2_ovr_bvalid(hsotg, true);
95+
/* This clear DCTL.SFTDISCON bit */
96+
dwc2_hsotg_core_connect(hsotg);
97+
} else {
98+
if (dwc2_is_device_mode(hsotg)) {
99+
if (!dwc2_ovr_bvalid(hsotg, false))
100+
/* This set DCTL.SFTDISCON bit */
101+
dwc2_hsotg_core_disconnect(hsotg);
102+
} else {
103+
dwc2_ovr_avalid(hsotg, false);
104+
}
105+
}
106+
107+
spin_unlock_irqrestore(&hsotg->lock, flags);
108+
109+
if (!already && hsotg->dr_mode == USB_DR_MODE_OTG)
110+
/* This will raise a Connector ID Status Change Interrupt */
111+
dwc2_force_mode(hsotg, role == USB_ROLE_HOST);
112+
113+
dev_dbg(hsotg->dev, "%s-session valid\n",
114+
role == USB_ROLE_NONE ? "No" :
115+
role == USB_ROLE_HOST ? "A" : "B");
116+
117+
return 0;
118+
}
119+
120+
int dwc2_drd_init(struct dwc2_hsotg *hsotg)
121+
{
122+
struct usb_role_switch_desc role_sw_desc = {0};
123+
struct usb_role_switch *role_sw;
124+
int ret;
125+
126+
if (!device_property_read_bool(hsotg->dev, "usb-role-switch"))
127+
return 0;
128+
129+
role_sw_desc.driver_data = hsotg;
130+
role_sw_desc.fwnode = dev_fwnode(hsotg->dev);
131+
role_sw_desc.set = dwc2_drd_role_sw_set;
132+
role_sw_desc.allow_userspace_control = true;
133+
134+
role_sw = usb_role_switch_register(hsotg->dev, &role_sw_desc);
135+
if (IS_ERR(role_sw)) {
136+
ret = PTR_ERR(role_sw);
137+
dev_err(hsotg->dev,
138+
"failed to register role switch: %d\n", ret);
139+
return ret;
140+
}
141+
142+
hsotg->role_sw = role_sw;
143+
144+
/* Enable override and initialize values */
145+
dwc2_ovr_init(hsotg);
146+
147+
return 0;
148+
}
149+
150+
void dwc2_drd_suspend(struct dwc2_hsotg *hsotg)
151+
{
152+
u32 gintsts, gintmsk;
153+
154+
if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) {
155+
gintmsk = dwc2_readl(hsotg, GINTMSK);
156+
gintmsk &= ~GINTSTS_CONIDSTSCHNG;
157+
dwc2_writel(hsotg, gintmsk, GINTMSK);
158+
gintsts = dwc2_readl(hsotg, GINTSTS);
159+
dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS);
160+
}
161+
}
162+
163+
void dwc2_drd_resume(struct dwc2_hsotg *hsotg)
164+
{
165+
u32 gintsts, gintmsk;
166+
167+
if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) {
168+
gintsts = dwc2_readl(hsotg, GINTSTS);
169+
dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS);
170+
gintmsk = dwc2_readl(hsotg, GINTMSK);
171+
gintmsk |= GINTSTS_CONIDSTSCHNG;
172+
dwc2_writel(hsotg, gintmsk, GINTMSK);
173+
}
174+
}
175+
176+
void dwc2_drd_exit(struct dwc2_hsotg *hsotg)
177+
{
178+
if (hsotg->role_sw)
179+
usb_role_switch_unregister(hsotg->role_sw);
180+
}

drivers/usb/dwc2/gadget.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3530,7 +3530,7 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg,
35303530
dwc2_readl(hsotg, DOEPCTL0));
35313531
}
35323532

3533-
static void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg)
3533+
void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg)
35343534
{
35353535
/* set the soft-disconnect bit */
35363536
dwc2_set_bit(hsotg, DCTL, DCTL_SFTDISCON);

drivers/usb/dwc2/platform.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ static int dwc2_driver_remove(struct platform_device *dev)
323323
if (hsotg->gadget_enabled)
324324
dwc2_hsotg_remove(hsotg);
325325

326+
dwc2_drd_exit(hsotg);
327+
326328
if (hsotg->params.activate_stm_id_vb_detection)
327329
regulator_disable(hsotg->usb33d);
328330

@@ -542,10 +544,17 @@ static int dwc2_driver_probe(struct platform_device *dev)
542544
dwc2_writel(hsotg, ggpio, GGPIO);
543545
}
544546

547+
retval = dwc2_drd_init(hsotg);
548+
if (retval) {
549+
if (retval != -EPROBE_DEFER)
550+
dev_err(hsotg->dev, "failed to initialize dual-role\n");
551+
goto error_init;
552+
}
553+
545554
if (hsotg->dr_mode != USB_DR_MODE_HOST) {
546555
retval = dwc2_gadget_init(hsotg);
547556
if (retval)
548-
goto error_init;
557+
goto error_drd;
549558
hsotg->gadget_enabled = 1;
550559
}
551560

@@ -571,7 +580,7 @@ static int dwc2_driver_probe(struct platform_device *dev)
571580
if (retval) {
572581
if (hsotg->gadget_enabled)
573582
dwc2_hsotg_remove(hsotg);
574-
goto error_init;
583+
goto error_drd;
575584
}
576585
hsotg->hcd_enabled = 1;
577586
}
@@ -603,6 +612,9 @@ static int dwc2_driver_probe(struct platform_device *dev)
603612
dwc2_debugfs_exit(hsotg);
604613
if (hsotg->hcd_enabled)
605614
dwc2_hcd_remove(hsotg);
615+
error_drd:
616+
dwc2_drd_exit(hsotg);
617+
606618
error_init:
607619
if (hsotg->params.activate_stm_id_vb_detection)
608620
regulator_disable(hsotg->usb33d);
@@ -621,6 +633,8 @@ static int __maybe_unused dwc2_suspend(struct device *dev)
621633
if (is_device_mode)
622634
dwc2_hsotg_suspend(dwc2);
623635

636+
dwc2_drd_suspend(dwc2);
637+
624638
if (dwc2->params.activate_stm_id_vb_detection) {
625639
unsigned long flags;
626640
u32 ggpio, gotgctl;
@@ -701,6 +715,8 @@ static int __maybe_unused dwc2_resume(struct device *dev)
701715
/* Need to restore FORCEDEVMODE/FORCEHOSTMODE */
702716
dwc2_force_dr_mode(dwc2);
703717

718+
dwc2_drd_resume(dwc2);
719+
704720
if (dwc2_is_device_mode(dwc2))
705721
ret = dwc2_hsotg_resume(dwc2);
706722

0 commit comments

Comments
 (0)