* [PATCH 0/5] Apple Silicon USB3 support - dwc3
@ 2025-10-13 16:03 Sven Peter
2025-10-13 16:03 ` [PATCH 1/5] dt-bindings: usb: Add Apple dwc3 Sven Peter
` (4 more replies)
0 siblings, 5 replies; 10+ messages in thread
From: Sven Peter @ 2025-10-13 16:03 UTC (permalink / raw)
To: Janne Grunau, Neal Gompa, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thinh Nguyen, Philipp Zabel
Cc: asahi, linux-arm-kernel, linux-usb, devicetree, linux-kernel,
Sven Peter, Krzysztof Kozlowski, stable
Hi,
As discussed in v2 of the combined Apple Silicon USB3 support series
this one only contains the dwc3 changes without the DTS changes.
Link to v2 of the combined series: https://lore.kernel.org/asahi/20250906-atcphy-6-17-v2-0-52c348623ef6@kernel.org/
Changes since v2 of that series:
- Documented all functions in glue.h
- Fixed a typo in the example for the dt-bindings
- Added detailed documentation about how the dwc3 hardware needs to be
driven on this platform to the glue driver
- Renamed dwc3_apple.mode to state and folded in the probe check into
the new DWC3_APPLE_PROBE_PENDING state
- Collected tags
Best,
Sven
Signed-off-by: Sven Peter <sven@kernel.org>
---
Sven Peter (5):
dt-bindings: usb: Add Apple dwc3
usb: dwc3: dwc3_power_off_all_roothub_ports: Use ioremap_np when required
usb: dwc3: glue: Add documentation
usb: dwc3: glue: Allow more fine grained control over mode switches
usb: dwc3: Add Apple Silicon DWC3 glue layer driver
.../devicetree/bindings/usb/apple,dwc3.yaml | 80 ++++
MAINTAINERS | 2 +
drivers/usb/dwc3/Kconfig | 11 +
drivers/usb/dwc3/Makefile | 1 +
drivers/usb/dwc3/core.c | 16 +-
drivers/usb/dwc3/dwc3-apple.c | 488 +++++++++++++++++++++
drivers/usb/dwc3/gadget.c | 2 +
drivers/usb/dwc3/glue.h | 143 ++++++
drivers/usb/dwc3/host.c | 7 +-
9 files changed, 744 insertions(+), 6 deletions(-)
---
base-commit: 3a8660878839faadb4f1a6dd72c3179c1df56787
change-id: 20251013-b4-aplpe-dwc3-4f79019741f9
Best regards,
--
Sven Peter <sven@kernel.org>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 1/5] dt-bindings: usb: Add Apple dwc3
2025-10-13 16:03 [PATCH 0/5] Apple Silicon USB3 support - dwc3 Sven Peter
@ 2025-10-13 16:03 ` Sven Peter
2025-10-13 16:03 ` [PATCH 2/5] usb: dwc3: dwc3_power_off_all_roothub_ports: Use ioremap_np when required Sven Peter
` (3 subsequent siblings)
4 siblings, 0 replies; 10+ messages in thread
From: Sven Peter @ 2025-10-13 16:03 UTC (permalink / raw)
To: Janne Grunau, Neal Gompa, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thinh Nguyen, Philipp Zabel
Cc: asahi, linux-arm-kernel, linux-usb, devicetree, linux-kernel,
Sven Peter, Krzysztof Kozlowski
Apple Silicon uses Synopsys DesignWare dwc3 based USB controllers for
their Type-C ports.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Reviewed-by: Neal Gompa <neal@gompa.dev>
Signed-off-by: Sven Peter <sven@kernel.org>
---
.../devicetree/bindings/usb/apple,dwc3.yaml | 80 ++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 81 insertions(+)
diff --git a/Documentation/devicetree/bindings/usb/apple,dwc3.yaml b/Documentation/devicetree/bindings/usb/apple,dwc3.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f70c33f32c5d6172224c524fe8e7218071eb4e9b
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/apple,dwc3.yaml
@@ -0,0 +1,80 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/apple,dwc3.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Apple Silicon DWC3 USB controller
+
+maintainers:
+ - Sven Peter <sven@kernel.org>
+
+description:
+ Apple Silicon SoCs use a Synopsys DesignWare DWC3 based controller for each of
+ their Type-C ports.
+
+allOf:
+ - $ref: snps,dwc3-common.yaml#
+
+properties:
+ compatible:
+ oneOf:
+ - items:
+ - enum:
+ - apple,t6000-dwc3
+ - apple,t6020-dwc3
+ - apple,t8112-dwc3
+ - const: apple,t8103-dwc3
+ - const: apple,t8103-dwc3
+
+ reg:
+ items:
+ - description: Core DWC3 region
+ - description: Apple-specific DWC3 region
+
+ reg-names:
+ items:
+ - const: dwc3-core
+ - const: dwc3-apple
+
+ interrupts:
+ maxItems: 1
+
+ iommus:
+ maxItems: 2
+
+ resets:
+ maxItems: 1
+
+ power-domains:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - iommus
+ - resets
+ - power-domains
+ - usb-role-switch
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/apple-aic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ usb@82280000 {
+ compatible = "apple,t8103-dwc3";
+ reg = <0x82280000 0xcd00>, <0x8228cd00 0x3200>;
+ reg-names = "dwc3-core", "dwc3-apple";
+ interrupts = <AIC_IRQ 777 IRQ_TYPE_LEVEL_HIGH>;
+ iommus = <&dwc3_0_dart_0 0>, <&dwc3_0_dart_1 1>;
+
+ power-domains = <&ps_atc0_usb>;
+ resets = <&atcphy0>;
+
+ usb-role-switch;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 46126ce2f968e4f9260263f1574ee29f5ff0de1c..fa238b5371b9c5942dc89ec4fa6b1d28e2d4dda3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2437,6 +2437,7 @@ F: Documentation/devicetree/bindings/power/reset/apple,smc-reboot.yaml
F: Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml
F: Documentation/devicetree/bindings/spi/apple,spi.yaml
F: Documentation/devicetree/bindings/spmi/apple,spmi.yaml
+F: Documentation/devicetree/bindings/usb/apple,dwc3.yaml
F: Documentation/devicetree/bindings/watchdog/apple,wdt.yaml
F: arch/arm64/boot/dts/apple/
F: drivers/bluetooth/hci_bcm4377.c
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 2/5] usb: dwc3: dwc3_power_off_all_roothub_ports: Use ioremap_np when required
2025-10-13 16:03 [PATCH 0/5] Apple Silicon USB3 support - dwc3 Sven Peter
2025-10-13 16:03 ` [PATCH 1/5] dt-bindings: usb: Add Apple dwc3 Sven Peter
@ 2025-10-13 16:03 ` Sven Peter
2025-10-13 16:03 ` [PATCH 3/5] usb: dwc3: glue: Add documentation Sven Peter
` (2 subsequent siblings)
4 siblings, 0 replies; 10+ messages in thread
From: Sven Peter @ 2025-10-13 16:03 UTC (permalink / raw)
To: Janne Grunau, Neal Gompa, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thinh Nguyen, Philipp Zabel
Cc: asahi, linux-arm-kernel, linux-usb, devicetree, linux-kernel,
Sven Peter, stable
On Apple Silicon machines we can't use ioremap() / Device-nGnRE to map most
regions but must use ioremap_np() / Device-nGnRnE whenever
IORESOURCE_MEM_NONPOSTED is set. Make sure this is also done inside
dwc3_power_off_all_roothub_ports to prevent SErrors.
Fixes: 2d2a3349521d ("usb: dwc3: Add workaround for host mode VBUS glitch when boot")
Cc: stable@kernel.org
Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Reviewed-by: Neal Gompa <neal@gompa.dev>
Signed-off-by: Sven Peter <sven@kernel.org>
---
drivers/usb/dwc3/host.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 1c513bf8002ec9ec91b41bfd096cbd0da1dd2d2e..e77fd86d09cf0a36161c20ad3c83f10e67099775 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -37,7 +37,10 @@ static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc)
/* xhci regs are not mapped yet, do it temporarily here */
if (dwc->xhci_resources[0].start) {
- xhci_regs = ioremap(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END);
+ if (dwc->xhci_resources[0].flags & IORESOURCE_MEM_NONPOSTED)
+ xhci_regs = ioremap_np(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END);
+ else
+ xhci_regs = ioremap(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END);
if (!xhci_regs) {
dev_err(dwc->dev, "Failed to ioremap xhci_regs\n");
return;
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 3/5] usb: dwc3: glue: Add documentation
2025-10-13 16:03 [PATCH 0/5] Apple Silicon USB3 support - dwc3 Sven Peter
2025-10-13 16:03 ` [PATCH 1/5] dt-bindings: usb: Add Apple dwc3 Sven Peter
2025-10-13 16:03 ` [PATCH 2/5] usb: dwc3: dwc3_power_off_all_roothub_ports: Use ioremap_np when required Sven Peter
@ 2025-10-13 16:03 ` Sven Peter
2025-10-14 23:13 ` Thinh Nguyen
2025-10-13 16:03 ` [PATCH 4/5] usb: dwc3: glue: Allow more fine grained control over mode switches Sven Peter
2025-10-13 16:03 ` [PATCH 5/5] usb: dwc3: Add Apple Silicon DWC3 glue layer driver Sven Peter
4 siblings, 1 reply; 10+ messages in thread
From: Sven Peter @ 2025-10-13 16:03 UTC (permalink / raw)
To: Janne Grunau, Neal Gompa, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thinh Nguyen, Philipp Zabel
Cc: asahi, linux-arm-kernel, linux-usb, devicetree, linux-kernel,
Sven Peter
We're about to add more exported functions to be used inside glue
driver which will need more detailed documentation explaining how
they must be used. Let's also add documentation for the functions
already available.
Signed-off-by: Sven Peter <sven@kernel.org>
---
drivers/usb/dwc3/glue.h | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h
index 2efd00e763be4fc51911f32d43054059e61fb43a..7f326cff12458901904d4c32f62ed9357d0f6e3b 100644
--- a/drivers/usb/dwc3/glue.h
+++ b/drivers/usb/dwc3/glue.h
@@ -22,9 +22,36 @@ struct dwc3_probe_data {
bool ignore_clocks_and_resets;
};
+/**
+ * dwc3_core_probe - Initialize the core dwc3 driver
+ * @data: Initialization and configuration parameters for the controller
+ *
+ * Initializes the DesignWare USB3 core driver by setting up resources,
+ * registering interrupts, performing hardware setup, and preparing
+ * the controller for operation in the appropriate mode (host, gadget,
+ * or OTG). This is the main initialization function called by glue
+ * layer drivers to set up the core controller.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
int dwc3_core_probe(const struct dwc3_probe_data *data);
+
+/**
+ * dwc3_core_remove - Deinitialize and remove the core dwc3 driver
+ * @dwc: Pointer to DWC3 controller context
+ *
+ * Cleans up resources and disables the dwc3 core driver. This should be called
+ * during driver removal or when the glue layer needs to shut down the
+ * controller completely.
+ */
void dwc3_core_remove(struct dwc3 *dwc);
+/*
+ * The following callbacks are provided for glue drivers to call from their
+ * own pm callbacks provided in struct dev_pm_ops. Glue drivers can perform
+ * platform-specific work before or after calling these functions and delegate
+ * the core suspend/resume operations to the core driver.
+ */
int dwc3_runtime_suspend(struct dwc3 *dwc);
int dwc3_runtime_resume(struct dwc3 *dwc);
int dwc3_runtime_idle(struct dwc3 *dwc);
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 4/5] usb: dwc3: glue: Allow more fine grained control over mode switches
2025-10-13 16:03 [PATCH 0/5] Apple Silicon USB3 support - dwc3 Sven Peter
` (2 preceding siblings ...)
2025-10-13 16:03 ` [PATCH 3/5] usb: dwc3: glue: Add documentation Sven Peter
@ 2025-10-13 16:03 ` Sven Peter
2025-10-14 23:16 ` Thinh Nguyen
2025-10-13 16:03 ` [PATCH 5/5] usb: dwc3: Add Apple Silicon DWC3 glue layer driver Sven Peter
4 siblings, 1 reply; 10+ messages in thread
From: Sven Peter @ 2025-10-13 16:03 UTC (permalink / raw)
To: Janne Grunau, Neal Gompa, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thinh Nguyen, Philipp Zabel
Cc: asahi, linux-arm-kernel, linux-usb, devicetree, linux-kernel,
Sven Peter
We need fine grained control over mode switched on the DWC3 controller
present on Apple Silicon. Export core, host and gadget init and exit,
ptrcap and susphy control functions. Also introduce an additional
parameter to probe_data that allows to skip the final initialization
step that would bring up host or gadget mode.
Signed-off-by: Sven Peter <sven@kernel.org>
---
drivers/usb/dwc3/core.c | 16 +++++--
drivers/usb/dwc3/gadget.c | 2 +
drivers/usb/dwc3/glue.h | 116 ++++++++++++++++++++++++++++++++++++++++++++++
drivers/usb/dwc3/host.c | 2 +
4 files changed, 131 insertions(+), 5 deletions(-)
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index ae140c356295c03df3982ff4fa95f8638296e52d..526c0453b99aad79d99a842797e52d9290456d76 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -132,6 +132,7 @@ void dwc3_enable_susphy(struct dwc3 *dwc, bool enable)
dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(i), reg);
}
}
+EXPORT_SYMBOL_GPL(dwc3_enable_susphy);
void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy)
{
@@ -158,6 +159,7 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy)
dwc->current_dr_role = mode;
trace_dwc3_set_prtcap(mode);
}
+EXPORT_SYMBOL_GPL(dwc3_set_prtcap);
static void __dwc3_set_mode(struct work_struct *work)
{
@@ -975,7 +977,7 @@ static void dwc3_clk_disable(struct dwc3 *dwc)
clk_disable_unprepare(dwc->bus_clk);
}
-static void dwc3_core_exit(struct dwc3 *dwc)
+void dwc3_core_exit(struct dwc3 *dwc)
{
dwc3_event_buffers_cleanup(dwc);
dwc3_phy_power_off(dwc);
@@ -983,6 +985,7 @@ static void dwc3_core_exit(struct dwc3 *dwc)
dwc3_clk_disable(dwc);
reset_control_assert(dwc->reset);
}
+EXPORT_SYMBOL_GPL(dwc3_core_exit);
static bool dwc3_core_is_valid(struct dwc3 *dwc)
{
@@ -1328,7 +1331,7 @@ static void dwc3_config_threshold(struct dwc3 *dwc)
*
* Returns 0 on success otherwise negative errno.
*/
-static int dwc3_core_init(struct dwc3 *dwc)
+int dwc3_core_init(struct dwc3 *dwc)
{
unsigned int hw_mode;
u32 reg;
@@ -1528,6 +1531,7 @@ static int dwc3_core_init(struct dwc3 *dwc)
return ret;
}
+EXPORT_SYMBOL_GPL(dwc3_core_init);
static int dwc3_core_get_phy(struct dwc3 *dwc)
{
@@ -2299,9 +2303,11 @@ int dwc3_core_probe(const struct dwc3_probe_data *data)
dwc3_check_params(dwc);
dwc3_debugfs_init(dwc);
- ret = dwc3_core_init_mode(dwc);
- if (ret)
- goto err_exit_debugfs;
+ if (!data->skip_core_init_mode) {
+ ret = dwc3_core_init_mode(dwc);
+ if (ret)
+ goto err_exit_debugfs;
+ }
pm_runtime_put(dev);
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 6f18b4840a25d176abb4134581ad0ce68ba19ffc..1f67fb6aead5725c2e7b553c635eab985c9e1d48 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -4810,6 +4810,7 @@ int dwc3_gadget_init(struct dwc3 *dwc)
err0:
return ret;
}
+EXPORT_SYMBOL_GPL(dwc3_gadget_init);
/* -------------------------------------------------------------------------- */
@@ -4828,6 +4829,7 @@ void dwc3_gadget_exit(struct dwc3 *dwc)
dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2,
dwc->ep0_trb, dwc->ep0_trb_addr);
}
+EXPORT_SYMBOL_GPL(dwc3_gadget_exit);
int dwc3_gadget_suspend(struct dwc3 *dwc)
{
diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h
index 7f326cff12458901904d4c32f62ed9357d0f6e3b..376e7aa74281c13bfb3a85dc033622bcff8d2acb 100644
--- a/drivers/usb/dwc3/glue.h
+++ b/drivers/usb/dwc3/glue.h
@@ -15,11 +15,14 @@
* @res: resource for the DWC3 core mmio region
* @ignore_clocks_and_resets: clocks and resets defined for the device should
* be ignored by the DWC3 core, as they are managed by the glue
+ * @skip_core_init_mode: Skip the finial initialization of the target mode, as
+ * it must be managed by the glue
*/
struct dwc3_probe_data {
struct dwc3 *dwc;
struct resource *res;
bool ignore_clocks_and_resets;
+ bool skip_core_init_mode;
};
/**
@@ -60,4 +63,117 @@ int dwc3_pm_resume(struct dwc3 *dwc);
void dwc3_pm_complete(struct dwc3 *dwc);
int dwc3_pm_prepare(struct dwc3 *dwc);
+
+/* All of the following functions must only be used with skip_core_init_mode */
+
+/**
+ * dwc3_core_init - Initialize DWC3 core hardware
+ * @dwc: Pointer to DWC3 controller context
+ *
+ * Configures and initializes the core hardware, usually done by dwc3_core_probe.
+ * This function is provided for platforms that use skip_core_init_mode and need
+ * to finalize the core initialization after some platform-specific setup.
+ * It must only be called when using skip_core_init_mode and before
+ * dwc3_host_init or dwc3_gadget_init.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int dwc3_core_init(struct dwc3 *dwc);
+
+/**
+ * dwc3_core_exit - Shut down DWC3 core hardware
+ * @dwc: Pointer to DWC3 controller context
+ *
+ * Disables and cleans up the core hardware state. This is usually handled
+ * internally by dwc3 and must only be called when using skip_core_init_mode
+ * and only after dwc3_core_init. Afterwards, dwc3_core_init may be called
+ * again.
+ */
+void dwc3_core_exit(struct dwc3 *dwc);
+
+/**
+ * dwc3_host_init - Initialize host mode operation
+ * @dwc: Pointer to DWC3 controller context
+ *
+ * Initializes the controller for USB host mode operation, usually done by
+ * dwc3_core_probe or from within the dwc3 USB role switch callback.
+ * This function is provided for platforms that use skip_core_init_mode and need
+ * to finalize the host initialization after some platform-specific setup.
+ * It must not be called before dwc3_core_init or when skip_core_init_mode is
+ * not used. It must also not be called when gadget or host mode has already
+ * been initialized.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int dwc3_host_init(struct dwc3 *dwc);
+
+/**
+ * dwc3_host_exit - Shut down host mode operation
+ * @dwc: Pointer to DWC3 controller context
+ *
+ * Disables and cleans up host mode resources, usually done by
+ * the dwc3 USB role switch callback before switching controller mode.
+ * It must only be called when skip_core_init_mode is used and only after
+ * dwc3_host_init.
+ */
+void dwc3_host_exit(struct dwc3 *dwc);
+
+/**
+ * dwc3_gadget_init - Initialize gadget mode operation
+ * @dwc: Pointer to DWC3 controller context
+ *
+ * Initializes the controller for USB gadget mode operation, usually done by
+ * dwc3_core_probe or from within the dwc3 USB role switch callback. This
+ * function is provided for platforms that use skip_core_init_mode and need to
+ * finalize the gadget initialization after some platform-specific setup.
+ * It must not be called before dwc3_core_init or when skip_core_init_mode is
+ * not used. It must also not be called when gadget or host mode has already
+ * been initialized.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int dwc3_gadget_init(struct dwc3 *dwc);
+
+/**
+ * dwc3_gadget_exit - Shut down gadget mode operation
+ * @dwc: Pointer to DWC3 controller context
+ *
+ * Disables and cleans up gadget mode resources, usually done by
+ * the dwc3 USB role switch callback before switching controller mode.
+ * It must only be called when skip_core_init_mode is used and only after
+ * dwc3_gadget_init.
+ */
+void dwc3_gadget_exit(struct dwc3 *dwc);
+
+/**
+ * dwc3_enable_susphy - Control SUSPHY status for all USB ports
+ * @dwc: Pointer to DWC3 controller context
+ * @enable: True to enable SUSPHY, false to disable
+ *
+ * Enables or disables the USB3 PHY SUSPEND and USB2 PHY SUSPHY feature for
+ * all available ports.
+ * This is usually handled by the dwc3 core code and should only be used
+ * when skip_core_init_mode is used and the glue layer needs to manage SUSPHY
+ * settings itself, e.g., due to platform-specific requirements during mode
+ * switches.
+ */
+void dwc3_enable_susphy(struct dwc3 *dwc, bool enable);
+
+/**
+ * dwc3_set_prtcap - Set the USB controller PRTCAP mode
+ * @dwc: Pointer to DWC3 controller context
+ * @mode: Target mode, must be one of DWC3_GCTL_PRTCAP_{HOST,DEVICE,OTG}
+ * @ignore_susphy: If true, skip disabling the SUSPHY and keep the current state
+ *
+ * Updates PRTCAP of the controller and current_dr_role inside the dwc3
+ * structure. For DRD controllers, this also disables SUSPHY unless explicitly
+ * told to skip via the ignore_susphy parameter.
+ *
+ * This is usually handled by the dwc3 core code and should only be used
+ * when skip_core_init_mode is used and the glue layer needs to manage mode
+ * transitions itself due to platform-specific requirements. It must be called
+ * with the correct mode before calling dwc3_host_init or dwc3_gadget_init.
+ */
+void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy);
+
#endif
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index e77fd86d09cf0a36161c20ad3c83f10e67099775..cf6512ed17a69134e6ca1b884f76c1439693fab1 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -220,6 +220,7 @@ int dwc3_host_init(struct dwc3 *dwc)
platform_device_put(xhci);
return ret;
}
+EXPORT_SYMBOL_GPL(dwc3_host_init);
void dwc3_host_exit(struct dwc3 *dwc)
{
@@ -230,3 +231,4 @@ void dwc3_host_exit(struct dwc3 *dwc)
platform_device_unregister(dwc->xhci);
dwc->xhci = NULL;
}
+EXPORT_SYMBOL_GPL(dwc3_host_exit);
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 5/5] usb: dwc3: Add Apple Silicon DWC3 glue layer driver
2025-10-13 16:03 [PATCH 0/5] Apple Silicon USB3 support - dwc3 Sven Peter
` (3 preceding siblings ...)
2025-10-13 16:03 ` [PATCH 4/5] usb: dwc3: glue: Allow more fine grained control over mode switches Sven Peter
@ 2025-10-13 16:03 ` Sven Peter
2025-10-14 23:22 ` Thinh Nguyen
4 siblings, 1 reply; 10+ messages in thread
From: Sven Peter @ 2025-10-13 16:03 UTC (permalink / raw)
To: Janne Grunau, Neal Gompa, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thinh Nguyen, Philipp Zabel
Cc: asahi, linux-arm-kernel, linux-usb, devicetree, linux-kernel,
Sven Peter
The dwc3 controller present on Apple Silicon SoCs like the M1 requires
a specific order of operations synchronized between its PHY and its
Type-C controller. Specifically, the PHY first has to go through initial
bringup (which requires knowledge of the lane mode and orientation)
before dwc3 itself can be brought up and can then finalize the PHY
configuration.
Additionally, dwc3 has to be teared down and re-initialized whenever
the cable is changed due to hardware quirks that prevent a new device
from being recognized and due to the PHY being unable to switch lane
mode or orientation while dwc3 is up and running.
These controllers also have a Apple-specific MMIO region after the
common dwc3 region where some controls have to be updated. PHY bringup
and shutdown also requires SUSPHY to be enabled for the ports to work
correctly.
In the future, this driver will also gain support for USB3-via-USB4
tunneling which will require additional tweaks.
Add a glue driver that takes of all of these constraints.
Reviewed-by: Neal Gompa <neal@gompa.dev>
Signed-off-by: Sven Peter <sven@kernel.org>
---
MAINTAINERS | 1 +
drivers/usb/dwc3/Kconfig | 11 +
drivers/usb/dwc3/Makefile | 1 +
drivers/usb/dwc3/dwc3-apple.c | 488 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 501 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index fa238b5371b9c5942dc89ec4fa6b1d28e2d4dda3..28bfefd7ecb895e2721800dbb3b954c4bdd9f539 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2462,6 +2462,7 @@ F: drivers/pwm/pwm-apple.c
F: drivers/soc/apple/*
F: drivers/spi/spi-apple.c
F: drivers/spmi/spmi-apple-controller.c
+F: drivers/usb/dwc3/dwc3-apple.c
F: drivers/video/backlight/apple_dwi_bl.c
F: drivers/watchdog/apple_wdt.c
F: include/dt-bindings/interrupt-controller/apple-aic.h
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
index 4925d15084f816d3ff92059b476ebcc799b56b51..bf3e04635131005096c6bc1802b251490ad2f483 100644
--- a/drivers/usb/dwc3/Kconfig
+++ b/drivers/usb/dwc3/Kconfig
@@ -200,4 +200,15 @@ config USB_DWC3_GENERIC_PLAT
the dwc3 child node in the device tree.
Say 'Y' or 'M' here if your platform integrates DWC3 in a similar way.
+config USB_DWC3_APPLE
+ tristate "Apple Silicon DWC3 Platform Driver"
+ depends on OF && ARCH_APPLE
+ default USB_DWC3
+ select USB_ROLE_SWITCH
+ help
+ Support Apple Silicon SoCs with DesignWare Core USB3 IP.
+ The DesignWare Core USB3 IP has to be used in dual-role
+ mode on these machines.
+ Say 'Y' or 'M' if you have such device.
+
endif
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index 96469e48ff9d189cc8d0b65e65424eae2158bcfe..89d46ab5006856c51b5007ecdd8fbdf431ecba40 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -43,6 +43,7 @@ endif
##
obj-$(CONFIG_USB_DWC3_AM62) += dwc3-am62.o
+obj-$(CONFIG_USB_DWC3_APPLE) += dwc3-apple.o
obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-omap.o
obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o
obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o
diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c
new file mode 100644
index 0000000000000000000000000000000000000000..e9dd6b22f485daed01618e64d93a40487fb20e3c
--- /dev/null
+++ b/drivers/usb/dwc3/dwc3-apple.c
@@ -0,0 +1,488 @@
+// 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
+ * @resets: 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 *resets;
+ 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;
+
+ 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->resets);
+ if (ret) {
+ dev_err(appledwc->dev, "Failed to deassert resets, 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->resets);
+ if (ret_reset)
+ dev_warn(appledwc->dev, "Failed to assert resets, 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->resets);
+ if (ret) {
+ dev_err(appledwc->dev, "Failed to assert resets, 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->resets = devm_reset_control_array_get_exclusive(dev);
+ if (IS_ERR(appledwc->resets))
+ return dev_err_probe(&pdev->dev, PTR_ERR(appledwc->resets),
+ "Failed to get resets\n");
+
+ ret = reset_control_assert(appledwc->resets);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to assert resets, 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");
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 3/5] usb: dwc3: glue: Add documentation
2025-10-13 16:03 ` [PATCH 3/5] usb: dwc3: glue: Add documentation Sven Peter
@ 2025-10-14 23:13 ` Thinh Nguyen
0 siblings, 0 replies; 10+ messages in thread
From: Thinh Nguyen @ 2025-10-14 23:13 UTC (permalink / raw)
To: Sven Peter
Cc: Janne Grunau, Neal Gompa, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thinh Nguyen, Philipp Zabel,
asahi@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
linux-usb@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org
On Mon, Oct 13, 2025, Sven Peter wrote:
> We're about to add more exported functions to be used inside glue
> driver which will need more detailed documentation explaining how
> they must be used. Let's also add documentation for the functions
> already available.
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> drivers/usb/dwc3/glue.h | 27 +++++++++++++++++++++++++++
> 1 file changed, 27 insertions(+)
>
> diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h
> index 2efd00e763be4fc51911f32d43054059e61fb43a..7f326cff12458901904d4c32f62ed9357d0f6e3b 100644
> --- a/drivers/usb/dwc3/glue.h
> +++ b/drivers/usb/dwc3/glue.h
> @@ -22,9 +22,36 @@ struct dwc3_probe_data {
> bool ignore_clocks_and_resets;
> };
>
> +/**
> + * dwc3_core_probe - Initialize the core dwc3 driver
> + * @data: Initialization and configuration parameters for the controller
> + *
> + * Initializes the DesignWare USB3 core driver by setting up resources,
> + * registering interrupts, performing hardware setup, and preparing
> + * the controller for operation in the appropriate mode (host, gadget,
> + * or OTG). This is the main initialization function called by glue
> + * layer drivers to set up the core controller.
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> int dwc3_core_probe(const struct dwc3_probe_data *data);
> +
> +/**
> + * dwc3_core_remove - Deinitialize and remove the core dwc3 driver
> + * @dwc: Pointer to DWC3 controller context
> + *
> + * Cleans up resources and disables the dwc3 core driver. This should be called
> + * during driver removal or when the glue layer needs to shut down the
> + * controller completely.
> + */
> void dwc3_core_remove(struct dwc3 *dwc);
>
> +/*
> + * The following callbacks are provided for glue drivers to call from their
> + * own pm callbacks provided in struct dev_pm_ops. Glue drivers can perform
> + * platform-specific work before or after calling these functions and delegate
> + * the core suspend/resume operations to the core driver.
> + */
> int dwc3_runtime_suspend(struct dwc3 *dwc);
> int dwc3_runtime_resume(struct dwc3 *dwc);
> int dwc3_runtime_idle(struct dwc3 *dwc);
>
> --
> 2.34.1
>
>
Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Thanks!
Thinh
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 4/5] usb: dwc3: glue: Allow more fine grained control over mode switches
2025-10-13 16:03 ` [PATCH 4/5] usb: dwc3: glue: Allow more fine grained control over mode switches Sven Peter
@ 2025-10-14 23:16 ` Thinh Nguyen
2025-10-15 7:32 ` Sven Peter
0 siblings, 1 reply; 10+ messages in thread
From: Thinh Nguyen @ 2025-10-14 23:16 UTC (permalink / raw)
To: Sven Peter
Cc: Janne Grunau, Neal Gompa, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thinh Nguyen, Philipp Zabel,
asahi@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
linux-usb@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org
On Mon, Oct 13, 2025, Sven Peter wrote:
> We need fine grained control over mode switched on the DWC3 controller
> present on Apple Silicon. Export core, host and gadget init and exit,
> ptrcap and susphy control functions. Also introduce an additional
> parameter to probe_data that allows to skip the final initialization
> step that would bring up host or gadget mode.
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> drivers/usb/dwc3/core.c | 16 +++++--
> drivers/usb/dwc3/gadget.c | 2 +
> drivers/usb/dwc3/glue.h | 116 ++++++++++++++++++++++++++++++++++++++++++++++
> drivers/usb/dwc3/host.c | 2 +
> 4 files changed, 131 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
> index ae140c356295c03df3982ff4fa95f8638296e52d..526c0453b99aad79d99a842797e52d9290456d76 100644
> --- a/drivers/usb/dwc3/core.c
> +++ b/drivers/usb/dwc3/core.c
> @@ -132,6 +132,7 @@ void dwc3_enable_susphy(struct dwc3 *dwc, bool enable)
> dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(i), reg);
> }
> }
> +EXPORT_SYMBOL_GPL(dwc3_enable_susphy);
>
> void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy)
> {
> @@ -158,6 +159,7 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy)
> dwc->current_dr_role = mode;
> trace_dwc3_set_prtcap(mode);
> }
> +EXPORT_SYMBOL_GPL(dwc3_set_prtcap);
>
> static void __dwc3_set_mode(struct work_struct *work)
> {
> @@ -975,7 +977,7 @@ static void dwc3_clk_disable(struct dwc3 *dwc)
> clk_disable_unprepare(dwc->bus_clk);
> }
>
> -static void dwc3_core_exit(struct dwc3 *dwc)
> +void dwc3_core_exit(struct dwc3 *dwc)
> {
> dwc3_event_buffers_cleanup(dwc);
> dwc3_phy_power_off(dwc);
> @@ -983,6 +985,7 @@ static void dwc3_core_exit(struct dwc3 *dwc)
> dwc3_clk_disable(dwc);
> reset_control_assert(dwc->reset);
> }
> +EXPORT_SYMBOL_GPL(dwc3_core_exit);
>
> static bool dwc3_core_is_valid(struct dwc3 *dwc)
> {
> @@ -1328,7 +1331,7 @@ static void dwc3_config_threshold(struct dwc3 *dwc)
> *
> * Returns 0 on success otherwise negative errno.
> */
> -static int dwc3_core_init(struct dwc3 *dwc)
> +int dwc3_core_init(struct dwc3 *dwc)
> {
> unsigned int hw_mode;
> u32 reg;
> @@ -1528,6 +1531,7 @@ static int dwc3_core_init(struct dwc3 *dwc)
>
> return ret;
> }
> +EXPORT_SYMBOL_GPL(dwc3_core_init);
>
> static int dwc3_core_get_phy(struct dwc3 *dwc)
> {
> @@ -2299,9 +2303,11 @@ int dwc3_core_probe(const struct dwc3_probe_data *data)
> dwc3_check_params(dwc);
> dwc3_debugfs_init(dwc);
>
> - ret = dwc3_core_init_mode(dwc);
> - if (ret)
> - goto err_exit_debugfs;
> + if (!data->skip_core_init_mode) {
> + ret = dwc3_core_init_mode(dwc);
> + if (ret)
> + goto err_exit_debugfs;
> + }
>
> pm_runtime_put(dev);
>
> diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
> index 6f18b4840a25d176abb4134581ad0ce68ba19ffc..1f67fb6aead5725c2e7b553c635eab985c9e1d48 100644
> --- a/drivers/usb/dwc3/gadget.c
> +++ b/drivers/usb/dwc3/gadget.c
> @@ -4810,6 +4810,7 @@ int dwc3_gadget_init(struct dwc3 *dwc)
> err0:
> return ret;
> }
> +EXPORT_SYMBOL_GPL(dwc3_gadget_init);
>
> /* -------------------------------------------------------------------------- */
>
> @@ -4828,6 +4829,7 @@ void dwc3_gadget_exit(struct dwc3 *dwc)
> dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2,
> dwc->ep0_trb, dwc->ep0_trb_addr);
> }
> +EXPORT_SYMBOL_GPL(dwc3_gadget_exit);
>
> int dwc3_gadget_suspend(struct dwc3 *dwc)
> {
> diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h
> index 7f326cff12458901904d4c32f62ed9357d0f6e3b..376e7aa74281c13bfb3a85dc033622bcff8d2acb 100644
> --- a/drivers/usb/dwc3/glue.h
> +++ b/drivers/usb/dwc3/glue.h
> @@ -15,11 +15,14 @@
> * @res: resource for the DWC3 core mmio region
> * @ignore_clocks_and_resets: clocks and resets defined for the device should
> * be ignored by the DWC3 core, as they are managed by the glue
> + * @skip_core_init_mode: Skip the finial initialization of the target mode, as
> + * it must be managed by the glue
> */
> struct dwc3_probe_data {
> struct dwc3 *dwc;
> struct resource *res;
> bool ignore_clocks_and_resets;
> + bool skip_core_init_mode;
> };
>
> /**
> @@ -60,4 +63,117 @@ int dwc3_pm_resume(struct dwc3 *dwc);
> void dwc3_pm_complete(struct dwc3 *dwc);
> int dwc3_pm_prepare(struct dwc3 *dwc);
>
> +
> +/* All of the following functions must only be used with skip_core_init_mode */
> +
> +/**
> + * dwc3_core_init - Initialize DWC3 core hardware
> + * @dwc: Pointer to DWC3 controller context
> + *
> + * Configures and initializes the core hardware, usually done by dwc3_core_probe.
> + * This function is provided for platforms that use skip_core_init_mode and need
> + * to finalize the core initialization after some platform-specific setup.
> + * It must only be called when using skip_core_init_mode and before
> + * dwc3_host_init or dwc3_gadget_init.
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int dwc3_core_init(struct dwc3 *dwc);
> +
> +/**
> + * dwc3_core_exit - Shut down DWC3 core hardware
> + * @dwc: Pointer to DWC3 controller context
> + *
> + * Disables and cleans up the core hardware state. This is usually handled
> + * internally by dwc3 and must only be called when using skip_core_init_mode
> + * and only after dwc3_core_init. Afterwards, dwc3_core_init may be called
> + * again.
> + */
> +void dwc3_core_exit(struct dwc3 *dwc);
> +
> +/**
> + * dwc3_host_init - Initialize host mode operation
> + * @dwc: Pointer to DWC3 controller context
> + *
> + * Initializes the controller for USB host mode operation, usually done by
> + * dwc3_core_probe or from within the dwc3 USB role switch callback.
> + * This function is provided for platforms that use skip_core_init_mode and need
> + * to finalize the host initialization after some platform-specific setup.
> + * It must not be called before dwc3_core_init or when skip_core_init_mode is
> + * not used. It must also not be called when gadget or host mode has already
> + * been initialized.
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int dwc3_host_init(struct dwc3 *dwc);
> +
> +/**
> + * dwc3_host_exit - Shut down host mode operation
> + * @dwc: Pointer to DWC3 controller context
> + *
> + * Disables and cleans up host mode resources, usually done by
> + * the dwc3 USB role switch callback before switching controller mode.
> + * It must only be called when skip_core_init_mode is used and only after
> + * dwc3_host_init.
> + */
> +void dwc3_host_exit(struct dwc3 *dwc);
> +
> +/**
> + * dwc3_gadget_init - Initialize gadget mode operation
> + * @dwc: Pointer to DWC3 controller context
> + *
> + * Initializes the controller for USB gadget mode operation, usually done by
> + * dwc3_core_probe or from within the dwc3 USB role switch callback. This
> + * function is provided for platforms that use skip_core_init_mode and need to
> + * finalize the gadget initialization after some platform-specific setup.
> + * It must not be called before dwc3_core_init or when skip_core_init_mode is
> + * not used. It must also not be called when gadget or host mode has already
> + * been initialized.
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int dwc3_gadget_init(struct dwc3 *dwc);
> +
> +/**
> + * dwc3_gadget_exit - Shut down gadget mode operation
> + * @dwc: Pointer to DWC3 controller context
> + *
> + * Disables and cleans up gadget mode resources, usually done by
> + * the dwc3 USB role switch callback before switching controller mode.
> + * It must only be called when skip_core_init_mode is used and only after
> + * dwc3_gadget_init.
> + */
> +void dwc3_gadget_exit(struct dwc3 *dwc);
> +
> +/**
> + * dwc3_enable_susphy - Control SUSPHY status for all USB ports
> + * @dwc: Pointer to DWC3 controller context
> + * @enable: True to enable SUSPHY, false to disable
> + *
> + * Enables or disables the USB3 PHY SUSPEND and USB2 PHY SUSPHY feature for
> + * all available ports.
> + * This is usually handled by the dwc3 core code and should only be used
> + * when skip_core_init_mode is used and the glue layer needs to manage SUSPHY
> + * settings itself, e.g., due to platform-specific requirements during mode
> + * switches.
> + */
> +void dwc3_enable_susphy(struct dwc3 *dwc, bool enable);
> +
> +/**
> + * dwc3_set_prtcap - Set the USB controller PRTCAP mode
> + * @dwc: Pointer to DWC3 controller context
> + * @mode: Target mode, must be one of DWC3_GCTL_PRTCAP_{HOST,DEVICE,OTG}
> + * @ignore_susphy: If true, skip disabling the SUSPHY and keep the current state
> + *
> + * Updates PRTCAP of the controller and current_dr_role inside the dwc3
> + * structure. For DRD controllers, this also disables SUSPHY unless explicitly
> + * told to skip via the ignore_susphy parameter.
> + *
> + * This is usually handled by the dwc3 core code and should only be used
> + * when skip_core_init_mode is used and the glue layer needs to manage mode
> + * transitions itself due to platform-specific requirements. It must be called
> + * with the correct mode before calling dwc3_host_init or dwc3_gadget_init.
> + */
> +void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy);
> +
> #endif
> diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
> index e77fd86d09cf0a36161c20ad3c83f10e67099775..cf6512ed17a69134e6ca1b884f76c1439693fab1 100644
> --- a/drivers/usb/dwc3/host.c
> +++ b/drivers/usb/dwc3/host.c
> @@ -220,6 +220,7 @@ int dwc3_host_init(struct dwc3 *dwc)
> platform_device_put(xhci);
> return ret;
> }
> +EXPORT_SYMBOL_GPL(dwc3_host_init);
>
> void dwc3_host_exit(struct dwc3 *dwc)
> {
> @@ -230,3 +231,4 @@ void dwc3_host_exit(struct dwc3 *dwc)
> platform_device_unregister(dwc->xhci);
> dwc->xhci = NULL;
> }
> +EXPORT_SYMBOL_GPL(dwc3_host_exit);
>
> --
> 2.34.1
>
>
Thanks for the documentations!
Please rebase against Greg's usb-testing branch. I think there is
conflict.
Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Thanks,
Thinh
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 5/5] usb: dwc3: Add Apple Silicon DWC3 glue layer driver
2025-10-13 16:03 ` [PATCH 5/5] usb: dwc3: Add Apple Silicon DWC3 glue layer driver Sven Peter
@ 2025-10-14 23:22 ` Thinh Nguyen
0 siblings, 0 replies; 10+ messages in thread
From: Thinh Nguyen @ 2025-10-14 23:22 UTC (permalink / raw)
To: Sven Peter
Cc: Janne Grunau, Neal Gompa, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Thinh Nguyen, Philipp Zabel,
asahi@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
linux-usb@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org
On Mon, Oct 13, 2025, Sven Peter wrote:
> The dwc3 controller present on Apple Silicon SoCs like the M1 requires
> a specific order of operations synchronized between its PHY and its
> Type-C controller. Specifically, the PHY first has to go through initial
> bringup (which requires knowledge of the lane mode and orientation)
> before dwc3 itself can be brought up and can then finalize the PHY
> configuration.
> Additionally, dwc3 has to be teared down and re-initialized whenever
> the cable is changed due to hardware quirks that prevent a new device
> from being recognized and due to the PHY being unable to switch lane
> mode or orientation while dwc3 is up and running.
>
> These controllers also have a Apple-specific MMIO region after the
> common dwc3 region where some controls have to be updated. PHY bringup
> and shutdown also requires SUSPHY to be enabled for the ports to work
> correctly.
>
> In the future, this driver will also gain support for USB3-via-USB4
> tunneling which will require additional tweaks.
>
> Add a glue driver that takes of all of these constraints.
>
> Reviewed-by: Neal Gompa <neal@gompa.dev>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> MAINTAINERS | 1 +
> drivers/usb/dwc3/Kconfig | 11 +
> drivers/usb/dwc3/Makefile | 1 +
> drivers/usb/dwc3/dwc3-apple.c | 488 ++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 501 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index fa238b5371b9c5942dc89ec4fa6b1d28e2d4dda3..28bfefd7ecb895e2721800dbb3b954c4bdd9f539 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2462,6 +2462,7 @@ F: drivers/pwm/pwm-apple.c
> F: drivers/soc/apple/*
> F: drivers/spi/spi-apple.c
> F: drivers/spmi/spmi-apple-controller.c
> +F: drivers/usb/dwc3/dwc3-apple.c
> F: drivers/video/backlight/apple_dwi_bl.c
> F: drivers/watchdog/apple_wdt.c
> F: include/dt-bindings/interrupt-controller/apple-aic.h
> diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
> index 4925d15084f816d3ff92059b476ebcc799b56b51..bf3e04635131005096c6bc1802b251490ad2f483 100644
> --- a/drivers/usb/dwc3/Kconfig
> +++ b/drivers/usb/dwc3/Kconfig
> @@ -200,4 +200,15 @@ config USB_DWC3_GENERIC_PLAT
> the dwc3 child node in the device tree.
> Say 'Y' or 'M' here if your platform integrates DWC3 in a similar way.
>
> +config USB_DWC3_APPLE
> + tristate "Apple Silicon DWC3 Platform Driver"
> + depends on OF && ARCH_APPLE
> + default USB_DWC3
> + select USB_ROLE_SWITCH
> + help
> + Support Apple Silicon SoCs with DesignWare Core USB3 IP.
> + The DesignWare Core USB3 IP has to be used in dual-role
> + mode on these machines.
> + Say 'Y' or 'M' if you have such device.
> +
> endif
> diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
> index 96469e48ff9d189cc8d0b65e65424eae2158bcfe..89d46ab5006856c51b5007ecdd8fbdf431ecba40 100644
> --- a/drivers/usb/dwc3/Makefile
> +++ b/drivers/usb/dwc3/Makefile
> @@ -43,6 +43,7 @@ endif
> ##
>
> obj-$(CONFIG_USB_DWC3_AM62) += dwc3-am62.o
> +obj-$(CONFIG_USB_DWC3_APPLE) += dwc3-apple.o
> obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-omap.o
> obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o
> obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o
> diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..e9dd6b22f485daed01618e64d93a40487fb20e3c
> --- /dev/null
> +++ b/drivers/usb/dwc3/dwc3-apple.c
> @@ -0,0 +1,488 @@
> +// 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://urldefense.com/v3/__https://www.ti.com__;!!A4F2R9G_pg!cHH-fr6cJjBLnchcSBeMykNeZE6tZYmFSwV9PJInp84RRTI_bCKCLf5S1N8Zwnqrq2_mz0Ps6uPWVfAfXg$
> + */
> +
> +#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.
> + */
Thanks for capturing this info.
> +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
> + * @resets: 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 *resets;
> + 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;
> +
> + 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->resets);
> + if (ret) {
> + dev_err(appledwc->dev, "Failed to deassert resets, 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->resets);
> + if (ret_reset)
> + dev_warn(appledwc->dev, "Failed to assert resets, 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->resets);
> + if (ret) {
> + dev_err(appledwc->dev, "Failed to assert resets, 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->resets = devm_reset_control_array_get_exclusive(dev);
> + if (IS_ERR(appledwc->resets))
> + return dev_err_probe(&pdev->dev, PTR_ERR(appledwc->resets),
> + "Failed to get resets\n");
> +
> + ret = reset_control_assert(appledwc->resets);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to assert resets, 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");
>
> --
> 2.34.1
>
>
May need to rebase against Greg's usb-testing branch. There's a new (but
minor change) DWC3_DEFAULT_PROPERTIES introduced in the glue.h
Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Thanks,
Thinh
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 4/5] usb: dwc3: glue: Allow more fine grained control over mode switches
2025-10-14 23:16 ` Thinh Nguyen
@ 2025-10-15 7:32 ` Sven Peter
0 siblings, 0 replies; 10+ messages in thread
From: Sven Peter @ 2025-10-15 7:32 UTC (permalink / raw)
To: Thinh Nguyen
Cc: Janne Grunau, Neal Gompa, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Philipp Zabel,
asahi@lists.linux.dev, linux-arm-kernel@lists.infradead.org,
linux-usb@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org
On 15.10.25 01:16, Thinh Nguyen wrote:
> On Mon, Oct 13, 2025, Sven Peter wrote:
>> We need fine grained control over mode switched on the DWC3 controller
>> present on Apple Silicon. Export core, host and gadget init and exit,
>> ptrcap and susphy control functions. Also introduce an additional
>> parameter to probe_data that allows to skip the final initialization
>> step that would bring up host or gadget mode.
>>
>> Signed-off-by: Sven Peter <sven@kernel.org>
>> ---
>> drivers/usb/dwc3/core.c | 16 +++++--
>> drivers/usb/dwc3/gadget.c | 2 +
>> drivers/usb/dwc3/glue.h | 116 ++++++++++++++++++++++++++++++++++++++++++++++
>> drivers/usb/dwc3/host.c | 2 +
>> 4 files changed, 131 insertions(+), 5 deletions(-)
>>
>> diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
>> index ae140c356295c03df3982ff4fa95f8638296e52d..526c0453b99aad79d99a842797e52d9290456d76 100644
>> --- a/drivers/usb/dwc3/core.c
>> +++ b/drivers/usb/dwc3/core.c
>> @@ -132,6 +132,7 @@ void dwc3_enable_susphy(struct dwc3 *dwc, bool enable)
>> dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(i), reg);
>> }
>> }
>> +EXPORT_SYMBOL_GPL(dwc3_enable_susphy);
>>
>> void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy)
>> {
>> @@ -158,6 +159,7 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy)
>> dwc->current_dr_role = mode;
>> trace_dwc3_set_prtcap(mode);
>> }
>> +EXPORT_SYMBOL_GPL(dwc3_set_prtcap);
>>
>> static void __dwc3_set_mode(struct work_struct *work)
>> {
>> @@ -975,7 +977,7 @@ static void dwc3_clk_disable(struct dwc3 *dwc)
>> clk_disable_unprepare(dwc->bus_clk);
>> }
>>
>> -static void dwc3_core_exit(struct dwc3 *dwc)
>> +void dwc3_core_exit(struct dwc3 *dwc)
>> {
>> dwc3_event_buffers_cleanup(dwc);
>> dwc3_phy_power_off(dwc);
>> @@ -983,6 +985,7 @@ static void dwc3_core_exit(struct dwc3 *dwc)
>> dwc3_clk_disable(dwc);
>> reset_control_assert(dwc->reset);
>> }
>> +EXPORT_SYMBOL_GPL(dwc3_core_exit);
>>
>> static bool dwc3_core_is_valid(struct dwc3 *dwc)
>> {
>> @@ -1328,7 +1331,7 @@ static void dwc3_config_threshold(struct dwc3 *dwc)
>> *
>> * Returns 0 on success otherwise negative errno.
>> */
>> -static int dwc3_core_init(struct dwc3 *dwc)
>> +int dwc3_core_init(struct dwc3 *dwc)
>> {
>> unsigned int hw_mode;
>> u32 reg;
>> @@ -1528,6 +1531,7 @@ static int dwc3_core_init(struct dwc3 *dwc)
>>
>> return ret;
>> }
>> +EXPORT_SYMBOL_GPL(dwc3_core_init);
>>
>> static int dwc3_core_get_phy(struct dwc3 *dwc)
>> {
>> @@ -2299,9 +2303,11 @@ int dwc3_core_probe(const struct dwc3_probe_data *data)
>> dwc3_check_params(dwc);
>> dwc3_debugfs_init(dwc);
>>
>> - ret = dwc3_core_init_mode(dwc);
>> - if (ret)
>> - goto err_exit_debugfs;
>> + if (!data->skip_core_init_mode) {
>> + ret = dwc3_core_init_mode(dwc);
>> + if (ret)
>> + goto err_exit_debugfs;
>> + }
>>
>> pm_runtime_put(dev);
>>
>> diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
>> index 6f18b4840a25d176abb4134581ad0ce68ba19ffc..1f67fb6aead5725c2e7b553c635eab985c9e1d48 100644
>> --- a/drivers/usb/dwc3/gadget.c
>> +++ b/drivers/usb/dwc3/gadget.c
>> @@ -4810,6 +4810,7 @@ int dwc3_gadget_init(struct dwc3 *dwc)
>> err0:
>> return ret;
>> }
>> +EXPORT_SYMBOL_GPL(dwc3_gadget_init);
>>
>> /* -------------------------------------------------------------------------- */
>>
>> @@ -4828,6 +4829,7 @@ void dwc3_gadget_exit(struct dwc3 *dwc)
>> dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2,
>> dwc->ep0_trb, dwc->ep0_trb_addr);
>> }
>> +EXPORT_SYMBOL_GPL(dwc3_gadget_exit);
>>
>> int dwc3_gadget_suspend(struct dwc3 *dwc)
>> {
>> diff --git a/drivers/usb/dwc3/glue.h b/drivers/usb/dwc3/glue.h
>> index 7f326cff12458901904d4c32f62ed9357d0f6e3b..376e7aa74281c13bfb3a85dc033622bcff8d2acb 100644
>> --- a/drivers/usb/dwc3/glue.h
>> +++ b/drivers/usb/dwc3/glue.h
>> @@ -15,11 +15,14 @@
>> * @res: resource for the DWC3 core mmio region
>> * @ignore_clocks_and_resets: clocks and resets defined for the device should
>> * be ignored by the DWC3 core, as they are managed by the glue
>> + * @skip_core_init_mode: Skip the finial initialization of the target mode, as
>> + * it must be managed by the glue
>> */
>> struct dwc3_probe_data {
>> struct dwc3 *dwc;
>> struct resource *res;
>> bool ignore_clocks_and_resets;
>> + bool skip_core_init_mode;
>> };
>>
>> /**
>> @@ -60,4 +63,117 @@ int dwc3_pm_resume(struct dwc3 *dwc);
>> void dwc3_pm_complete(struct dwc3 *dwc);
>> int dwc3_pm_prepare(struct dwc3 *dwc);
>>
>> +
>> +/* All of the following functions must only be used with skip_core_init_mode */
>> +
>> +/**
>> + * dwc3_core_init - Initialize DWC3 core hardware
>> + * @dwc: Pointer to DWC3 controller context
>> + *
>> + * Configures and initializes the core hardware, usually done by dwc3_core_probe.
>> + * This function is provided for platforms that use skip_core_init_mode and need
>> + * to finalize the core initialization after some platform-specific setup.
>> + * It must only be called when using skip_core_init_mode and before
>> + * dwc3_host_init or dwc3_gadget_init.
>> + *
>> + * Return: 0 on success, negative error code on failure
>> + */
>> +int dwc3_core_init(struct dwc3 *dwc);
>> +
>> +/**
>> + * dwc3_core_exit - Shut down DWC3 core hardware
>> + * @dwc: Pointer to DWC3 controller context
>> + *
>> + * Disables and cleans up the core hardware state. This is usually handled
>> + * internally by dwc3 and must only be called when using skip_core_init_mode
>> + * and only after dwc3_core_init. Afterwards, dwc3_core_init may be called
>> + * again.
>> + */
>> +void dwc3_core_exit(struct dwc3 *dwc);
>> +
>> +/**
>> + * dwc3_host_init - Initialize host mode operation
>> + * @dwc: Pointer to DWC3 controller context
>> + *
>> + * Initializes the controller for USB host mode operation, usually done by
>> + * dwc3_core_probe or from within the dwc3 USB role switch callback.
>> + * This function is provided for platforms that use skip_core_init_mode and need
>> + * to finalize the host initialization after some platform-specific setup.
>> + * It must not be called before dwc3_core_init or when skip_core_init_mode is
>> + * not used. It must also not be called when gadget or host mode has already
>> + * been initialized.
>> + *
>> + * Return: 0 on success, negative error code on failure
>> + */
>> +int dwc3_host_init(struct dwc3 *dwc);
>> +
>> +/**
>> + * dwc3_host_exit - Shut down host mode operation
>> + * @dwc: Pointer to DWC3 controller context
>> + *
>> + * Disables and cleans up host mode resources, usually done by
>> + * the dwc3 USB role switch callback before switching controller mode.
>> + * It must only be called when skip_core_init_mode is used and only after
>> + * dwc3_host_init.
>> + */
>> +void dwc3_host_exit(struct dwc3 *dwc);
>> +
>> +/**
>> + * dwc3_gadget_init - Initialize gadget mode operation
>> + * @dwc: Pointer to DWC3 controller context
>> + *
>> + * Initializes the controller for USB gadget mode operation, usually done by
>> + * dwc3_core_probe or from within the dwc3 USB role switch callback. This
>> + * function is provided for platforms that use skip_core_init_mode and need to
>> + * finalize the gadget initialization after some platform-specific setup.
>> + * It must not be called before dwc3_core_init or when skip_core_init_mode is
>> + * not used. It must also not be called when gadget or host mode has already
>> + * been initialized.
>> + *
>> + * Return: 0 on success, negative error code on failure
>> + */
>> +int dwc3_gadget_init(struct dwc3 *dwc);
>> +
>> +/**
>> + * dwc3_gadget_exit - Shut down gadget mode operation
>> + * @dwc: Pointer to DWC3 controller context
>> + *
>> + * Disables and cleans up gadget mode resources, usually done by
>> + * the dwc3 USB role switch callback before switching controller mode.
>> + * It must only be called when skip_core_init_mode is used and only after
>> + * dwc3_gadget_init.
>> + */
>> +void dwc3_gadget_exit(struct dwc3 *dwc);
>> +
>> +/**
>> + * dwc3_enable_susphy - Control SUSPHY status for all USB ports
>> + * @dwc: Pointer to DWC3 controller context
>> + * @enable: True to enable SUSPHY, false to disable
>> + *
>> + * Enables or disables the USB3 PHY SUSPEND and USB2 PHY SUSPHY feature for
>> + * all available ports.
>> + * This is usually handled by the dwc3 core code and should only be used
>> + * when skip_core_init_mode is used and the glue layer needs to manage SUSPHY
>> + * settings itself, e.g., due to platform-specific requirements during mode
>> + * switches.
>> + */
>> +void dwc3_enable_susphy(struct dwc3 *dwc, bool enable);
>> +
>> +/**
>> + * dwc3_set_prtcap - Set the USB controller PRTCAP mode
>> + * @dwc: Pointer to DWC3 controller context
>> + * @mode: Target mode, must be one of DWC3_GCTL_PRTCAP_{HOST,DEVICE,OTG}
>> + * @ignore_susphy: If true, skip disabling the SUSPHY and keep the current state
>> + *
>> + * Updates PRTCAP of the controller and current_dr_role inside the dwc3
>> + * structure. For DRD controllers, this also disables SUSPHY unless explicitly
>> + * told to skip via the ignore_susphy parameter.
>> + *
>> + * This is usually handled by the dwc3 core code and should only be used
>> + * when skip_core_init_mode is used and the glue layer needs to manage mode
>> + * transitions itself due to platform-specific requirements. It must be called
>> + * with the correct mode before calling dwc3_host_init or dwc3_gadget_init.
>> + */
>> +void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy);
>> +
>> #endif
>> diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
>> index e77fd86d09cf0a36161c20ad3c83f10e67099775..cf6512ed17a69134e6ca1b884f76c1439693fab1 100644
>> --- a/drivers/usb/dwc3/host.c
>> +++ b/drivers/usb/dwc3/host.c
>> @@ -220,6 +220,7 @@ int dwc3_host_init(struct dwc3 *dwc)
>> platform_device_put(xhci);
>> return ret;
>> }
>> +EXPORT_SYMBOL_GPL(dwc3_host_init);
>>
>> void dwc3_host_exit(struct dwc3 *dwc)
>> {
>> @@ -230,3 +231,4 @@ void dwc3_host_exit(struct dwc3 *dwc)
>> platform_device_unregister(dwc->xhci);
>> dwc->xhci = NULL;
>> }
>> +EXPORT_SYMBOL_GPL(dwc3_host_exit);
>>
>> --
>> 2.34.1
>>
>>
>
> Thanks for the documentations!
>
> Please rebase against Greg's usb-testing branch. I think there is
> conflict.
Yup, there's a (small) conflict. I'll rebase the series.
And thanks again for the review! This looks much better than my original
approach now.
Best,
Sven
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2025-10-15 7:32 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-13 16:03 [PATCH 0/5] Apple Silicon USB3 support - dwc3 Sven Peter
2025-10-13 16:03 ` [PATCH 1/5] dt-bindings: usb: Add Apple dwc3 Sven Peter
2025-10-13 16:03 ` [PATCH 2/5] usb: dwc3: dwc3_power_off_all_roothub_ports: Use ioremap_np when required Sven Peter
2025-10-13 16:03 ` [PATCH 3/5] usb: dwc3: glue: Add documentation Sven Peter
2025-10-14 23:13 ` Thinh Nguyen
2025-10-13 16:03 ` [PATCH 4/5] usb: dwc3: glue: Allow more fine grained control over mode switches Sven Peter
2025-10-14 23:16 ` Thinh Nguyen
2025-10-15 7:32 ` Sven Peter
2025-10-13 16:03 ` [PATCH 5/5] usb: dwc3: Add Apple Silicon DWC3 glue layer driver Sven Peter
2025-10-14 23:22 ` Thinh Nguyen
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).