From 5550520f2edf4f157e5305a213e1c3ee4bff269a Mon Sep 17 00:00:00 2001 From: Sean Dolbec Date: Wed, 11 Nov 2026 17:31:12 -0500 Subject: [PATCH 1/2] phy: apple: atc: Serve USB2 for USB4/TBT on T8132 without CIO calibration The SN201202x port controllers of the Mac mini (J773g) enter USB4 by themselves with a USB4 dock or another Mac, and Thunderbolt 3 alt mode with a Thunderbolt 3 dock. atcphy_typec_target_mode() maps those states to the USB4 and TBT PHY modes, which atcphy_mux_check() refuses with -ENODEV on a T8132 PHY without the CIO calibration. The J773g device tree has none. The failed mux update makes cd321x_update_work() retry before it sets the USB role, so the port ends up with sn201202x 1-08: failed to configure Type-C connection: -19 sn201202x 1-08: cable revalidation failed after 3 attempts and its dwc3 controller never starts an xHCI. USB 2.0 on D+/D- does not depend on the USB4 or Thunderbolt link. As is already done for the DisplayPort pin assignments this PHY cannot carry, serve the partner's USB2 instead of refusing the mode. The CIO fallback has to come before the DisplayPort fallback picks its USB mode: that one compares its result with target_mode, and started from the USB4 mode it would put USB4 back. Ports with the CIO calibration are unchanged. A KUnit case covers both. Assisted-by: LLM checkpatch Signed-off-by: Sean Dolbec --- drivers/phy/apple/atc-test.c | 35 +++++++++++++++++++++++++++++++++++ drivers/phy/apple/atc.c | 16 +++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/drivers/phy/apple/atc-test.c b/drivers/phy/apple/atc-test.c index 09eec7f9d6185..7acde177f69bd 100644 --- a/drivers/phy/apple/atc-test.c +++ b/drivers/phy/apple/atc-test.c @@ -104,6 +104,40 @@ static void atcphy_test_target_mode_t8132(struct kunit *test) KUNIT_EXPECT_EQ(test, atcphy_typec_target_mode(atcphy, &state), APPLE_ATCPHY_MODE_OFF); } +/* Without the CIO calibration, a T8132 port serves a USB4 or TBT partner's USB2 */ +static void atcphy_test_target_mode_t8132_cio(struct kunit *test) +{ + struct typec_altmode tbt = { .svid = USB_TYPEC_TBT_SID }; + struct enter_usb_data eusb = { + .eudo = FIELD_PREP(EUDO_USB_MODE_MASK, EUDO_USB_MODE_USB4), + }; + struct typec_mux_state state = { .mode = TYPEC_MODE_USB4, .data = &eusb }; + struct apple_atcphy *atcphy = atcphy_test_alloc_dev(test, false); + + atcphy->hw = &atcphy_hw_t8132; + KUNIT_EXPECT_EQ(test, atcphy_typec_target_mode(atcphy, &state), APPLE_ATCPHY_MODE_USB2); + state.alt = &tbt; + state.mode = TYPEC_TBT_MODE; + state.data = NULL; + KUNIT_EXPECT_EQ(test, atcphy_typec_target_mode(atcphy, &state), APPLE_ATCPHY_MODE_USB2); + /* The same on a SuperSpeed port. */ + atcphy->t8132_ss = true; + atcphy->ss_tunables = true; + KUNIT_EXPECT_EQ(test, atcphy_typec_target_mode(atcphy, &state), APPLE_ATCPHY_MODE_USB2); + state.alt = NULL; + state.mode = TYPEC_MODE_USB4; + state.data = &eusb; + KUNIT_EXPECT_EQ(test, atcphy_typec_target_mode(atcphy, &state), APPLE_ATCPHY_MODE_USB2); + + /* With the calibration, the link itself is served. */ + atcphy->cio_tunables = true; + KUNIT_EXPECT_EQ(test, atcphy_typec_target_mode(atcphy, &state), APPLE_ATCPHY_MODE_USB4); + state.alt = &tbt; + state.mode = TYPEC_TBT_MODE; + state.data = NULL; + KUNIT_EXPECT_EQ(test, atcphy_typec_target_mode(atcphy, &state), APPLE_ATCPHY_MODE_TBT); +} + static void atcphy_test_noop_same_mode(struct kunit *test) { struct apple_atcphy *atcphy = atcphy_test_alloc(test, true); @@ -1391,6 +1425,7 @@ static struct kunit_case atcphy_mux_test_cases[] = { KUNIT_CASE(atcphy_test_target_mode), KUNIT_CASE(atcphy_test_target_mode_fixed_hub), KUNIT_CASE(atcphy_test_target_mode_t8132), + KUNIT_CASE(atcphy_test_target_mode_t8132_cio), KUNIT_CASE(atcphy_test_noop_same_mode), KUNIT_CASE(atcphy_test_noop_needs_work), KUNIT_CASE(atcphy_test_usb3_port_ready), diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 414ce695f37a2..af28f03b1a6c0 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -5729,10 +5729,24 @@ static enum atcphy_mode atcphy_typec_target_mode(struct apple_atcphy *atcphy, * its USB on the other wires: USB2 next to four DP lanes (pin * assignments C and E), USB3 next to two (D). Serve that USB rather * than refuse the mode, which would leave the port without USB at all. + * + * The same goes for USB4 and Thunderbolt on a port without the CIO + * calibration, such as those of the Mac mini (J773g). Its port + * controller enters USB4 or Thunderbolt 3 by itself with a USB4 dock + * or another Mac, and USB 2.0 on D+/D- does not depend on that link. + * Do this first, so that the DisplayPort fallback below starts from + * the mode actually served rather than putting USB4 back. */ if (atcphy->hw->gen == ATCPHY_GENERATION_T8132) { - enum atcphy_mode usb_mode = target_mode; + enum atcphy_mode usb_mode; + + if (atcphy_mode_is_cio(target_mode) && !atcphy->cio_tunables) { + dev_info(atcphy->dev, + "USB4/Thunderbolt is not available on this port: serving its USB2 only\n"); + target_mode = APPLE_ATCPHY_MODE_USB2; + } + usb_mode = target_mode; /* 4-lane DP stays DP only on a port wired for DP alt mode */ if (target_mode == APPLE_ATCPHY_MODE_DP && !atcphy_t8132_dp_on(atcphy)) usb_mode = APPLE_ATCPHY_MODE_USB2; From 0430675d5a043ee270e8500217432a6244f2d43b Mon Sep 17 00:00:00 2001 From: Sean Dolbec Date: Fri, 13 Nov 2026 14:01:00 -0500 Subject: [PATCH 2/2] tools: asahi, selftests: apple-dcp: stub dev_info in the ATC host tests The ATC USB3 host test and the apple-dcp phy, pm, quiesce and dwc3 host tests compile functions taken from drivers/phy/apple/atc.c. Define dev_info() as a no-op beside their existing logging shims so the generated host C keeps compiling when one of those functions prints an informational message. Assisted-by: LLM Signed-off-by: Sean Dolbec --- tools/asahi/atc-usb3-tests.py | 1 + tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/prefix.c | 1 + tools/testing/selftests/drivers/gpu/apple-dcp/phy.py | 3 +++ tools/testing/selftests/drivers/gpu/apple-dcp/pm.py | 1 + tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py | 1 + 5 files changed, 7 insertions(+) diff --git a/tools/asahi/atc-usb3-tests.py b/tools/asahi/atc-usb3-tests.py index 6d92c105c45be..30ae465f18e6c 100644 --- a/tools/asahi/atc-usb3-tests.py +++ b/tools/asahi/atc-usb3-tests.py @@ -62,6 +62,7 @@ def block(source, marker): static unsigned reports; #define dev_warn(...) ((void)reports++) #define dev_err(...) ((void)reports++) +#define dev_info(...) ((void)0) #define dev_dbg(...) ((void)0) #define lockdep_assert_held(...) ((void)0) #define guard(which) (void) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/prefix.c b/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/prefix.c index 8c0bfb426e8a0..81a66a396ba0a 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/prefix.c +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/prefix.c @@ -28,6 +28,7 @@ static void warning(const char *message) generic_warnings++; } #define dev_warn(d, message, ...) warning(message) +#define dev_info(...) ((void)0) #define dev_err(...) ((void)0) #define dev_dbg(...) ((void)0) #define lockdep_assert_held(p) assert(*(p)) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/phy.py b/tools/testing/selftests/drivers/gpu/apple-dcp/phy.py index 7998abe4b8ac9..d386409fd6367 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/phy.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/phy.py @@ -221,6 +221,7 @@ def function(source, name): #define dev_info_once(...) ((void)0) #define dev_dbg(...) ((void)0) #define dev_warn(...) ((void)0) +#define dev_info(...) ((void)0) static void report(void *dev, const char *fmt, ...) { (void)dev; (void)fmt; } #define dev_err report #define lockdep_assert_held(...) ((void)0) @@ -2042,6 +2043,7 @@ def function(name): #define field_prep FIELD_PREP #define dev_dbg(...) ((void)0) #define dev_warn(...) ((void)0) +#define dev_info(...) ((void)0) #define dev_err(...) ((void)0) #define lockdep_assert_held(...) ((void)0) #define WARN_ON_ONCE(v) assert(!(v)) @@ -2459,6 +2461,7 @@ def function(source, name): #define dev_info_once(...) ((void)0) #define dev_dbg(...) ((void)0) #define dev_warn(...) ((void)0) +#define dev_info(...) ((void)0) static void report(void *dev, const char *fmt, ...) { (void)dev; (void)fmt; } #define dev_err report #define lockdep_assert_held(...) ((void)0) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py b/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py index 4dcdc35ed409b..4431d873c5957 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/pm.py @@ -570,6 +570,7 @@ def function(text, name): ' struct notifier_block tbt_watch;\n bool tbt_watch_registered;\n', 1) mocks = mocks.replace('static void cd321x_stop_work(struct cd321x *c) {\n', r'''#define INIT_WORK(p,fn) ((p)->pending=false) #define dev_warn(...) ((void)0) +#define dev_info(...) ((void)0) static int watches, watch_error; /* Like the real watch, hold the port fwnode until the port is unregistered. */ static int cd321x_tbt_watch_late(struct cd321x *c,struct fwnode_handle *f) { diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py b/tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py index 9d5bb22eae4f9..c31c25d45a3ef 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/quiesce.py @@ -31,6 +31,7 @@ def make_unit(source, parent=False, reference=None): #define dev_dbg(...) ((void)0) #define dev_warn(...) ((void)0) #define dev_info(...) ((void)0) +#define dev_info(...) ((void)0) #define dev_name(p) "device" #define list_for_each_entry(p,h,m) for((void)(h),(p)=sole_port();(p);(p)=NULL) #define lockdep_assert_held(p) ((void)(p))