[PATCH v5] usb: mtu3: support the upstream devicetree binding

Carlo Caione <[email protected]> Tue, 04 Aug 2026 12:05:10 +0200
Newsgroups gmane.comp.boot-loaders.u-boot
Message-ID <20260804-ccaione-upstream-mtu3-spl-gadget-v5-1-5c3b6ec44eb5@baylibre.com>
The MTU3 glue driver expects a synthetic mediatek,ssusb child that
combines the device and xHCI register windows. Upstream devicetrees
instead put the device resources on the controller node and describe the
xHCI window with a standard child node, so U-Boot cannot use an upstream
topology.

Switch to the upstream layout. The controller owns the shared resources
and the device MAC, with the device register offsets relative to it as in
Linux. The gadget role binds as a node-less internal device, while the
host role binds to an enabled xHCI child and uses its own clocks,
supplies and register window. dr_mode selects the role and defaults to
otg, and the Kconfig choice constrains which role the build provides.
The legacy child layout is rejected explicitly so its combined register
window cannot be mistaken for the device MAC.

Convert the in-tree MT8183 and MT8512 devicetrees in the same commit so
neither platform is left without USB. MT8512 stays peripheral-only
because its xHCI host interrupt is not documented and the
mediatek,mtk-xhci binding requires it.

Signed-off-by: Carlo Caione <[email protected]>
---
Changes in v5:
- Use separate device-MAC and xHCI register resources
- Bind the gadget as an internal device without reusing the controller node
- Bind host mode to an enabled standard xHCI child and consume its resources
- Honor dr_mode, including its default of otg
- Reject the legacy combined-register child layout explicitly
- Enable address translation for an SPL host build
- Keep MT8512 peripheral-only until its host clock and interrupt are known
- Convert the in-tree devicetrees and update the binding documentation
- Keep the driver and devicetree conversion in one bisectable patch
- Link to v4: https://patch.msgid.link/20260722-ccaione-upstream-mtu3-spl-gadget-v4-0-9719f4cecaeb@baylibre.com

Changes in v4:
- Describe mediatek,ssusb as a legacy U-Boot-only binding
- Fix the MAC resource error and remove redundant ofnode initialization
- Convert the MT8183 devicetree to the upstream MTU3 binding
- Link to v3: https://patch.msgid.link/20260720-ccaione-upstream-mtu3-spl-gadget-v3-1-91cff44b1622@baylibre.com

Changes in v3:
- Remove the temporary platform data and derive the binding during probe
- Use dev_read_addr_name() for the MAC resource
- Link to v2: https://patch.msgid.link/20260718-ccaione-upstream-mtu3-spl-gadget-v2-1-7f69ca462559@baylibre.com

Changes in v2:
- Removed typecast horror
- Added platform storage to pass around legacy flags and device
- Link to v1: https://patch.msgid.link/20260717-ccaione-upstream-mtu3-spl-gadget-v1-1-57a3e2d0a2b0@baylibre.com
---
 arch/arm/dts/mt8183-pumpkin.dts                |   4 -
 arch/arm/dts/mt8183.dtsi                       |  17 +-
 arch/arm/dts/mt8512-bm1-emmc.dts               |   6 +-
 arch/arm/dts/mt8512.dtsi                       |  19 +--
 doc/device-tree-bindings/usb/mediatek,mtu3.txt |  93 ++++------
 drivers/usb/mtu3/Kconfig                       |   1 +
 drivers/usb/mtu3/mtu3.h                        |  12 +-
 drivers/usb/mtu3/mtu3_core.c                   |   4 +-
 drivers/usb/mtu3/mtu3_host.c                   |  11 +-
 drivers/usb/mtu3/mtu3_hw_regs.h                |  11 +-
 drivers/usb/mtu3/mtu3_plat.c                   | 227 ++++++++++++++++++-------
 11 files changed, 222 insertions(+), 183 deletions(-)

diff --git a/arch/arm/dts/mt8183-pumpkin.dts b/arch/arm/dts/mt8183-pumpkin.dts
index 470a7693997..623561b6a63 100644
--- a/arch/arm/dts/mt8183-pumpkin.dts
+++ b/arch/arm/dts/mt8183-pumpkin.dts
@@ -88,10 +88,6 @@
 };
 
 &usb {
-	status = "okay";
-};
-
-&ssusb {
 	mediatek,force-vbus;
 	maximum-speed = "high-speed";
 	dr_mode = "peripheral";
diff --git a/arch/arm/dts/mt8183.dtsi b/arch/arm/dts/mt8183.dtsi
index 294aa2b8979..323a88a7ac9 100644
--- a/arch/arm/dts/mt8183.dtsi
+++ b/arch/arm/dts/mt8183.dtsi
@@ -236,9 +236,9 @@
 			};
 		};
 
-		usb: usb@11200000 {
+		usb: usb@11201000 {
 			compatible ="mediatek,mt8183-mtu3", "mediatek,mtu3";
-			reg = <0 0x11200000 0 0x3e00>,
+			reg = <0 0x11201000 0 0x2e00>,
 			      <0 0x11203e00 0 0x0100>;
 			reg-names = "mac", "ippc";
 			interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_LOW>;
@@ -251,16 +251,9 @@
 			ranges;
 			status = "disabled";
 
-			ssusb: ssusb@11200000 {
-				compatible = "mediatek,ssusb";
-				reg = <0 0x11200000 0 0x3e00>;
-				reg-names = "mac";
-				interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_LOW>;
-				status = "disabled";
-			};
-
-			usb_host: xhci@11200000 {
-				compatible = "mediatek,mtk-xhci";
+			usb_host: usb@11200000 {
+				compatible = "mediatek,mt8183-xhci",
+					     "mediatek,mtk-xhci";
 				reg = <0 0x11200000 0 0x1000>;
 				reg-names = "mac";
 				interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_LOW>;
diff --git a/arch/arm/dts/mt8512-bm1-emmc.dts b/arch/arm/dts/mt8512-bm1-emmc.dts
index 12511b5fed4..13d3f804cb4 100644
--- a/arch/arm/dts/mt8512-bm1-emmc.dts
+++ b/arch/arm/dts/mt8512-bm1-emmc.dts
@@ -114,13 +114,9 @@
 		};
 };
 
-&ssusb {
+&usb3 {
 	dr_mode = "peripheral";
 	maximum-speed = "high-speed";
-	status = "okay";
-};
-
-&usb3 {
 	vbus-supply = <&usb_p0_vbus>;
 	status = "okay";
 };
diff --git a/arch/arm/dts/mt8512.dtsi b/arch/arm/dts/mt8512.dtsi
index bdb84f8ef00..eb0a09826f9 100644
--- a/arch/arm/dts/mt8512.dtsi
+++ b/arch/arm/dts/mt8512.dtsi
@@ -101,27 +101,18 @@
 		status = "disabled";
 	};
 
-	usb3: usb@11213e00 {
+	usb3: usb@11211000 {
 		compatible = "mediatek,mt8512-mtu3", "mediatek,mtu3";
-		reg = <0x11213e00 0x0100>;
-		reg-names = "ippc";
+		reg = <0x11211000 0x2e00>,
+		      <0x11213e00 0x0100>;
+		reg-names = "mac", "ippc";
+		interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_LOW>;
 		phys = <&u2port0 PHY_TYPE_USB2>, <&u2port1 PHY_TYPE_USB2>;
 		clocks = <&infracfg CLK_INFRA_USB_SYS>,
 			 <&topckgen CLK_TOP_SSUSB_TOP_CK_EN>,
 			 <&infracfg CLK_INFRA_ICUSB>;
 		clock-names = "sys_ck", "ref_ck", "mcu_ck";
-		#address-cells = <1>;
-		#size-cells = <1>;
-		ranges;
 		status = "disabled";
-
-		ssusb: usb@11210000 {
-			compatible = "mediatek,ssusb";
-			reg = <0x11210000 0x3e00>;
-			interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_LOW>;
-			reg-names = "mac";
-			status = "disabled";
-		};
 	};
 
 	u3phy: usb-phy@11cc0000 {
diff --git a/doc/device-tree-bindings/usb/mediatek,mtu3.txt b/doc/device-tree-bindings/usb/mediatek,mtu3.txt
index ab877bfa89f..c0f077c8c8e 100644
--- a/doc/device-tree-bindings/usb/mediatek,mtu3.txt
+++ b/doc/device-tree-bindings/usb/mediatek,mtu3.txt
@@ -1,79 +1,44 @@
-The device node for Mediatek USB3 DRD controller
+MediaTek USB3 DRD controller
+============================
 
-Required properties:
- - compatible : should be "mediatek,<soc-model>-mtu3", "mediatek,mtu3",
-	soc-model is the name of SoC, such as mt8512 etc,
-	when using "mediatek,mtu3" compatible string, you need SoC specific
-	ones in addition, one of:
-	- "mediatek,mt8512-mtu3"
- - reg : specifies physical base address and size of the registers
- - reg-names: should be
-	- "ippc" : IP Port Control
- - power-domains : a phandle to USB power domain node to control USB's MTCMOS
- - clocks : a list of phandle + clock-specifier pairs, one for each
-	entry in clock-names
- - clock-names : must contain "sys_ck" for clock of controller,
-	the following clocks are optional:
-	"ref_ck", "mcu_ck", "dma_ck" and "xhci_ck";
- - phys : list of all the USB PHYs on this HCD
- - #address-cells, #size-cells : used for sub-nodes with 'reg' property
- - ranges : allows valid 1:1 translation between child's address space and
-	parent's address space
+U-Boot follows the upstream MediaTek MTU3 binding described in:
 
-Optional properties:
- - vusb33-supply : regulator of USB AVDD3.3v
- - vbus-supply : regulator of VBUS 5v, needed when supports host mode.
+  dts/upstream/Bindings/usb/mediatek,mtu3.yaml
 
-Sub-nodes:
-Required properties:
- - compatible : should be "mediatek,ssusb"
- - reg : specifies physical base address and size of the registers
- - reg-names: should be
-	- "mac" : SSUSB MAC, include xHCI and device
- - interrupts : interrupt used by xHCI or device
- - dr_mode : should be one of "host" or "peripheral",
-	see : usb/generic.txt
+The controller node owns the device MAC and IP Port Control resources and the
+device interrupt. The "mac" resource starts at the device MAC register block;
+it does not include the preceding xHCI register block. The dr_mode property
+selects the controller role, while CONFIG_USB_MTU3_GADGET and
+CONFIG_USB_MTU3_HOST select the role supported by the U-Boot build.
 
-Optional properties:
- - pinctrl-names : a pinctrl state named "default" is optional
- - pinctrl-0 : pin control group
-	See: pinctrl/pinctrl-bindings.txt
+U-Boot additionally supports the "mediatek,mt8512-mtu3" compatible using the
+same property layout. It also supports the optional boolean property
+"mediatek,force-vbus" to force VBUS valid in device mode. These U-Boot
+extensions are not part of the upstream schema, so dt-validate flags them.
 
- - device mode:
-   - maximum-speed : valid arguments are "full-speed", "high-speed",
-	"super-speed" and "super-speed-plus",
-	see: usb/generic.txt
-   - mediatek,force-vbus : force vbus as valid by SW
-
- - host mode (dr_mode is "host"):
-   - mediatek,u3p-dis-msk : mask to disable u3ports, bit0 for u3port0,
-	bit1 for u3port1, ... etc;
+The legacy U-Boot-only "mediatek,ssusb" child layout is not supported. In host
+mode U-Boot binds its host role to an enabled standard MediaTek xHCI child,
+whose "mac" resource describes the separate xHCI register block. MT8512 host
+mode is not described because its xHCI host interrupt is not documented, and
+the mediatek,mtk-xhci binding requires it; the host clock itself is available
+as CLK_INFRA_USB_XHCI.
 
 Example:
-usb3: usb@11213e00 {
+
+usb3: usb@11211000 {
 	compatible = "mediatek,mt8512-mtu3", "mediatek,mtu3";
-	reg = <0x11213e00 0x0100>;
-	reg-names = "ippc";
+	reg = <0x11211000 0x2e00>,
+	      <0x11213e00 0x0100>;
+	reg-names = "mac", "ippc";
+	interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_LOW>;
 	phys = <&u2port0 PHY_TYPE_USB2>, <&u2port1 PHY_TYPE_USB2>;
-	power-domains = <&scpsys MT8512_POWER_DOMAIN_USB>;
 	clocks = <&infracfg CLK_INFRA_USB_SYS>,
 		 <&topckgen CLK_TOP_SSUSB_TOP_CK_EN>,
 		 <&infracfg CLK_INFRA_ICUSB>;
 	clock-names = "sys_ck", "ref_ck", "mcu_ck";
-	vusb33-supply = <reg_3p3v>;
+	dr_mode = "peripheral";
+	maximum-speed = "high-speed";
+	mediatek,force-vbus;
 	vbus-supply = <&usb_p0_vbus>;
-	#address-cells = <1>;
-	#size-cells = <1>;
-	ranges;
-	status = "disabled";
-
-	ssusb: usb@11210000 {
-		compatible = "mediatek,ssusb";
-		reg = <0x11210000 0x3e00>;
-		interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_LOW>;
-		reg-names = "mac";
-		dr_mode = "peripheral";
-		maximum-speed = "high-speed";
-		status = "disabled";
-	};
+	status = "okay";
 };
diff --git a/drivers/usb/mtu3/Kconfig b/drivers/usb/mtu3/Kconfig
index 5ec498ec953..22f93385d1d 100644
--- a/drivers/usb/mtu3/Kconfig
+++ b/drivers/usb/mtu3/Kconfig
@@ -22,6 +22,7 @@ choice
 config USB_MTU3_HOST
 	bool "Host only mode"
 	depends on USB_XHCI_HCD
+	select SPL_OF_TRANSLATE if SPL_USB_HOST
 	help
 	  Select this when you want to use MTU3 in host mode only,
 	  thereby the gadget feature will be regressed.
diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h
index 8a7ae83ee99..7afccd7208a 100644
--- a/drivers/usb/mtu3/mtu3.h
+++ b/drivers/usb/mtu3/mtu3.h
@@ -137,15 +137,13 @@ enum mtu3_dr_force_mode {
 };
 
 /**
- * @mac_base: register base address of MAC, include xHCI and device
+ * @mac_base: register base address of the device MAC
  * @ippc_base: register base address of IP Power and Clock interface (IPPC)
  * @vusb33_supply: usb3.3V shared by device/host IP
  * @vbus_supply: vbus 5v of OTG port
  * @clks: optional clocks, include "sys_ck", "ref_ck", "mcu_ck",
  *		"dma_ck" and "xhci_ck"
  * @phys: phys used
- * @dr_mode: works in which mode:
- *		host only, device only or dual-role mode
  */
 struct ssusb_mtk {
 	struct udevice *dev;
@@ -158,13 +156,14 @@ struct ssusb_mtk {
 	struct udevice *vbus_supply;
 	struct clk_bulk clks;
 	struct phy_bulk phys;
-	/* otg */
-	enum usb_dr_mode dr_mode;
 };
 
 /**
  * @ctrl: xHCI controller, needs to come first in this struct!
  * @hcd: xHCI's register base address
+ * @vusb33_supply: regulator from the xHCI child node
+ * @vbus_supply: regulator from the xHCI child node or its MTU3 parent
+ * @clks: clocks from the xHCI child node
  * @u2_ports: number of usb2 host ports
  * @u3_ports: number of usb3 host ports
  * @u3p_dis_msk: mask of disabling usb3 ports, for example, bit0==1 to
@@ -176,6 +175,9 @@ struct mtu3_host {
 	void __iomem *ippc_base;
 	struct ssusb_mtk *ssusb;
 	struct udevice *dev;
+	struct udevice *vusb33_supply;
+	struct udevice *vbus_supply;
+	struct clk_bulk clks;
 	u32 u2_ports;
 	u32 u3_ports;
 	u32 u3p_dis_msk;
diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c
index 2f5cc9b1480..3a1f8ff404f 100644
--- a/drivers/usb/mtu3/mtu3_core.c
+++ b/drivers/usb/mtu3/mtu3_core.c
@@ -802,8 +802,8 @@ int ssusb_gadget_init(struct ssusb_mtk *ssusb)
 	mtu->ippc_base = ssusb->ippc_base;
 	mtu->mac_base = ssusb->mac_base;
 	mtu->ssusb = ssusb;
-	mtu->max_speed = usb_get_maximum_speed(dev_ofnode(dev));
-	mtu->force_vbus = dev_read_bool(dev, "mediatek,force-vbus");
+	mtu->max_speed = usb_get_maximum_speed(dev_ofnode(ssusb->dev));
+	mtu->force_vbus = dev_read_bool(ssusb->dev, "mediatek,force-vbus");
 
 	ret = mtu3_hw_init(mtu);
 	if (ret) {
diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c
index 8001fc2d9bd..f1675065c54 100644
--- a/drivers/usb/mtu3/mtu3_host.c
+++ b/drivers/usb/mtu3/mtu3_host.c
@@ -110,11 +110,11 @@ int ssusb_host_init(struct ssusb_mtk *ssusb)
 	int ret;
 
 	u3h->ssusb = ssusb;
-	u3h->hcd = ssusb->mac_base;
 	u3h->ippc_base = ssusb->ippc_base;
 
 	/* optional property, ignore the error */
-	dev_read_u32(dev, "mediatek,u3p-dis-msk", &u3h->u3p_dis_msk);
+	dev_read_u32(ssusb->dev, "mediatek,u3p-dis-msk",
+		     &u3h->u3p_dis_msk);
 
 	host_ports_num_get(u3h);
 	ret = ssusb_host_enable(u3h);
@@ -123,12 +123,6 @@ int ssusb_host_init(struct ssusb_mtk *ssusb)
 
 	ssusb_set_force_mode(ssusb, MTU3_DR_FORCE_HOST);
 
-	ret = regulator_set_enable(ssusb->vbus_supply, true);
-	if (ret < 0 && ret != -ENOSYS) {
-		dev_err(dev, "failed to enable vbus %d!\n", ret);
-		return ret;
-	}
-
 	dev_info(dev, "%s done...\n", __func__);
 
 	return 0;
@@ -136,6 +130,5 @@ int ssusb_host_init(struct ssusb_mtk *ssusb)
 
 void ssusb_host_exit(struct ssusb_mtk *ssusb)
 {
-	regulator_set_enable(ssusb->vbus_supply, false);
 	ssusb_host_disable(ssusb->u3h);
 }
diff --git a/drivers/usb/mtu3/mtu3_hw_regs.h b/drivers/usb/mtu3/mtu3_hw_regs.h
index 9c2a7e1f466..2ac72953e34 100644
--- a/drivers/usb/mtu3/mtu3_hw_regs.h
+++ b/drivers/usb/mtu3/mtu3_hw_regs.h
@@ -11,12 +11,11 @@
 #define _SSUSB_HW_REGS_H_
 
 /* segment offset of MAC register */
-#define SSUSB_XCHI_BASE		0x0000
-#define SSUSB_DEV_BASE		0x1000
-#define SSUSB_EPCTL_CSR_BASE	0x1800
-#define SSUSB_USB3_MAC_CSR_BASE	0x2400
-#define SSUSB_USB3_SYS_CSR_BASE	0x2400
-#define SSUSB_USB2_CSR_BASE	0x3400
+#define SSUSB_DEV_BASE		0x0000
+#define SSUSB_EPCTL_CSR_BASE	0x0800
+#define SSUSB_USB3_MAC_CSR_BASE	0x1400
+#define SSUSB_USB3_SYS_CSR_BASE	0x1400
+#define SSUSB_USB2_CSR_BASE	0x2400
 
 /* IPPC register in Infra */
 #define SSUSB_SIFSLV_IPPC_BASE	0x0000
diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c
index 26fee141f6e..6957e3c2d33 100644
--- a/drivers/usb/mtu3/mtu3_plat.c
+++ b/drivers/usb/mtu3/mtu3_plat.c
@@ -135,7 +135,6 @@ static void ssusb_ip_sw_reset(struct ssusb_mtk *ssusb)
 
 static int get_ssusb_rscs(struct udevice *dev, struct ssusb_mtk *ssusb)
 {
-	struct udevice *child;
 	int ret;
 
 	ret = device_get_supply_regulator(dev, "vusb33-supply",
@@ -154,37 +153,20 @@ static int get_ssusb_rscs(struct udevice *dev, struct ssusb_mtk *ssusb)
 		return ret;
 	}
 
-	ssusb->ippc_base = devfdt_remap_addr_name(dev, "ippc");
+	ssusb->ippc_base = dev_remap_addr_name(dev, "ippc");
 	if (!ssusb->ippc_base) {
 		dev_err(dev, "error mapping memory for ippc\n");
 		return -ENODEV;
 	}
 
-	ret = device_find_first_child(dev, &child);
-	if (ret || !child) {
-		dev_err(dev, "failed to get child %d!\n", ret);
-		return ret;
-	}
-
-	ssusb->mac_base = devfdt_remap_addr_name(child, "mac");
+	ssusb->mac_base = dev_remap_addr_name(dev, "mac");
 	if (!ssusb->mac_base) {
 		dev_err(dev, "error mapping memory for mac\n");
 		return -ENODEV;
 	}
 
-	ssusb->dr_mode = usb_get_dr_mode(dev_ofnode(child));
-
-	if (ssusb->dr_mode == USB_DR_MODE_UNKNOWN ||
-		ssusb->dr_mode == USB_DR_MODE_OTG)
-		ssusb->dr_mode = USB_DR_MODE_PERIPHERAL;
-
-	if (IS_ENABLED(CONFIG_USB_MTU3_GADGET))
-		ssusb->dr_mode = USB_DR_MODE_PERIPHERAL;
-	else if (IS_ENABLED(CONFIG_USB_MTU3_HOST))
-		ssusb->dr_mode = USB_DR_MODE_HOST;
-
-	dev_info(dev, "dr_mode: %d, ippc: 0x%p, mac: 0x%p\n",
-		 ssusb->dr_mode, ssusb->ippc_base, ssusb->mac_base);
+	dev_info(dev, "ippc: 0x%p, mac: 0x%p\n",
+		 ssusb->ippc_base, ssusb->mac_base);
 
 	return 0;
 }
@@ -217,11 +199,6 @@ static int mtu3_remove(struct udevice *dev)
 	return 0;
 }
 
-static const struct udevice_id ssusb_of_match[] = {
-	{.compatible = "mediatek,ssusb",},
-	{},
-};
-
 #if CONFIG_IS_ENABLED(DM_USB_GADGET)
 static int mtu3_gadget_probe(struct udevice *dev)
 {
@@ -257,16 +234,90 @@ static const struct usb_gadget_generic_ops mtu3_gadget_ops = {
 U_BOOT_DRIVER(mtu3_peripheral) = {
 	.name = "mtu3-peripheral",
 	.id = UCLASS_USB_GADGET_GENERIC,
-	.of_match = ssusb_of_match,
 	.ops = &mtu3_gadget_ops,
 	.probe = mtu3_gadget_probe,
 	.remove = mtu3_gadget_remove,
 	.priv_auto	= sizeof(struct mtu3),
 };
+
+static int mtu3_bind_gadget(struct udevice *parent)
+{
+	struct udevice *dev;
+	int ret;
+
+	/* Node-less device: name it after the controller for diagnostics. */
+	ret = device_bind_driver(parent, "mtu3-peripheral",
+				 ofnode_get_name(dev_ofnode(parent)), &dev);
+	if (ret)
+		dev_err(parent, "failed to bind peripheral mode\n");
+
+	return ret;
+}
+#else
+static int mtu3_bind_gadget(struct udevice *parent)
+{
+	return -ENODEV;
+}
 #endif
 
 #if defined(CONFIG_SPL_USB_HOST) || \
 	(!defined(CONFIG_XPL_BUILD) && defined(CONFIG_USB_HOST))
+static int mtu3_host_rscs_init(struct mtu3_host *u3h)
+{
+	struct ssusb_mtk *ssusb = u3h->ssusb;
+	int ret;
+
+	ret = clk_get_bulk(u3h->dev, &u3h->clks);
+	if (ret) {
+		dev_err(u3h->dev, "failed to get clocks %d!\n", ret);
+		return ret;
+	}
+
+	ret = device_get_supply_regulator(u3h->dev, "vusb33-supply",
+					  &u3h->vusb33_supply);
+	if (ret)
+		dev_dbg(u3h->dev, "can't get optional vusb33 %d\n", ret);
+
+	ret = device_get_supply_regulator(u3h->dev, "vbus-supply",
+					  &u3h->vbus_supply);
+	if (ret) {
+		dev_dbg(u3h->dev, "can't get optional vbus regulator %d\n",
+			ret);
+		u3h->vbus_supply = ssusb->vbus_supply;
+	}
+
+	ret = regulator_set_enable_if_allowed(u3h->vusb33_supply, true);
+	if (ret && ret != -ENOSYS) {
+		dev_err(u3h->dev, "failed to enable vusb33 %d!\n", ret);
+		return ret;
+	}
+
+	ret = regulator_set_enable_if_allowed(u3h->vbus_supply, true);
+	if (ret && ret != -ENOSYS) {
+		dev_err(u3h->dev, "failed to enable vbus %d!\n", ret);
+		goto vbus_err;
+	}
+
+	ret = clk_enable_bulk(&u3h->clks);
+	if (ret)
+		goto clks_err;
+
+	return 0;
+
+clks_err:
+	regulator_set_enable_if_allowed(u3h->vbus_supply, false);
+vbus_err:
+	regulator_set_enable_if_allowed(u3h->vusb33_supply, false);
+	return ret;
+}
+
+static void mtu3_host_rscs_exit(struct mtu3_host *u3h)
+{
+	clk_disable_bulk(&u3h->clks);
+	regulator_set_enable_if_allowed(u3h->vbus_supply, false);
+	regulator_set_enable_if_allowed(u3h->vusb33_supply, false);
+}
+
 static int mtu3_host_probe(struct udevice *dev)
 {
 	struct ssusb_mtk *ssusb = dev_to_ssusb(dev->parent);
@@ -276,15 +327,36 @@ static int mtu3_host_probe(struct udevice *dev)
 
 	u3h->dev = dev;
 	ssusb->u3h = u3h;
-	rc = ssusb_host_init(ssusb);
+	u3h->ssusb = ssusb;
+	u3h->hcd = dev_remap_addr_name(dev, "mac");
+	if (!u3h->hcd) {
+		dev_err(dev, "error mapping memory for xHCI mac\n");
+		return -ENODEV;
+	}
+
+	rc = mtu3_host_rscs_init(u3h);
 	if (rc)
 		return rc;
 
+	rc = ssusb_host_init(ssusb);
+	if (rc)
+		goto rscs_err;
+
 	u3h->ctrl.quirks = XHCI_MTK_HOST;
 	hcor = (struct xhci_hcor *)((uintptr_t)u3h->hcd +
 			HC_LENGTH(xhci_readl(&u3h->hcd->cr_capbase)));
 
-	return xhci_register(dev, u3h->hcd, hcor);
+	rc = xhci_register(dev, u3h->hcd, hcor);
+	if (rc)
+		goto host_err;
+
+	return 0;
+
+host_err:
+	ssusb_host_exit(ssusb);
+rscs_err:
+	mtu3_host_rscs_exit(u3h);
+	return rc;
 }
 
 static int mtu3_host_remove(struct udevice *dev)
@@ -293,68 +365,99 @@ static int mtu3_host_remove(struct udevice *dev)
 
 	xhci_deregister(dev);
 	ssusb_host_exit(u3h->ssusb);
+	mtu3_host_rscs_exit(u3h);
 	return 0;
 }
 
 U_BOOT_DRIVER(mtu3_host) = {
 	.name = "mtu3-host",
 	.id = UCLASS_USB,
-	.of_match = ssusb_of_match,
 	.probe = mtu3_host_probe,
 	.remove = mtu3_host_remove,
 	.priv_auto	= sizeof(struct mtu3_host),
 	.ops = &xhci_usb_ops,
 	.flags = DM_FLAG_ALLOC_PRIV_DMA,
 };
-#endif
 
-static int mtu3_glue_bind(struct udevice *parent)
+static int mtu3_bind_host(struct udevice *parent)
 {
 	struct udevice *dev;
-	enum usb_dr_mode dr_mode;
-	const char *driver;
 	const char *name;
 	ofnode node;
 	int ret;
 
-	node = ofnode_by_compatible(dev_ofnode(parent), "mediatek,ssusb");
-	if (!ofnode_valid(node))
+	ofnode_for_each_subnode(node, dev_ofnode(parent)) {
+		if (ofnode_is_enabled(node) &&
+		    ofnode_device_is_compatible(node, "mediatek,mtk-xhci"))
+			break;
+	}
+	if (!ofnode_valid(node)) {
+		dev_err(parent, "failed to find enabled xHCI child node\n");
 		return -ENODEV;
+	}
 
 	name = ofnode_get_name(node);
-	dr_mode = usb_get_dr_mode(node);
+	ret = device_bind_driver_to_node(parent, "mtu3-host", name, node,
+					 &dev);
+	if (ret)
+		dev_err(parent, "failed to bind host mode\n");
+
+	return ret;
+}
+#else
+static int mtu3_bind_host(struct udevice *parent)
+{
+	return -ENODEV;
+}
+#endif
+
+static int mtu3_glue_bind(struct udevice *parent)
+{
+	enum usb_dr_mode dr_mode;
+	ofnode node;
+
+	/* Reject the old layout before misinterpreting its register offsets. */
+	ofnode_for_each_subnode(node, dev_ofnode(parent)) {
+		if (ofnode_device_is_compatible(node, "mediatek,ssusb")) {
+			dev_err(parent,
+				"legacy mediatek,ssusb child is unsupported\n");
+			/* Only -ENODEV keeps the failure contained to USB. */
+			return -ENODEV;
+		}
+	}
+
+	dr_mode = usb_get_dr_mode(dev_ofnode(parent));
+	if (dr_mode == USB_DR_MODE_UNKNOWN) {
+		/* Absent defaults to otg, an unparsable value is an error. */
+		if (dev_read_string(parent, "dr_mode")) {
+			dev_err(parent, "invalid dr_mode\n");
+			return -ENODEV;
+		}
+
+		dr_mode = USB_DR_MODE_OTG;
+	}
 
 	switch (dr_mode) {
-#if CONFIG_IS_ENABLED(DM_USB_GADGET)
 	case USB_DR_MODE_PERIPHERAL:
-	case USB_DR_MODE_OTG:
-		dev_dbg(parent, "%s: dr_mode: peripheral\n", __func__);
-		driver = "mtu3-peripheral";
+		if (IS_ENABLED(CONFIG_USB_MTU3_GADGET))
+			return mtu3_bind_gadget(parent);
 		break;
-#endif
-
-#if defined(CONFIG_SPL_USB_HOST) || \
-	(!defined(CONFIG_XPL_BUILD) && defined(CONFIG_USB_HOST))
 	case USB_DR_MODE_HOST:
-		dev_dbg(parent, "%s: dr_mode: host\n", __func__);
-		driver = "mtu3-host";
+		if (IS_ENABLED(CONFIG_USB_MTU3_HOST))
+			return mtu3_bind_host(parent);
+		break;
+	case USB_DR_MODE_OTG:
+		if (IS_ENABLED(CONFIG_USB_MTU3_GADGET))
+			return mtu3_bind_gadget(parent);
+		if (IS_ENABLED(CONFIG_USB_MTU3_HOST))
+			return mtu3_bind_host(parent);
 		break;
-#endif
 	default:
-		dev_err(parent, "%s: unsupported dr_mode %d\n",
-			__func__, dr_mode);
-		return -ENODEV;
-	};
-
-	dev_dbg(parent, "%s: node name: %s, driver %s, dr_mode %d\n",
-		__func__, name, driver, dr_mode);
-
-	ret = device_bind_driver_to_node(parent, driver, name, node, &dev);
-	if (ret)
-		dev_err(parent, "%s: not able to bind usb device mode\n",
-			__func__);
+		break;
+	}
 
-	return ret;
+	dev_err(parent, "dr_mode %d is unsupported by this build\n", dr_mode);
+	return -ENODEV;
 }
 
 static const struct udevice_id mtu3_of_match[] = {

---
base-commit: d0c49353a648e135f269a2953dc9326505ba87de
change-id: 20260717-ccaione-upstream-mtu3-spl-gadget-aa3d39cad06a

Best regards,
--  
Carlo Caione <[email protected]>