[PATCH v3 4/6] arch: arm: mach-snapdragon: Auto-detect USB SSPHY driver support

Balaji Selvanathan balaji.selvanathan at oss.qualcomm.com
Wed Dec 3 12:07:33 CET 2025


Automatically detect super-speed USB PHY driver availability and
skip the USB speed fixup if driver is available, eliminating the need
for manual configuration.

Previously, U-Boot unconditionally limited USB to high-speed mode
on all Qualcomm platforms because most lacked super-speed PHY
drivers.

This change implements runtime detection that checks if a PHY
driver exists for the super-speed PHY node referenced by the DWC3
controller. The fixup is automatically skipped when a compatible
driver is found, allowing the hardware to operate at full
capability. Platforms without super-speed PHY drivers continue to
receive the fixup automatically.
---
v3:
- Removed support to manually disable the fixup via Kconfig.
- Instead added code to automatically detect if
  SSPHY driver is available.

Signed-off-by: Balaji Selvanathan <balaji.selvanathan at oss.qualcomm.com>
---
 arch/arm/mach-snapdragon/of_fixup.c | 130 ++++++++++++++++++++++++----
 1 file changed, 112 insertions(+), 18 deletions(-)

diff --git a/arch/arm/mach-snapdragon/of_fixup.c b/arch/arm/mach-snapdragon/of_fixup.c
index eec2c0c757e..85dc1c4573d 100644
--- a/arch/arm/mach-snapdragon/of_fixup.c
+++ b/arch/arm/mach-snapdragon/of_fixup.c
@@ -4,7 +4,7 @@
  *
  * This file implements runtime fixups for Qualcomm DT to improve
  * compatibility with U-Boot. This includes adjusting the USB nodes
- * to only use USB high-speed.
+ * to only use USB high-speed if SSPHY driver is not available.
  *
  * We use OF_LIVE for this rather than early FDT fixup for a couple
  * of reasons: it has a much nicer API, is most likely more efficient,
@@ -21,32 +21,108 @@
 #include <dt-bindings/input/linux-event-codes.h>
 #include <dm/of_access.h>
 #include <dm/of.h>
+#include <dm/device.h>
+#include <dm/lists.h>
 #include <event.h>
 #include <fdt_support.h>
 #include <linux/errno.h>
+#include <linker_lists.h>
 #include <stdlib.h>
 #include <time.h>
 
-/* U-Boot only supports USB high-speed mode on Qualcomm platforms with DWC3
- * USB controllers. Rather than requiring source level DT changes, we fix up
- * DT here. This improves compatibility with upstream DT and simplifies the
- * porting process for new devices.
+/**
+ * find_ssphy_node() - Find the super-speed PHY node referenced by DWC3
+ * @dwc3: DWC3 device node
+ *
+ * Returns: Pointer to SS-PHY node if found, NULL otherwise
+ */
+static struct device_node *find_ssphy_node(struct device_node *dwc3)
+{
+	const __be32 *phandles;
+	const char *phy_name;
+	int len, i, ret;
+
+	phandles = of_get_property(dwc3, "phys", &len);
+	if (!phandles)
+		return NULL;
+
+	len /= sizeof(*phandles);
+
+	/* Iterate through PHY phandles to find the SS-PHY */
+	for (i = 0; i < len; i++) {
+		ret = of_property_read_string_index(dwc3, "phy-names", i, &phy_name);
+		if (ret)
+			continue;
+
+		/* Check if this is the super-speed PHY */
+		if (!strncmp("usb3-phy", phy_name, strlen("usb3-phy")) ||
+		    !strncmp("usb3_phy", phy_name, strlen("usb3_phy"))) {
+			return of_find_node_by_phandle(NULL, be32_to_cpu(phandles[i]));
+		}
+	}
+
+	return NULL;
+}
+
+/**
+ * has_driver_for_node() - Check if any PHY driver can bind to this node
+ * @np: Device node to check
+ *
+ * Returns: true if a PHY driver with matching compatible string exists, false otherwise
  */
+static bool has_driver_for_node(struct device_node *np)
+{
+	struct driver *driver = ll_entry_start(struct driver, driver);
+	const int n_ents = ll_entry_count(struct driver, driver);
+	const char *compat_list, *compat;
+	int compat_length, i;
+	struct driver *entry;
+
+	if (!np)
+		return false;
+
+	/* Get compatible strings from the node */
+	compat_list = of_get_property(np, "compatible", &compat_length);
+	if (!compat_list)
+		return false;
+
+	/* Check each compatible string against PHY drivers only */
+	for (i = 0; i < compat_length; i += strlen(compat) + 1) {
+		compat = compat_list + i;
+
+		/* Iterate through all registered drivers */
+		for (entry = driver; entry != driver + n_ents; entry++) {
+			const struct udevice_id *of_match = entry->of_match;
+
+			/* Skip non-PHY drivers to improve performance */
+			if (entry->id != UCLASS_PHY)
+				continue;
+
+			if (!of_match)
+				continue;
+
+			while (of_match->compatible) {
+				if (!strcmp(of_match->compatible, compat)) {
+					debug("Found PHY driver '%s' for SS-PHY compatible '%s'\n",
+					      entry->name, compat);
+					return true;
+				}
+				of_match++;
+			}
+		}
+	}
+
+	return false;
+}
+
 static int fixup_qcom_dwc3(struct device_node *root, struct device_node *glue_np)
 {
-	struct device_node *dwc3;
+	struct device_node *dwc3, *ssphy_np;
 	int ret, len, hsphy_idx = 1;
 	const __be32 *phandles;
 	const char *second_phy_name;
 
-	debug("Fixing up %s\n", glue_np->name);
-
-	/* Tell the glue driver to configure the wrapper for high-speed only operation */
-	ret = of_write_prop(glue_np, "qcom,select-utmi-as-pipe-clk", 0, NULL);
-	if (ret) {
-		log_err("Failed to add property 'qcom,select-utmi-as-pipe-clk': %d\n", ret);
-		return ret;
-	}
+	debug("Checking USB configuration for %s\n", glue_np->name);
 
 	/* Find the DWC3 node itself */
 	dwc3 = of_find_compatible_node(glue_np, NULL, "snps,dwc3");
@@ -58,20 +134,38 @@ static int fixup_qcom_dwc3(struct device_node *root, struct device_node *glue_np
 	phandles = of_get_property(dwc3, "phys", &len);
 	len /= sizeof(*phandles);
 	if (len == 1) {
-		log_debug("Only one phy, not a superspeed controller\n");
+		debug("Only one phy, not a superspeed controller\n");
 		return 0;
 	}
 
-	/* Figure out if the superspeed phy is present and if so then which phy is it? */
+	/* Figure out if the superspeed phy is present */
 	ret = of_property_read_string_index(dwc3, "phy-names", 1, &second_phy_name);
 	if (ret == -ENODATA) {
-		log_debug("Only one phy, not a super-speed controller\n");
+		debug("Only one phy, not a super-speed controller\n");
 		return 0;
 	} else if (ret) {
 		log_err("Failed to read second phy name: %d\n", ret);
 		return ret;
 	}
 
+	/* Find the super-speed PHY node and check if a driver is available */
+	ssphy_np = find_ssphy_node(dwc3);
+	if (ssphy_np && has_driver_for_node(ssphy_np)) {
+		debug("Skipping USB fixup for %s (SS-PHY driver available)\n",
+		      glue_np->name);
+		return 0;
+	}
+
+	/* No driver available - apply the fixup */
+	debug("Applying USB high-speed fixup to %s\n", glue_np->name);
+
+	/* Tell the glue driver to configure the wrapper for high-speed only operation */
+	ret = of_write_prop(glue_np, "qcom,select-utmi-as-pipe-clk", 0, NULL);
+	if (ret) {
+		log_err("Failed to add property 'qcom,select-utmi-as-pipe-clk': %d\n", ret);
+		return ret;
+	}
+
 	/*
 	 * Determine which phy is the superspeed phy by checking the name of the second phy
 	 * since it is typically the superspeed one.
@@ -150,7 +244,7 @@ static void fixup_power_domains(struct device_node *root)
 	do { \
 		u64 start = timer_get_us(); \
 		func(__VA_ARGS__); \
-		debug(#func " took %lluus\n", timer_get_us() - start); \
+		printf(#func " took %lluus\n", timer_get_us() - start); \
 	} while (0)
 
 static int qcom_of_fixup_nodes(void * __maybe_unused ctx, struct event *event)
-- 
2.34.1



More information about the U-Boot mailing list