From a4446e13dba65f2e56e0d7a97875f76e5199015c Mon Sep 17 00:00:00 2001 From: Lukasz Czechowski Date: Tue, 22 Jul 2025 11:55:33 +0200 Subject: [PATCH 1/5] usb: onboard-hub: Use the ofnode to check if the peer-hub was probed Currently the check in usb_onboard_hub_bind is relying on specific compatible string for the Michrochip USB5744. Replace this with more generic approach that will allow to add new types of devices to the of_match table. Because the driver only needs to bind one "half" of the hub, the peer-hub node is used to find out if it was already done. In case peer-hub was bound, -ENODEV is returned. Reviewed-by: Quentin Schulz Signed-off-by: Lukasz Czechowski --- common/usb_onboard_hub.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/common/usb_onboard_hub.c b/common/usb_onboard_hub.c index d17c85dd622..7606362a4ee 100644 --- a/common/usb_onboard_hub.c +++ b/common/usb_onboard_hub.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -179,8 +180,8 @@ err: static int usb_onboard_hub_bind(struct udevice *dev) { struct ofnode_phandle_args phandle; - const void *fdt = gd->fdt_blob; - int ret, off; + struct udevice *peerdev; + int ret; ret = dev_read_phandle_with_args(dev, "peer-hub", NULL, 0, 0, &phandle); if (ret == -ENOENT) { @@ -193,10 +194,14 @@ static int usb_onboard_hub_bind(struct udevice *dev) return ret; } - off = ofnode_to_offset(phandle.node); - ret = fdt_node_check_compatible(fdt, off, "usb424,5744"); - if (!ret) + ret = uclass_find_device_by_ofnode(UCLASS_USB_HUB, phandle.node, &peerdev); + if (ret) { + dev_dbg(dev, "binding before peer-hub %s\n", + ofnode_get_name(phandle.node)); return 0; + } + + dev_dbg(dev, "peer-hub %s has been bound\n", peerdev->name); return -ENODEV; } From 480753ca92352e69a08455ad487089388fac9d88 Mon Sep 17 00:00:00 2001 From: Lukasz Czechowski Date: Tue, 22 Jul 2025 11:55:34 +0200 Subject: [PATCH 2/5] usb: onboard-hub: Use devm API do automatically free the reset GPIO The reset GPIO is obtained during driver probing by the function devm_gpiod_get_optional, which means the GPIO will be automatically freed when the device is removed. Because of this, explicit call to free the reset GPIO in hub remove function is not needed. To support the Managed device resources, the DEVRES config must be enabled, otherwise the devres functions fall back to non-managed variants. Set the necessary dependency to DEVRES in Kconfig. Reviewed-by: Quentin Schulz Signed-off-by: Lukasz Czechowski --- common/usb_onboard_hub.c | 3 --- drivers/usb/Kconfig | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/common/usb_onboard_hub.c b/common/usb_onboard_hub.c index 7606362a4ee..c6379192fe8 100644 --- a/common/usb_onboard_hub.c +++ b/common/usb_onboard_hub.c @@ -211,9 +211,6 @@ static int usb_onboard_hub_remove(struct udevice *dev) struct onboard_hub *hub = dev_get_priv(dev); int ret = 0; - if (hub->reset_gpio) - dm_gpio_free(hub->reset_gpio->dev, hub->reset_gpio); - if (hub->vdd) { ret = regulator_set_enable_if_allowed(hub->vdd, false); if (ret) diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 99c6649e417..daf2240ffd9 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -112,6 +112,7 @@ config USB_KEYBOARD config USB_ONBOARD_HUB bool "Onboard USB hub support" depends on DM_USB + select DEVRES ---help--- Say Y here if you want to support discrete onboard USB hubs that don't require an additional control bus for initialization, but From 42a025412d737cd57874b81de8c8f49fea538c8b Mon Sep 17 00:00:00 2001 From: Lukasz Czechowski Date: Tue, 22 Jul 2025 11:55:35 +0200 Subject: [PATCH 3/5] usb: onboard-hub: Set the reset gpio pin before freeing In the usb_onboard_hub_remove, the reset gpio, if available, is freed. The pin state however, remains unchanged, as set in the usb_onboard_hub_reset. The hub is then left enabled. During second onboard hub probing, the hub is initially enabled (reset pin in state "0"), and then it is being reset by the reset function (transition to "1" and then to "0"). Because of this, the hub first disconnects from root hub, and then it connects again. When the devices are being discovered in the usb_scan_port in the usb_hub driver, initially there is the USB_PORT_STAT_CONNECTION bit not set in portstatus, but USB_PORT_STAT_C_CONNECTION set in portchange data (which is because disconnect event occurred first). In this condition, the driver does not wait for devices to appear. This can cause the hub (and all child devices) to be not enumerated when rescanning on "usb reset" command. To fix this, set the reset gpio to active in usb_onboard_hub_remove, to put the hub into reset state. However, in case the hub reset pin is by default held in high state by HW before U-Boot takes over, in which case the USB hub is active, then during the first probe it gets reset and we might get into the same issue of the hub being not enumerated. Reviewed-by: Quentin Schulz Signed-off-by: Lukasz Czechowski --- common/usb_onboard_hub.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/common/usb_onboard_hub.c b/common/usb_onboard_hub.c index c6379192fe8..046831d0966 100644 --- a/common/usb_onboard_hub.c +++ b/common/usb_onboard_hub.c @@ -211,6 +211,13 @@ static int usb_onboard_hub_remove(struct udevice *dev) struct onboard_hub *hub = dev_get_priv(dev); int ret = 0; + if (hub->reset_gpio) { + ret = dm_gpio_set_value(hub->reset_gpio, 1); + if (ret) + dev_err(dev, "can't set gpio %s: %d\n", hub->reset_gpio->dev->name, + ret); + } + if (hub->vdd) { ret = regulator_set_enable_if_allowed(hub->vdd, false); if (ret) From 3eb6b0f504f7b6ed3d7786c48fc15fa4d8708d7c Mon Sep 17 00:00:00 2001 From: Lukasz Czechowski Date: Tue, 22 Jul 2025 11:55:36 +0200 Subject: [PATCH 4/5] usb: onboard-hub: Add support for multiple power supplies Some of the onboard hubs require multiple power supplies, so extend the driver to support them. The implementation is inspired by the kernel driver, as introduced by commit [1] in the v6.10 kernel. [1] https://github.com/torvalds/linux/commit/ec1848cd5df426f57a7f6a8a6b95b69259c52cfc Reviewed-by: Quentin Schulz Signed-off-by: Lukasz Czechowski --- common/usb_onboard_hub.c | 68 ++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/common/usb_onboard_hub.c b/common/usb_onboard_hub.c index 046831d0966..dbe4c3cf9b8 100644 --- a/common/usb_onboard_hub.c +++ b/common/usb_onboard_hub.c @@ -20,14 +20,18 @@ #define USB5744_CONFIG_REG_ACCESS 0x0037 #define USB5744_CONFIG_REG_ACCESS_LSB 0x99 +#define MAX_SUPPLIES 2 + struct onboard_hub { - struct udevice *vdd; + struct udevice *vdd[MAX_SUPPLIES]; struct gpio_desc *reset_gpio; }; struct onboard_hub_data { unsigned long reset_us; unsigned long power_on_delay_us; + unsigned int num_supplies; + const char * const supply_names[MAX_SUPPLIES]; int (*init)(struct udevice *dev); }; @@ -139,30 +143,59 @@ int usb_onboard_hub_reset(struct udevice *dev) return 0; } +static int usb_onboard_hub_power_off(struct udevice *dev) +{ + struct onboard_hub_data *data = + (struct onboard_hub_data *)dev_get_driver_data(dev); + struct onboard_hub *hub = dev_get_priv(dev); + int ret = 0, ret2; + unsigned int i; + + for (i = data->num_supplies; i > 0; i--) { + if (hub->vdd[i-1]) { + ret2 = regulator_set_enable_if_allowed(hub->vdd[i-1], false); + if (ret2 && ret2 != -ENOSYS) { + dev_err(dev, "can't disable %s: %d\n", data->supply_names[i-1], ret2); + ret |= ret2; + } + } + } + + return ret; +} + static int usb_onboard_hub_probe(struct udevice *dev) { struct onboard_hub_data *data = (struct onboard_hub_data *)dev_get_driver_data(dev); struct onboard_hub *hub = dev_get_priv(dev); + unsigned int i; int ret; - ret = device_get_supply_regulator(dev, "vdd-supply", &hub->vdd); - if (ret && ret != -ENOENT && ret != -ENOSYS) { - dev_err(dev, "can't get vdd-supply: %d\n", ret); - return ret; + if (data->num_supplies > MAX_SUPPLIES) { + dev_err(dev, "invalid supplies number, max supported: %d\n", MAX_SUPPLIES); + return -EINVAL; } - if (hub->vdd) { - ret = regulator_set_enable_if_allowed(hub->vdd, true); - if (ret && ret != -ENOSYS) { - dev_err(dev, "can't enable vdd-supply: %d\n", ret); - return ret; + for (i = 0; i < data->num_supplies; i++) { + ret = device_get_supply_regulator(dev, data->supply_names[i], &hub->vdd[i]); + if (ret && ret != -ENOENT && ret != -ENOSYS) { + dev_err(dev, "can't get %s: %d\n", data->supply_names[i], ret); + goto err_supply; + } + + if (hub->vdd[i]) { + ret = regulator_set_enable_if_allowed(hub->vdd[i], true); + if (ret && ret != -ENOSYS) { + dev_err(dev, "can't enable %s: %d\n", data->supply_names[i], ret); + goto err_supply; + } } } ret = usb_onboard_hub_reset(dev); if (ret) - return ret; + goto err_supply; if (data->init) { ret = data->init(dev); @@ -174,6 +207,8 @@ static int usb_onboard_hub_probe(struct udevice *dev) return 0; err: dm_gpio_set_value(hub->reset_gpio, 0); +err_supply: + usb_onboard_hub_power_off(dev); return ret; } @@ -218,24 +253,23 @@ static int usb_onboard_hub_remove(struct udevice *dev) ret); } - if (hub->vdd) { - ret = regulator_set_enable_if_allowed(hub->vdd, false); - if (ret) - dev_err(dev, "can't disable vdd-supply: %d\n", ret); - } - + ret |= usb_onboard_hub_power_off(dev); return ret; } static const struct onboard_hub_data usb2514_data = { .power_on_delay_us = 500, .reset_us = 1, + .num_supplies = 1, + .supply_names = { "vdd-supply" }, }; static const struct onboard_hub_data usb5744_data = { .init = usb5744_i2c_init, .power_on_delay_us = 1000, .reset_us = 5, + .num_supplies = 1, + .supply_names = { "vdd-supply" }, }; static const struct udevice_id usb_onboard_hub_ids[] = { From a1f8103bbeedfcb2f4521eabc9f1aa580d81ec00 Mon Sep 17 00:00:00 2001 From: Lukasz Czechowski Date: Tue, 22 Jul 2025 11:55:37 +0200 Subject: [PATCH 5/5] usb: onboard-hub: Add support for Cypress HX3 family The HX3 is a family of USB3.0 hub controllers that comes in different variants: CYUSB330x/CYUSB331x/CYUSB332x/CYUSB230x. To support this hub, controlling of reset pin and two power supplies is required. The reset time is set to 10ms, based on the datasheet [1]. Power-on delay time is not required, so it is set to 0. The compatible strings added to of_match table are compliant with usb/cypress,hx3.yaml bindings. [1] https://www.infineon.com/dgdl/Infineon-HX3_USB_3_0_Hub_Consumer_Industrial-DataSheet-v22_00-EN.pdf?fileId=8ac78c8c7d0d8da4017d0ecb53f644b8 Reviewed-by: Quentin Schulz Signed-off-by: Lukasz Czechowski --- common/usb_onboard_hub.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/common/usb_onboard_hub.c b/common/usb_onboard_hub.c index dbe4c3cf9b8..805b2ccbc00 100644 --- a/common/usb_onboard_hub.c +++ b/common/usb_onboard_hub.c @@ -272,6 +272,12 @@ static const struct onboard_hub_data usb5744_data = { .supply_names = { "vdd-supply" }, }; +static const struct onboard_hub_data usbhx3_data = { + .reset_us = 10000, + .num_supplies = 2, + .supply_names = { "vdd-supply", "vdd2-supply" }, +}; + static const struct udevice_id usb_onboard_hub_ids[] = { /* Use generic usbVID,PID dt-bindings (usb-device.yaml) */ { .compatible = "usb424,2514", /* USB2514B USB 2.0 */ @@ -282,7 +288,14 @@ static const struct udevice_id usb_onboard_hub_ids[] = { }, { .compatible = "usb424,5744", /* USB5744 USB 3.0 */ .data = (ulong)&usb5744_data, - } + }, { + .compatible = "usb4b4,6504", /* Cypress HX3 USB 3.0 */ + .data = (ulong)&usbhx3_data, + }, { + .compatible = "usb4b4,6506", /* Cypress HX3 USB 2.0 */ + .data = (ulong)&usbhx3_data, + }, + { /* sentinel */ } }; U_BOOT_DRIVER(usb_onboard_hub) = {