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
>