From 5f22d758f3945179499af690bd31e7462a6ea6b3 Mon Sep 17 00:00:00 2001 From: milijan Date: Mon, 25 May 2026 13:53:45 +0000 Subject: [PATCH 1/2] phy: apple: atc: retry pipehandler lock with backoff on timeout At boot the Apple co-processor firmware does not acknowledge the pipehandler lock within the fixed 1ms timeout (PIPEHANDLER_LOCK_ACK_TIMEOUT_US = 1000). The function clears the lock bit and returns an error, leaving the PHY unconfigured for the rest of the session and preventing USB3 and DP alt mode from working. Replace the single poll attempt with a retry loop: up to 10 attempts with a 5-10ms sleep between each, giving the firmware approximately 100ms total to respond. Add an early return if the lock is already held to avoid double-locking. Tested on Apple M1 MacBook Air (J313), Fedora Asahi Remix 44, kernel 6.19.14. Boot with multiport hub connected no longer produces "Pipehandler lock not acked" errors and USB3 enumerates correctly. Signed-off-by: milijan --- drivers/phy/apple/atc.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 64d0c3dba1cbb9..18f5718f67f703 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -922,7 +922,7 @@ static void atcphy_apply_tunables(struct apple_atcphy *atcphy, enum atcphy_mode static int atcphy_pipehandler_lock(struct apple_atcphy *atcphy) { - int ret; + int ret, retries = 10; u32 reg; if (readl(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ) & PIPEHANDLER_LOCK_EN) { @@ -930,15 +930,17 @@ static int atcphy_pipehandler_lock(struct apple_atcphy *atcphy) return 0; } - set32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ, PIPEHANDLER_LOCK_EN); - - ret = readl_poll_timeout(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_ACK, reg, - reg & PIPEHANDLER_LOCK_EN, 10, PIPEHANDLER_LOCK_ACK_TIMEOUT_US); - if (ret) { - clear32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ, 1); - dev_warn(atcphy->dev, "Pipehandler lock not acked.\n"); - } + do { + set32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ, PIPEHANDLER_LOCK_EN); + ret = readl_poll_timeout(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_ACK, reg, + reg & PIPEHANDLER_LOCK_EN, 10, PIPEHANDLER_LOCK_ACK_TIMEOUT_US); + if (!ret) + return 0; + clear32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ, PIPEHANDLER_LOCK_EN); + usleep_range(5000, 10000); + } while (--retries); + dev_warn(atcphy->dev, "Pipehandler lock not acked.\n"); return ret; } From cfca9fe3b4d7685d4aecf3099e8ff1d754dd3acd Mon Sep 17 00:00:00 2001 From: milijan Date: Mon, 25 May 2026 15:33:53 +0000 Subject: [PATCH 2/2] phy: apple: atc: fix pipehandler teardown race and false-positive WARN on USB3_DP When DP alt mode is negotiated on hotplug, atcphy_mux_set() can be called while atcphy->pipehandler_up is still true. The existing WARN_ON fired unconditionally, but the situation has two distinct sub-cases that require different handling: 1. Target mode uses ATCPHY_PIPEHANDLER_STATE_DUMMY (DP-only, USB2, TBT, OFF): pipehandler_up being true is a teardown race between the TypeC mux call chain (cd321x_update_work -> typec_mux_set -> atcphy_mux_set) and the DWC3 PHY teardown (phy_power_off -> atcphy_usb3_power_off). By the time the kernel TypeC stack reaches atcphy_mux_set the USB PD Enter_Mode handshake is complete and the SS lanes are already being repurposed, so switching the pipehandler to dummy is safe. Tear it down explicitly here rather than relying on the race resolving itself. 2. Target mode uses ATCPHY_PIPEHANDLER_STATE_USB3 (USB3_DP, pin assignment D): pipehandler_up being true is correct and expected. The Apple USB-C Digital AV Multiport Adapter (05ac:1463) always negotiates pin assignment D, so the previous WARN_ON fired on every hotplug despite nothing being wrong with the pipehandler state. Retain the WARN_ON but scope it to case 1 only: warn when the target mode requires dummy state but the pipehandler is still up after the explicit teardown attempt (indicating atcphy_configure_pipehandler_dummy failed). Do not warn for USB3_DP transitions where USB3 pipehandler state is the correct precondition. Tested on Apple M1 MacBook Air (J313), Fedora Asahi Remix 44, kernel 6.19.14, with Apple USB-C Digital AV Multiport Adapter (05ac:1463). Hotplug no longer produces a spurious WARN_ON. Signed-off-by: milijan --- drivers/phy/apple/atc.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 18f5718f67f703..1178801fe5dcb1 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -2083,6 +2083,7 @@ static int atcphy_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *sta { struct apple_atcphy *atcphy = typec_mux_get_drvdata(mux); enum atcphy_mode target_mode; + int ret; guard(mutex)(&atcphy->lock); @@ -2140,11 +2141,31 @@ static int atcphy_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *sta return 0; /* - * If the pipehandler is still/already up here there's a bug somewhere so make sure to - * complain loudly. We can still try to switch modes and hope for the best though, - * in the worst case the hardware will fall back to USB2-only. + * If the pipehandler is still up and the target mode uses the dummy PIPE + * state (DP, USB2, TBT, OFF), tear it down before reconfiguring the lanes. + * Normally atcphy_usb3_power_off() does this, but a TypeC mux switch to + * DP alt mode can race the DWC3 PHY teardown on hotplug. By the time the + * kernel TypeC stack reaches here the USB PD Enter_Mode handshake is + * complete and the SS lanes are already being repurposed, so switching the + * pipehandler to dummy is safe even if DWC3 has not yet called phy_power_off. + */ + if (atcphy->pipehandler_up && + atcphy_modes[target_mode].pipehandler_state == ATCPHY_PIPEHANDLER_STATE_DUMMY) { + ret = atcphy_configure_pipehandler_dummy(atcphy); + if (ret) + dev_warn(atcphy->dev, + "Failed to clear pipehandler before mode switch: %d\n", ret); + else + atcphy->pipehandler_up = false; + } + + /* + * For modes that keep the pipehandler in USB3 state (e.g. USB3_DP), + * pipehandler_up being true here is expected and correct. Only warn + * if we tried to tear down the pipehandler above but failed. */ - WARN_ON_ONCE(atcphy->pipehandler_up); + WARN_ON_ONCE(atcphy->pipehandler_up && + atcphy_modes[target_mode].pipehandler_state == ATCPHY_PIPEHANDLER_STATE_DUMMY); return atcphy_configure(atcphy, target_mode); }