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

Varadarajan Narayanan varadarajan.narayanan at oss.qualcomm.com
Thu Dec 18 09:04:19 CET 2025


On Wed, Dec 03, 2025 at 04:37:33PM +0530, Balaji Selvanathan wrote:
> 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>

Move 'Signed-off-by:' above the '---' line.

> ---
>  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]));
> +		}
> +	}

fixup_qcom_dwc3() somehow assumes the second phandle *will* be usb3 phy.
Can't we continue with the assumption here too?

> +
> +	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;

		if (entry->id != UCLASS_PHY || !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");

Undo.

>  		return 0;

Earlier, the fixup was at the top, so you can return here. Now you have
to apply the fixup in this case.

>  	}
>
> -	/* 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");

Undo.

>  		return 0;

Can't return. Same as above.

>  	} 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;

Both the node and driver can be present. Should we consider the
possibility of phy DT node status = "disabled"?

> +	}
> +
> +	/* 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