| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Apple Silicon DWC3 Glue driver
- * Copyright (C) The Asahi Linux Contributors
- *
- * Based on:
- * - dwc3-qcom.c Copyright (c) 2018, The Linux Foundation. All rights reserved.
- * - dwc3-of-simple.c Copyright (c) 2015 Texas Instruments Incorporated - https://www.ti.com
- */
- #include <linux/of.h>
- #include <linux/module.h>
- #include <linux/mutex.h>
- #include <linux/platform_device.h>
- #include <linux/reset.h>
- #include "glue.h"
- /*
- * This platform requires a very specific sequence of operations to bring up dwc3 and its USB3 PHY:
- *
- * 1) The PHY itself has to be brought up; for this we need to know the mode (USB3,
- * USB3+DisplayPort, USB4, etc) and the lane orientation. This happens through typec_mux_set.
- * 2) DWC3 has to be brought up but we must not touch the gadget area or start xhci yet.
- * 3) The PHY bring-up has to be finalized and dwc3's PIPE interface has to be switched to the
- * USB3 PHY, this is done inside phy_set_mode.
- * 4) We can now initialize xhci or gadget mode.
- *
- * We can switch 1 and 2 but 3 has to happen after (1 and 2) and 4 has to happen after 3.
- *
- * And then to bring this all down again:
- *
- * 1) DWC3 has to exit host or gadget mode and must no longer touch those registers
- * 2) The PHY has to switch dwc3's PIPE interface back to the dummy backend
- * 3) The PHY itself can be shut down, this happens from typec_mux_set
- *
- * We also can't transition the PHY from one mode to another while dwc3 is up and running (this is
- * slightly wrong, some transitions are possible, others aren't but because we have no documentation
- * for this I'd rather play it safe).
- *
- * After both the PHY and dwc3 are initialized we will only ever see a single "new device connected"
- * event. If we just keep them running only the first device plugged in will ever work. XHCI's port
- * status register actually does show the correct state but no interrupt ever comes in. In gadget
- * mode we don't even get a USBDisconnected event and everything looks like there's still something
- * connected on the other end.
- * This can be partially explained because the USB2 D+/D- lines are connected through a stateful
- * eUSB2 repeater which in turn is controlled by a variant of the TI TPS6598x USB PD chip which
- * resets the repeater out-of-band everytime the CC lines are (dis)connected. This then requires a
- * PHY reset to make sure the PHY and the eUSB2 repeater state are synchronized again.
- *
- * And to make this all extra fun: If we get the order of some of this wrong either the port is just
- * broken until a phy+dwc3 reset, or it's broken until a full SoC reset (likely because we can't
- * reset some parts of the PHY), or some watchdog kicks in after a few seconds and forces a full SoC
- * reset (mostly seen this with USB4/Thunderbolt but there's clearly some watchdog that hates
- * invalid states).
- *
- * Hence there's really no good way to keep dwc3 fully up and running after we disconnect a cable
- * because then we can't shut down the PHY anymore. And if we kept the PHY running in whatever mode
- * it was until the next cable is connected we'd need to tear it all down and bring it back up again
- * anyway to detect and use the next device.
- *
- * Instead, we just shut down everything when a cable is disconnected and transition to
- * DWC3_APPLE_NO_CABLE.
- * During initial probe we don't have any information about the connected cable and can't bring up
- * the PHY properly and thus also can't fully bring up dwc3. Instead, we just keep everything off
- * and defer the first dwc3 probe until we get the first cable connected event. Until then we stay
- * in DWC3_APPLE_PROBE_PENDING.
- * Once a cable is connected we then keep track of the controller mode here by transitioning to
- * DWC3_APPLE_HOST or DWC3_APPLE_DEVICE.
- */
- enum dwc3_apple_state {
- DWC3_APPLE_PROBE_PENDING, /* Before first cable connection, dwc3_core_probe not called */
- DWC3_APPLE_NO_CABLE, /* No cable connected, dwc3 suspended after dwc3_core_exit */
- DWC3_APPLE_HOST, /* Cable connected, dwc3 in host mode */
- DWC3_APPLE_DEVICE, /* Cable connected, dwc3 in device mode */
- };
- /**
- * struct dwc3_apple - Apple-specific DWC3 USB controller
- * @dwc: Core DWC3 structure
- * @dev: Pointer to the device structure
- * @mmio_resource: Resource to be passed to dwc3_core_probe
- * @apple_regs: Apple-specific DWC3 registers
- * @reset: Reset control
- * @role_sw: USB role switch
- * @lock: Mutex for synchronizing access
- * @state: Current state of the controller, see documentation for the enum for details
- */
- struct dwc3_apple {
- struct dwc3 dwc;
- struct device *dev;
- struct resource *mmio_resource;
- void __iomem *apple_regs;
- struct reset_control *reset;
- struct usb_role_switch *role_sw;
- struct mutex lock;
- enum dwc3_apple_state state;
- };
- #define to_dwc3_apple(d) container_of((d), struct dwc3_apple, dwc)
- /*
- * Apple Silicon dwc3 vendor-specific registers
- *
- * These registers were identified by tracing XNU's memory access patterns and correlating them with
- * debug output over serial to determine their names. We don't exactly know what these do but
- * without these USB3 devices sometimes don't work.
- */
- #define APPLE_DWC3_REGS_START 0xcd00
- #define APPLE_DWC3_REGS_END 0xcdff
- #define APPLE_DWC3_CIO_LFPS_OFFSET 0xcd38
- #define APPLE_DWC3_CIO_LFPS_OFFSET_VALUE 0xf800f80
- #define APPLE_DWC3_CIO_BW_NGT_OFFSET 0xcd3c
- #define APPLE_DWC3_CIO_BW_NGT_OFFSET_VALUE 0xfc00fc0
- #define APPLE_DWC3_CIO_LINK_TIMER 0xcd40
- #define APPLE_DWC3_CIO_PENDING_HP_TIMER GENMASK(23, 16)
- #define APPLE_DWC3_CIO_PENDING_HP_TIMER_VALUE 0x14
- #define APPLE_DWC3_CIO_PM_LC_TIMER GENMASK(15, 8)
- #define APPLE_DWC3_CIO_PM_LC_TIMER_VALUE 0xa
- #define APPLE_DWC3_CIO_PM_ENTRY_TIMER GENMASK(7, 0)
- #define APPLE_DWC3_CIO_PM_ENTRY_TIMER_VALUE 0x10
- static inline void dwc3_apple_writel(struct dwc3_apple *appledwc, u32 offset, u32 value)
- {
- writel(value, appledwc->apple_regs + offset - APPLE_DWC3_REGS_START);
- }
- static inline u32 dwc3_apple_readl(struct dwc3_apple *appledwc, u32 offset)
- {
- return readl(appledwc->apple_regs + offset - APPLE_DWC3_REGS_START);
- }
- static inline void dwc3_apple_mask(struct dwc3_apple *appledwc, u32 offset, u32 mask, u32 value)
- {
- u32 reg;
- reg = dwc3_apple_readl(appledwc, offset);
- reg &= ~mask;
- reg |= value;
- dwc3_apple_writel(appledwc, offset, reg);
- }
- static void dwc3_apple_setup_cio(struct dwc3_apple *appledwc)
- {
- dwc3_apple_writel(appledwc, APPLE_DWC3_CIO_LFPS_OFFSET, APPLE_DWC3_CIO_LFPS_OFFSET_VALUE);
- dwc3_apple_writel(appledwc, APPLE_DWC3_CIO_BW_NGT_OFFSET,
- APPLE_DWC3_CIO_BW_NGT_OFFSET_VALUE);
- dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PENDING_HP_TIMER,
- FIELD_PREP(APPLE_DWC3_CIO_PENDING_HP_TIMER,
- APPLE_DWC3_CIO_PENDING_HP_TIMER_VALUE));
- dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PM_LC_TIMER,
- FIELD_PREP(APPLE_DWC3_CIO_PM_LC_TIMER, APPLE_DWC3_CIO_PM_LC_TIMER_VALUE));
- dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PM_ENTRY_TIMER,
- FIELD_PREP(APPLE_DWC3_CIO_PM_ENTRY_TIMER,
- APPLE_DWC3_CIO_PM_ENTRY_TIMER_VALUE));
- }
- static void dwc3_apple_set_ptrcap(struct dwc3_apple *appledwc, u32 mode)
- {
- guard(spinlock_irqsave)(&appledwc->dwc.lock);
- dwc3_set_prtcap(&appledwc->dwc, mode, false);
- }
- static int dwc3_apple_core_probe(struct dwc3_apple *appledwc)
- {
- struct dwc3_probe_data probe_data = {};
- int ret;
- lockdep_assert_held(&appledwc->lock);
- WARN_ON_ONCE(appledwc->state != DWC3_APPLE_PROBE_PENDING);
- appledwc->dwc.dev = appledwc->dev;
- probe_data.dwc = &appledwc->dwc;
- probe_data.res = appledwc->mmio_resource;
- probe_data.ignore_clocks_and_resets = true;
- probe_data.skip_core_init_mode = true;
- probe_data.properties = DWC3_DEFAULT_PROPERTIES;
- ret = dwc3_core_probe(&probe_data);
- if (ret)
- return ret;
- appledwc->state = DWC3_APPLE_NO_CABLE;
- return 0;
- }
- static int dwc3_apple_core_init(struct dwc3_apple *appledwc)
- {
- int ret;
- lockdep_assert_held(&appledwc->lock);
- switch (appledwc->state) {
- case DWC3_APPLE_PROBE_PENDING:
- ret = dwc3_apple_core_probe(appledwc);
- if (ret)
- dev_err(appledwc->dev, "Failed to probe DWC3 Core, err=%d\n", ret);
- break;
- case DWC3_APPLE_NO_CABLE:
- ret = dwc3_core_init(&appledwc->dwc);
- if (ret)
- dev_err(appledwc->dev, "Failed to initialize DWC3 Core, err=%d\n", ret);
- break;
- default:
- /* Unreachable unless there's a bug in this driver */
- WARN_ON_ONCE(1);
- ret = -EINVAL;
- break;
- }
- return ret;
- }
- static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state state)
- {
- int ret, ret_reset;
- lockdep_assert_held(&appledwc->lock);
- /*
- * The USB2 PHY on this platform must be configured for host or device mode while it is
- * still powered off and before dwc3 tries to access it. Otherwise, the new configuration
- * will sometimes only take affect after the *next* time dwc3 is brought up which causes
- * the connected device to just not work.
- * The USB3 PHY must be configured later after dwc3 has already been initialized.
- */
- switch (state) {
- case DWC3_APPLE_HOST:
- phy_set_mode(appledwc->dwc.usb2_generic_phy[0], PHY_MODE_USB_HOST);
- break;
- case DWC3_APPLE_DEVICE:
- phy_set_mode(appledwc->dwc.usb2_generic_phy[0], PHY_MODE_USB_DEVICE);
- break;
- default:
- /* Unreachable unless there's a bug in this driver */
- return -EINVAL;
- }
- ret = reset_control_deassert(appledwc->reset);
- if (ret) {
- dev_err(appledwc->dev, "Failed to deassert reset, err=%d\n", ret);
- return ret;
- }
- ret = dwc3_apple_core_init(appledwc);
- if (ret)
- goto reset_assert;
- /*
- * Now that the core is initialized and already went through dwc3_core_soft_reset we can
- * configure some unknown Apple-specific settings and then bring up xhci or gadget mode.
- */
- dwc3_apple_setup_cio(appledwc);
- switch (state) {
- case DWC3_APPLE_HOST:
- appledwc->dwc.dr_mode = USB_DR_MODE_HOST;
- dwc3_apple_set_ptrcap(appledwc, DWC3_GCTL_PRTCAP_HOST);
- /*
- * 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. The USB2 PHY
- * has already been configured to the correct mode earlier.
- */
- dwc3_enable_susphy(&appledwc->dwc, true);
- phy_set_mode(appledwc->dwc.usb3_generic_phy[0], 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);
- /*
- * 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. The USB2 PHY
- * has already been configured to the correct mode earlier.
- */
- dwc3_enable_susphy(&appledwc->dwc, true);
- phy_set_mode(appledwc->dwc.usb3_generic_phy[0], PHY_MODE_USB_DEVICE);
- ret = dwc3_gadget_init(&appledwc->dwc);
- if (ret) {
- dev_err(appledwc->dev, "Failed to initialize gadget, ret=%d\n", ret);
- goto core_exit;
- }
- break;
- default:
- /* Unreachable unless there's a bug in this driver */
- WARN_ON_ONCE(1);
- ret = -EINVAL;
- goto core_exit;
- }
- appledwc->state = state;
- return 0;
- core_exit:
- dwc3_core_exit(&appledwc->dwc);
- reset_assert:
- ret_reset = reset_control_assert(appledwc->reset);
- if (ret_reset)
- dev_warn(appledwc->dev, "Failed to assert reset, err=%d\n", ret_reset);
- return ret;
- }
- static int dwc3_apple_exit(struct dwc3_apple *appledwc)
- {
- int ret = 0;
- lockdep_assert_held(&appledwc->lock);
- switch (appledwc->state) {
- case DWC3_APPLE_PROBE_PENDING:
- case DWC3_APPLE_NO_CABLE:
- /* Nothing to do if we're already off */
- return 0;
- case DWC3_APPLE_DEVICE:
- dwc3_gadget_exit(&appledwc->dwc);
- break;
- case DWC3_APPLE_HOST:
- dwc3_host_exit(&appledwc->dwc);
- break;
- }
- /*
- * This platform requires SUSPHY to be enabled in order to properly power down the PHY
- * and switch dwc3's PIPE interface back to a dummy PHY (i.e. no USB3 support and USB2 via
- * a different PHY connected through ULPI).
- */
- dwc3_enable_susphy(&appledwc->dwc, true);
- dwc3_core_exit(&appledwc->dwc);
- appledwc->state = DWC3_APPLE_NO_CABLE;
- ret = reset_control_assert(appledwc->reset);
- if (ret) {
- dev_err(appledwc->dev, "Failed to assert reset, err=%d\n", ret);
- return ret;
- }
- return 0;
- }
- static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role)
- {
- struct dwc3_apple *appledwc = usb_role_switch_get_drvdata(sw);
- int ret;
- guard(mutex)(&appledwc->lock);
- /*
- * Skip role switches if appledwc is already in the desired state. The
- * USB-C port controller on M2 and M1/M2 Pro/Max/Ultra devices issues
- * additional interrupts which results in usb_role_switch_set_role()
- * calls with the current role.
- * Ignore those calls here to ensure the USB-C port controller and
- * appledwc are in a consistent state.
- * This matches the behaviour in __dwc3_set_mode().
- * Do no handle USB_ROLE_NONE for DWC3_APPLE_NO_CABLE and
- * DWC3_APPLE_PROBE_PENDING since that is no-op anyway.
- */
- if (appledwc->state == DWC3_APPLE_HOST && role == USB_ROLE_HOST)
- return 0;
- if (appledwc->state == DWC3_APPLE_DEVICE && role == USB_ROLE_DEVICE)
- return 0;
- /*
- * We need to tear all of dwc3 down and re-initialize it every time a cable is
- * connected or disconnected or when the mode changes. See the documentation for enum
- * dwc3_apple_state for details.
- */
- ret = dwc3_apple_exit(appledwc);
- if (ret)
- return ret;
- switch (role) {
- case USB_ROLE_NONE:
- /* Nothing to do if no cable is connected */
- return 0;
- case USB_ROLE_HOST:
- return dwc3_apple_init(appledwc, DWC3_APPLE_HOST);
- case USB_ROLE_DEVICE:
- return dwc3_apple_init(appledwc, DWC3_APPLE_DEVICE);
- default:
- dev_err(appledwc->dev, "Invalid target role: %d\n", role);
- return -EINVAL;
- }
- }
- static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
- {
- struct dwc3_apple *appledwc = usb_role_switch_get_drvdata(sw);
- guard(mutex)(&appledwc->lock);
- switch (appledwc->state) {
- case DWC3_APPLE_HOST:
- return USB_ROLE_HOST;
- case DWC3_APPLE_DEVICE:
- return USB_ROLE_DEVICE;
- case DWC3_APPLE_NO_CABLE:
- case DWC3_APPLE_PROBE_PENDING:
- return USB_ROLE_NONE;
- default:
- /* Unreachable unless there's a bug in this driver */
- dev_err(appledwc->dev, "Invalid internal state: %d\n", appledwc->state);
- return USB_ROLE_NONE;
- }
- }
- static int dwc3_apple_setup_role_switch(struct dwc3_apple *appledwc)
- {
- struct usb_role_switch_desc dwc3_role_switch = { NULL };
- dwc3_role_switch.fwnode = dev_fwnode(appledwc->dev);
- dwc3_role_switch.set = dwc3_usb_role_switch_set;
- dwc3_role_switch.get = dwc3_usb_role_switch_get;
- dwc3_role_switch.driver_data = appledwc;
- appledwc->role_sw = usb_role_switch_register(appledwc->dev, &dwc3_role_switch);
- if (IS_ERR(appledwc->role_sw))
- return PTR_ERR(appledwc->role_sw);
- return 0;
- }
- static int dwc3_apple_probe(struct platform_device *pdev)
- {
- struct device *dev = &pdev->dev;
- struct dwc3_apple *appledwc;
- int ret;
- appledwc = devm_kzalloc(&pdev->dev, sizeof(*appledwc), GFP_KERNEL);
- if (!appledwc)
- return -ENOMEM;
- appledwc->dev = &pdev->dev;
- mutex_init(&appledwc->lock);
- appledwc->reset = devm_reset_control_get_exclusive(dev, NULL);
- if (IS_ERR(appledwc->reset))
- return dev_err_probe(&pdev->dev, PTR_ERR(appledwc->reset),
- "Failed to get reset control\n");
- ret = reset_control_assert(appledwc->reset);
- if (ret) {
- dev_err(&pdev->dev, "Failed to assert reset, err=%d\n", ret);
- return ret;
- }
- appledwc->mmio_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dwc3-core");
- if (!appledwc->mmio_resource) {
- dev_err(dev, "Failed to get DWC3 MMIO\n");
- return -EINVAL;
- }
- appledwc->apple_regs = devm_platform_ioremap_resource_byname(pdev, "dwc3-apple");
- if (IS_ERR(appledwc->apple_regs))
- return dev_err_probe(dev, PTR_ERR(appledwc->apple_regs),
- "Failed to map Apple-specific MMIO\n");
- /*
- * On this platform, DWC3 can only be brought up after parts of the PHY have been
- * initialized with knowledge of the target mode and cable orientation from typec_set_mux.
- * Since this has not happened here we cannot setup DWC3 yet and instead defer this until
- * the first cable is connected. See the documentation for enum dwc3_apple_state for
- * details.
- */
- appledwc->state = DWC3_APPLE_PROBE_PENDING;
- ret = dwc3_apple_setup_role_switch(appledwc);
- if (ret)
- return dev_err_probe(&pdev->dev, ret, "Failed to setup role switch\n");
- return 0;
- }
- static void dwc3_apple_remove(struct platform_device *pdev)
- {
- struct dwc3 *dwc = platform_get_drvdata(pdev);
- struct dwc3_apple *appledwc = to_dwc3_apple(dwc);
- guard(mutex)(&appledwc->lock);
- usb_role_switch_unregister(appledwc->role_sw);
- /*
- * If we're still in DWC3_APPLE_PROBE_PENDING we never got any cable connected event and
- * dwc3_core_probe was never called and there's hence no need to call dwc3_core_remove.
- * dwc3_apple_exit can be called unconditionally because it checks the state itself.
- */
- dwc3_apple_exit(appledwc);
- if (appledwc->state != DWC3_APPLE_PROBE_PENDING)
- dwc3_core_remove(&appledwc->dwc);
- }
- static const struct of_device_id dwc3_apple_of_match[] = {
- { .compatible = "apple,t8103-dwc3" },
- {}
- };
- MODULE_DEVICE_TABLE(of, dwc3_apple_of_match);
- static struct platform_driver dwc3_apple_driver = {
- .probe = dwc3_apple_probe,
- .remove = dwc3_apple_remove,
- .driver = {
- .name = "dwc3-apple",
- .of_match_table = dwc3_apple_of_match,
- },
- };
- module_platform_driver(dwc3_apple_driver);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Sven Peter <sven@kernel.org>");
- MODULE_DESCRIPTION("DesignWare DWC3 Apple Silicon Glue Driver");
|