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 <[email protected]>

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
>

Reply via email to