Skip to content

Commit 18863a8

Browse files
svenpeter42jannau
authored andcommitted
usb: dwc3: Add support for Apple DWC3
As mad as it sounds, the dwc3 controller present on the Apple M1 must be reset and reinitialized whenever a device is unplugged from the root port or when the PHY mode is changed. This is required for at least the following reasons: - The USB2 D+/D- lines are connected through a stateful eUSB2 repeater which in turn is controlled by a variant of the TI TPS6598x USB PD chip. When the USB PD controller detects a hotplug event it resets the eUSB2 repeater. Afterwards, no new device is recognized before the DWC3 core and PHY are reset as well because the eUSB2 repeater and the PHY/dwc3 block disagree about the current state. - It's possible to completely break the dwc3 controller by switching it to device mode and unplugging the cable at just the wrong time. If this happens dwc3 behaves as if no device is connected. CORESOFTRESET will also never clear after it has been set. The only workaround is to trigger a hard reset of the entire dwc3 core with its external reset line. - Whenever the PHY mode is changed (to e.g. transition to DisplayPort alternate mode or USB4) dwc3 has to be shutdown and reinitialized. Otherwise the Type-C port will not be useable until the entire SoC has been reset. All of this can be easily worked around by respecting transitions to USB_ROLE_NONE and making sure the external reset line is asserted when switching roles. Signed-off-by: Sven Peter <sven@svenpeter.dev>
1 parent 54f791e commit 18863a8

3 files changed

Lines changed: 67 additions & 5 deletions

File tree

drivers/usb/dwc3/core.c

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy)
156156
dwc->current_dr_role = mode;
157157
}
158158

159+
static void dwc3_core_exit(struct dwc3 *dwc);
160+
static int dwc3_core_init_for_resume(struct dwc3 *dwc);
161+
159162
static void __dwc3_set_mode(struct work_struct *work)
160163
{
161164
struct dwc3 *dwc = work_to_dwc(work);
@@ -175,7 +178,7 @@ static void __dwc3_set_mode(struct work_struct *work)
175178
if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_OTG)
176179
dwc3_otg_update(dwc, 0);
177180

178-
if (!desired_dr_role)
181+
if (!desired_dr_role && !dwc->role_switch_reset_quirk)
179182
goto out;
180183

181184
if (desired_dr_role == dwc->current_dr_role)
@@ -203,13 +206,32 @@ static void __dwc3_set_mode(struct work_struct *work)
203206
break;
204207
}
205208

209+
if (dwc->role_switch_reset_quirk) {
210+
if (dwc->current_dr_role) {
211+
dwc->current_dr_role = 0;
212+
dwc3_core_exit(dwc);
213+
}
214+
215+
if (desired_dr_role) {
216+
ret = dwc3_core_init_for_resume(dwc);
217+
if (ret) {
218+
dev_err(dwc->dev,
219+
"failed to reinitialize core\n");
220+
goto out;
221+
}
222+
} else {
223+
goto out;
224+
}
225+
}
226+
206227
/*
207228
* When current_dr_role is not set, there's no role switching.
208229
* Only perform GCTL.CoreSoftReset when there's DRD role switching.
209230
*/
210-
if (dwc->current_dr_role && ((DWC3_IP_IS(DWC3) ||
231+
if (dwc->role_switch_reset_quirk ||
232+
(dwc->current_dr_role && ((DWC3_IP_IS(DWC3) ||
211233
DWC3_VER_IS_PRIOR(DWC31, 190A)) &&
212-
desired_dr_role != DWC3_GCTL_PRTCAP_OTG)) {
234+
desired_dr_role != DWC3_GCTL_PRTCAP_OTG))) {
213235
reg = dwc3_readl(dwc->regs, DWC3_GCTL);
214236
reg |= DWC3_GCTL_CORESOFTRESET;
215237
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
@@ -1633,6 +1655,18 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
16331655
ret = dwc3_drd_init(dwc);
16341656
if (ret)
16351657
return dev_err_probe(dev, ret, "failed to initialize dual-role\n");
1658+
1659+
/*
1660+
* If the role switch reset quirk is required the first role
1661+
* switch notification will initialize the core such that we
1662+
* have to shut it down here. Make sure that the __dwc3_set_mode
1663+
* queued by dwc3_drd_init has completed before since it
1664+
* may still try to access MMIO.
1665+
*/
1666+
if (dwc->role_switch_reset_quirk) {
1667+
flush_work(&dwc->drd_work);
1668+
dwc3_core_exit(dwc);
1669+
}
16361670
break;
16371671
default:
16381672
dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
@@ -2218,6 +2252,22 @@ static int dwc3_probe(struct platform_device *pdev)
22182252
if (ret)
22192253
goto err_put_psy;
22202254

2255+
if (dev->of_node) {
2256+
if (of_device_is_compatible(dev->of_node, "apple,dwc3")) {
2257+
if (!IS_ENABLED(CONFIG_USB_ROLE_SWITCH) ||
2258+
!IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)) {
2259+
dev_err(dev,
2260+
"Apple DWC3 requires role switch support.\n"
2261+
);
2262+
ret = -EINVAL;
2263+
goto err_put_psy;
2264+
}
2265+
2266+
dwc->dr_mode = USB_DR_MODE_OTG;
2267+
dwc->role_switch_reset_quirk = true;
2268+
}
2269+
}
2270+
22212271
ret = reset_control_deassert(dwc->reset);
22222272
if (ret)
22232273
goto err_put_psy;
@@ -2357,7 +2407,6 @@ static void dwc3_remove(struct platform_device *pdev)
23572407
power_supply_put(dwc->usb_psy);
23582408
}
23592409

2360-
#ifdef CONFIG_PM
23612410
static int dwc3_core_init_for_resume(struct dwc3 *dwc)
23622411
{
23632412
int ret;
@@ -2384,6 +2433,7 @@ static int dwc3_core_init_for_resume(struct dwc3 *dwc)
23842433
return ret;
23852434
}
23862435

2436+
#ifdef CONFIG_PM
23872437
static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
23882438
{
23892439
u32 reg;

drivers/usb/dwc3/core.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,7 @@ struct dwc3_scratchpad_array {
11531153
* @suspended: set to track suspend event due to U3/L2.
11541154
* @susphy_state: state of DWC3_GUSB2PHYCFG_SUSPHY + DWC3_GUSB3PIPECTL_SUSPHY
11551155
* before PM suspend.
1156+
* @role_switch_reset_quirk: set to force reinitialization after any role switch
11561157
* @imod_interval: set the interrupt moderation interval in 250ns
11571158
* increments or 0 to disable.
11581159
* @max_cfg_eps: current max number of IN eps used across all USB configs.
@@ -1387,6 +1388,8 @@ struct dwc3 {
13871388
unsigned suspended:1;
13881389
unsigned susphy_state:1;
13891390

1391+
unsigned role_switch_reset_quirk:1;
1392+
13901393
u16 imod_interval;
13911394

13921395
int max_cfg_eps;

drivers/usb/dwc3/drd.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,9 @@ static int dwc3_usb_role_switch_set(struct usb_role_switch *sw,
464464
break;
465465
}
466466

467+
if (dwc->role_switch_reset_quirk && role == USB_ROLE_NONE)
468+
mode = 0;
469+
467470
dwc3_set_mode(dwc, mode);
468471
return 0;
469472
}
@@ -492,6 +495,10 @@ static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
492495
role = USB_ROLE_DEVICE;
493496
break;
494497
}
498+
499+
if (dwc->role_switch_reset_quirk && !dwc->current_dr_role)
500+
role = USB_ROLE_NONE;
501+
495502
spin_unlock_irqrestore(&dwc->lock, flags);
496503
return role;
497504
}
@@ -502,7 +509,9 @@ static int dwc3_setup_role_switch(struct dwc3 *dwc)
502509
u32 mode;
503510

504511
dwc->role_switch_default_mode = usb_get_role_switch_default_mode(dwc->dev);
505-
if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) {
512+
if (dwc->role_switch_reset_quirk) {
513+
mode = 0;
514+
} else if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) {
506515
mode = DWC3_GCTL_PRTCAP_HOST;
507516
} else {
508517
dwc->role_switch_default_mode = USB_DR_MODE_PERIPHERAL;

0 commit comments

Comments
 (0)