diff options
| author | Balaji Selvanathan <[email protected]> | 2025-12-03 16:37:33 +0530 |
|---|---|---|
| committer | Casey Connolly <[email protected]> | 2026-04-27 12:38:44 +0200 |
| commit | 5ebc47d984c4d510ceec4748e7d86451b72b6577 (patch) | |
| tree | d71468182fedd959248752f92cd8012b5fc46838 | |
| parent | 2c93971ba3b8f75304e31d28d16e0b0148fe0a77 (diff) | |
arch: arm: mach-snapdragon: Auto-detect USB SSPHY driver support
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.
Signed-off-by: Balaji Selvanathan <[email protected]>
[casey: rebased on flat-dwc3 dt support + fix Balaji's signoff]
[casey: make erroneous printf() a debug() again]
Link: https://patch.msgid.link/[email protected]
Signed-off-by: Casey Connolly <[email protected]>
| -rw-r--r-- | arch/arm/mach-snapdragon/of_fixup.c | 128 |
1 files changed, 111 insertions, 17 deletions
diff --git a/arch/arm/mach-snapdragon/of_fixup.c b/arch/arm/mach-snapdragon/of_fixup.c index 286541c8edc..1298b774c1e 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,26 +21,107 @@ #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, bool flat) { - 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); - /* New DT flattens the glue and controller into a single node. */ if (flat) { dwc3 = glue_np; @@ -54,30 +135,43 @@ static int fixup_qcom_dwc3(struct device_node *root, struct device_node *glue_np } } - /* 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", dwc3->name); 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", + dwc3->name); + return 0; + } + + /* No driver available - apply the fixup */ + debug("Applying USB high-speed fixup to %s\n", dwc3->name); + + /* Tell the glue driver to configure the wrapper for high-speed only operation */ + ret = of_write_prop(dwc3, "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. |
