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 <balaji.selvanathan@oss.qualcomm.com> [casey: rebased on flat-dwc3 dt support + fix Balaji's signoff] [casey: make erroneous printf() a debug() again] Link: https://patch.msgid.link/20251203110735.1959862-5-balaji.selvanathan@oss.qualcomm.com Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
This commit is contained in:
committed by
Casey Connolly
parent
2c93971ba3
commit
5ebc47d984
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user