[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20251014232250.nryf3zv4rx2coipu@synopsys.com>
Date: Tue, 14 Oct 2025 23:22:51 +0000
From: Thinh Nguyen <Thinh.Nguyen@...opsys.com>
To: Sven Peter <sven@...nel.org>
CC: Janne Grunau <j@...nau.net>, Neal Gompa <neal@...pa.dev>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
Rob Herring <robh@...nel.org>,
Krzysztof Kozlowski <krzk+dt@...nel.org>,
Conor Dooley <conor+dt@...nel.org>,
Thinh Nguyen <Thinh.Nguyen@...opsys.com>,
Philipp Zabel <p.zabel@...gutronix.de>,
"asahi@...ts.linux.dev" <asahi@...ts.linux.dev>,
"linux-arm-kernel@...ts.infradead.org" <linux-arm-kernel@...ts.infradead.org>,
"linux-usb@...r.kernel.org" <linux-usb@...r.kernel.org>,
"devicetree@...r.kernel.org" <devicetree@...r.kernel.org>,
"linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>
Subject: Re: [PATCH 5/5] usb: dwc3: Add Apple Silicon DWC3 glue layer driver
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@...pa.dev>
> Signed-off-by: Sven Peter <sven@...nel.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@...nel.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@...opsys.com>
Thanks,
Thinh
Powered by blists - more mailing lists