1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
|
// SPDX-License-Identifier: GPL-2.0
/*
* Apple Silicon DWC3 Glue driver
* Copyright (C) The Asahi Linux Contributors
*
* Based on:
* - dwc3-qcom.c Copyright (c) 2018, The Linux Foundation. All rights reserved.
* - dwc3-of-simple.c Copyright (c) 2015 Texas Instruments Incorporated - https://www.ti.com
*/
#include <linux/of.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include "glue.h"
/*
* This platform requires a very specific sequence of operations to bring up dwc3 and its USB3 PHY:
*
* 1) The PHY itself has to be brought up; for this we need to know the mode (USB3,
* USB3+DisplayPort, USB4, etc) and the lane orientation. This happens through typec_mux_set.
* 2) DWC3 has to be brought up but we must not touch the gadget area or start xhci yet.
* 3) The PHY bring-up has to be finalized and dwc3's PIPE interface has to be switched to the
* USB3 PHY, this is done inside phy_set_mode.
* 4) We can now initialize xhci or gadget mode.
*
* We can switch 1 and 2 but 3 has to happen after (1 and 2) and 4 has to happen after 3.
*
* And then to bring this all down again:
*
* 1) DWC3 has to exit host or gadget mode and must no longer touch those registers
* 2) The PHY has to switch dwc3's PIPE interface back to the dummy backend
* 3) The PHY itself can be shut down, this happens from typec_mux_set
*
* We also can't transition the PHY from one mode to another while dwc3 is up and running (this is
* slightly wrong, some transitions are possible, others aren't but because we have no documentation
* for this I'd rather play it safe).
*
* After both the PHY and dwc3 are initialized we will only ever see a single "new device connected"
* event. If we just keep them running only the first device plugged in will ever work. XHCI's port
* status register actually does show the correct state but no interrupt ever comes in. In gadget
* mode we don't even get a USBDisconnected event and everything looks like there's still something
* connected on the other end.
* This can be partially explained because 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 which
* resets the repeater out-of-band everytime the CC lines are (dis)connected. This then requires a
* PHY reset to make sure the PHY and the eUSB2 repeater state are synchronized again.
*
* And to make this all extra fun: If we get the order of some of this wrong either the port is just
* broken until a phy+dwc3 reset, or it's broken until a full SoC reset (likely because we can't
* reset some parts of the PHY), or some watchdog kicks in after a few seconds and forces a full SoC
* reset (mostly seen this with USB4/Thunderbolt but there's clearly some watchdog that hates
* invalid states).
*
* Hence there's really no good way to keep dwc3 fully up and running after we disconnect a cable
* because then we can't shut down the PHY anymore. And if we kept the PHY running in whatever mode
* it was until the next cable is connected we'd need to tear it all down and bring it back up again
* anyway to detect and use the next device.
*
* Instead, we just shut down everything when a cable is disconnected and transition to
* DWC3_APPLE_NO_CABLE.
* During initial probe we don't have any information about the connected cable and can't bring up
* the PHY properly and thus also can't fully bring up dwc3. Instead, we just keep everything off
* and defer the first dwc3 probe until we get the first cable connected event. Until then we stay
* in DWC3_APPLE_PROBE_PENDING.
* Once a cable is connected we then keep track of the controller mode here by transitioning to
* DWC3_APPLE_HOST or DWC3_APPLE_DEVICE.
*/
enum dwc3_apple_state {
DWC3_APPLE_PROBE_PENDING, /* Before first cable connection, dwc3_core_probe not called */
DWC3_APPLE_NO_CABLE, /* No cable connected, dwc3 suspended after dwc3_core_exit */
DWC3_APPLE_HOST, /* Cable connected, dwc3 in host mode */
DWC3_APPLE_DEVICE, /* Cable connected, dwc3 in device mode */
};
/**
* struct dwc3_apple - Apple-specific DWC3 USB controller
* @dwc: Core DWC3 structure
* @dev: Pointer to the device structure
* @mmio_resource: Resource to be passed to dwc3_core_probe
* @apple_regs: Apple-specific DWC3 registers
* @reset: Reset control
* @role_sw: USB role switch
* @lock: Mutex for synchronizing access
* @state: Current state of the controller, see documentation for the enum for details
*/
struct dwc3_apple {
struct dwc3 dwc;
struct device *dev;
struct resource *mmio_resource;
void __iomem *apple_regs;
struct reset_control *reset;
struct usb_role_switch *role_sw;
struct mutex lock;
enum dwc3_apple_state state;
};
#define to_dwc3_apple(d) container_of((d), struct dwc3_apple, dwc)
/*
* Apple Silicon dwc3 vendor-specific registers
*
* These registers were identified by tracing XNU's memory access patterns and correlating them with
* debug output over serial to determine their names. We don't exactly know what these do but
* without these USB3 devices sometimes don't work.
*/
#define APPLE_DWC3_REGS_START 0xcd00
#define APPLE_DWC3_REGS_END 0xcdff
#define APPLE_DWC3_CIO_LFPS_OFFSET 0xcd38
#define APPLE_DWC3_CIO_LFPS_OFFSET_VALUE 0xf800f80
#define APPLE_DWC3_CIO_BW_NGT_OFFSET 0xcd3c
#define APPLE_DWC3_CIO_BW_NGT_OFFSET_VALUE 0xfc00fc0
#define APPLE_DWC3_CIO_LINK_TIMER 0xcd40
#define APPLE_DWC3_CIO_PENDING_HP_TIMER GENMASK(23, 16)
#define APPLE_DWC3_CIO_PENDING_HP_TIMER_VALUE 0x14
#define APPLE_DWC3_CIO_PM_LC_TIMER GENMASK(15, 8)
#define APPLE_DWC3_CIO_PM_LC_TIMER_VALUE 0xa
#define APPLE_DWC3_CIO_PM_ENTRY_TIMER GENMASK(7, 0)
#define APPLE_DWC3_CIO_PM_ENTRY_TIMER_VALUE 0x10
static inline void dwc3_apple_writel(struct dwc3_apple *appledwc, u32 offset, u32 value)
{
writel(value, appledwc->apple_regs + offset - APPLE_DWC3_REGS_START);
}
static inline u32 dwc3_apple_readl(struct dwc3_apple *appledwc, u32 offset)
{
return readl(appledwc->apple_regs + offset - APPLE_DWC3_REGS_START);
}
static inline void dwc3_apple_mask(struct dwc3_apple *appledwc, u32 offset, u32 mask, u32 value)
{
u32 reg;
reg = dwc3_apple_readl(appledwc, offset);
reg &= ~mask;
reg |= value;
dwc3_apple_writel(appledwc, offset, reg);
}
static void dwc3_apple_setup_cio(struct dwc3_apple *appledwc)
{
dwc3_apple_writel(appledwc, APPLE_DWC3_CIO_LFPS_OFFSET, APPLE_DWC3_CIO_LFPS_OFFSET_VALUE);
dwc3_apple_writel(appledwc, APPLE_DWC3_CIO_BW_NGT_OFFSET,
APPLE_DWC3_CIO_BW_NGT_OFFSET_VALUE);
dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PENDING_HP_TIMER,
FIELD_PREP(APPLE_DWC3_CIO_PENDING_HP_TIMER,
APPLE_DWC3_CIO_PENDING_HP_TIMER_VALUE));
dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PM_LC_TIMER,
FIELD_PREP(APPLE_DWC3_CIO_PM_LC_TIMER, APPLE_DWC3_CIO_PM_LC_TIMER_VALUE));
dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PM_ENTRY_TIMER,
FIELD_PREP(APPLE_DWC3_CIO_PM_ENTRY_TIMER,
APPLE_DWC3_CIO_PM_ENTRY_TIMER_VALUE));
}
static void dwc3_apple_set_ptrcap(struct dwc3_apple *appledwc, u32 mode)
{
guard(spinlock_irqsave)(&appledwc->dwc.lock);
dwc3_set_prtcap(&appledwc->dwc, mode, false);
}
static int dwc3_apple_core_probe(struct dwc3_apple *appledwc)
{
struct dwc3_probe_data probe_data = {};
int ret;
lockdep_assert_held(&appledwc->lock);
WARN_ON_ONCE(appledwc->state != DWC3_APPLE_PROBE_PENDING);
appledwc->dwc.dev = appledwc->dev;
probe_data.dwc = &appledwc->dwc;
probe_data.res = appledwc->mmio_resource;
probe_data.ignore_clocks_and_resets = true;
probe_data.skip_core_init_mode = true;
probe_data.properties = DWC3_DEFAULT_PROPERTIES;
ret = dwc3_core_probe(&probe_data);
if (ret)
return ret;
appledwc->state = DWC3_APPLE_NO_CABLE;
return 0;
}
static int dwc3_apple_core_init(struct dwc3_apple *appledwc)
{
int ret;
lockdep_assert_held(&appledwc->lock);
switch (appledwc->state) {
case DWC3_APPLE_PROBE_PENDING:
ret = dwc3_apple_core_probe(appledwc);
if (ret)
dev_err(appledwc->dev, "Failed to probe DWC3 Core, err=%d\n", ret);
break;
case DWC3_APPLE_NO_CABLE:
ret = dwc3_core_init(&appledwc->dwc);
if (ret)
dev_err(appledwc->dev, "Failed to initialize DWC3 Core, err=%d\n", ret);
break;
default:
/* Unreachable unless there's a bug in this driver */
WARN_ON_ONCE(1);
ret = -EINVAL;
break;
}
return ret;
}
static void dwc3_apple_phy_set_mode(struct dwc3_apple *appledwc, enum phy_mode mode)
{
lockdep_assert_held(&appledwc->lock);
/*
* This platform requires SUSPHY to be enabled here already in order to properly configure
* the PHY and switch dwc3's PIPE interface to USB3 PHY.
*/
dwc3_enable_susphy(&appledwc->dwc, true);
phy_set_mode(appledwc->dwc.usb2_generic_phy[0], mode);
phy_set_mode(appledwc->dwc.usb3_generic_phy[0], mode);
}
static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state state)
{
int ret, ret_reset;
lockdep_assert_held(&appledwc->lock);
ret = reset_control_deassert(appledwc->reset);
if (ret) {
dev_err(appledwc->dev, "Failed to deassert reset, err=%d\n", ret);
return ret;
}
ret = dwc3_apple_core_init(appledwc);
if (ret)
goto reset_assert;
/*
* Now that the core is initialized and already went through dwc3_core_soft_reset we can
* configure some unknown Apple-specific settings and then bring up xhci or gadget mode.
*/
dwc3_apple_setup_cio(appledwc);
switch (state) {
case DWC3_APPLE_HOST:
appledwc->dwc.dr_mode = USB_DR_MODE_HOST;
dwc3_apple_set_ptrcap(appledwc, DWC3_GCTL_PRTCAP_HOST);
dwc3_apple_phy_set_mode(appledwc, PHY_MODE_USB_HOST);
ret = dwc3_host_init(&appledwc->dwc);
if (ret) {
dev_err(appledwc->dev, "Failed to initialize host, ret=%d\n", ret);
goto core_exit;
}
break;
case DWC3_APPLE_DEVICE:
appledwc->dwc.dr_mode = USB_DR_MODE_PERIPHERAL;
dwc3_apple_set_ptrcap(appledwc, DWC3_GCTL_PRTCAP_DEVICE);
dwc3_apple_phy_set_mode(appledwc, PHY_MODE_USB_DEVICE);
ret = dwc3_gadget_init(&appledwc->dwc);
if (ret) {
dev_err(appledwc->dev, "Failed to initialize gadget, ret=%d\n", ret);
goto core_exit;
}
break;
default:
/* Unreachable unless there's a bug in this driver */
WARN_ON_ONCE(1);
ret = -EINVAL;
goto core_exit;
}
appledwc->state = state;
return 0;
core_exit:
dwc3_core_exit(&appledwc->dwc);
reset_assert:
ret_reset = reset_control_assert(appledwc->reset);
if (ret_reset)
dev_warn(appledwc->dev, "Failed to assert reset, err=%d\n", ret_reset);
return ret;
}
static int dwc3_apple_exit(struct dwc3_apple *appledwc)
{
int ret = 0;
lockdep_assert_held(&appledwc->lock);
switch (appledwc->state) {
case DWC3_APPLE_PROBE_PENDING:
case DWC3_APPLE_NO_CABLE:
/* Nothing to do if we're already off */
return 0;
case DWC3_APPLE_DEVICE:
dwc3_gadget_exit(&appledwc->dwc);
break;
case DWC3_APPLE_HOST:
dwc3_host_exit(&appledwc->dwc);
break;
}
/*
* This platform requires SUSPHY to be enabled in order to properly power down the PHY
* and switch dwc3's PIPE interface back to a dummy PHY (i.e. no USB3 support and USB2 via
* a different PHY connected through ULPI).
*/
dwc3_enable_susphy(&appledwc->dwc, true);
dwc3_core_exit(&appledwc->dwc);
appledwc->state = DWC3_APPLE_NO_CABLE;
ret = reset_control_assert(appledwc->reset);
if (ret) {
dev_err(appledwc->dev, "Failed to assert reset, err=%d\n", ret);
return ret;
}
return 0;
}
static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role)
{
struct dwc3_apple *appledwc = usb_role_switch_get_drvdata(sw);
int ret;
guard(mutex)(&appledwc->lock);
/*
* We need to tear all of dwc3 down and re-initialize it every time a cable is
* connected or disconnected or when the mode changes. See the documentation for enum
* dwc3_apple_state for details.
*/
ret = dwc3_apple_exit(appledwc);
if (ret)
return ret;
switch (role) {
case USB_ROLE_NONE:
/* Nothing to do if no cable is connected */
return 0;
case USB_ROLE_HOST:
return dwc3_apple_init(appledwc, DWC3_APPLE_HOST);
case USB_ROLE_DEVICE:
return dwc3_apple_init(appledwc, DWC3_APPLE_DEVICE);
default:
dev_err(appledwc->dev, "Invalid target role: %d\n", role);
return -EINVAL;
}
}
static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
{
struct dwc3_apple *appledwc = usb_role_switch_get_drvdata(sw);
guard(mutex)(&appledwc->lock);
switch (appledwc->state) {
case DWC3_APPLE_HOST:
return USB_ROLE_HOST;
case DWC3_APPLE_DEVICE:
return USB_ROLE_DEVICE;
case DWC3_APPLE_NO_CABLE:
case DWC3_APPLE_PROBE_PENDING:
return USB_ROLE_NONE;
default:
/* Unreachable unless there's a bug in this driver */
dev_err(appledwc->dev, "Invalid internal state: %d\n", appledwc->state);
return USB_ROLE_NONE;
}
}
static int dwc3_apple_setup_role_switch(struct dwc3_apple *appledwc)
{
struct usb_role_switch_desc dwc3_role_switch = { NULL };
dwc3_role_switch.fwnode = dev_fwnode(appledwc->dev);
dwc3_role_switch.set = dwc3_usb_role_switch_set;
dwc3_role_switch.get = dwc3_usb_role_switch_get;
dwc3_role_switch.driver_data = appledwc;
appledwc->role_sw = usb_role_switch_register(appledwc->dev, &dwc3_role_switch);
if (IS_ERR(appledwc->role_sw))
return PTR_ERR(appledwc->role_sw);
return 0;
}
static int dwc3_apple_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dwc3_apple *appledwc;
int ret;
appledwc = devm_kzalloc(&pdev->dev, sizeof(*appledwc), GFP_KERNEL);
if (!appledwc)
return -ENOMEM;
appledwc->dev = &pdev->dev;
mutex_init(&appledwc->lock);
appledwc->reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(appledwc->reset))
return dev_err_probe(&pdev->dev, PTR_ERR(appledwc->reset),
"Failed to get reset control\n");
ret = reset_control_assert(appledwc->reset);
if (ret) {
dev_err(&pdev->dev, "Failed to assert reset, err=%d\n", ret);
return ret;
}
appledwc->mmio_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dwc3-core");
if (!appledwc->mmio_resource) {
dev_err(dev, "Failed to get DWC3 MMIO\n");
return -EINVAL;
}
appledwc->apple_regs = devm_platform_ioremap_resource_byname(pdev, "dwc3-apple");
if (IS_ERR(appledwc->apple_regs))
return dev_err_probe(dev, PTR_ERR(appledwc->apple_regs),
"Failed to map Apple-specific MMIO\n");
/*
* On this platform, DWC3 can only be brought up after parts of the PHY have been
* initialized with knowledge of the target mode and cable orientation from typec_set_mux.
* Since this has not happened here we cannot setup DWC3 yet and instead defer this until
* the first cable is connected. See the documentation for enum dwc3_apple_state for
* details.
*/
appledwc->state = DWC3_APPLE_PROBE_PENDING;
ret = dwc3_apple_setup_role_switch(appledwc);
if (ret)
return dev_err_probe(&pdev->dev, ret, "Failed to setup role switch\n");
return 0;
}
static void dwc3_apple_remove(struct platform_device *pdev)
{
struct dwc3 *dwc = platform_get_drvdata(pdev);
struct dwc3_apple *appledwc = to_dwc3_apple(dwc);
guard(mutex)(&appledwc->lock);
usb_role_switch_unregister(appledwc->role_sw);
/*
* If we're still in DWC3_APPLE_PROBE_PENDING we never got any cable connected event and
* dwc3_core_probe was never called and there's hence no need to call dwc3_core_remove.
* dwc3_apple_exit can be called unconditionally because it checks the state itself.
*/
dwc3_apple_exit(appledwc);
if (appledwc->state != DWC3_APPLE_PROBE_PENDING)
dwc3_core_remove(&appledwc->dwc);
}
static const struct of_device_id dwc3_apple_of_match[] = {
{ .compatible = "apple,t8103-dwc3" },
{}
};
MODULE_DEVICE_TABLE(of, dwc3_apple_of_match);
static struct platform_driver dwc3_apple_driver = {
.probe = dwc3_apple_probe,
.remove = dwc3_apple_remove,
.driver = {
.name = "dwc3-apple",
.of_match_table = dwc3_apple_of_match,
},
};
module_platform_driver(dwc3_apple_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sven Peter <sven@kernel.org>");
MODULE_DESCRIPTION("DesignWare DWC3 Apple Silicon Glue Driver");
|