[PATCH v3 07/15] usb: ehci: add Marvell EHCI host controller driver

Luca Lauro via B4 Relay <[email protected]> Sun, 02 Aug 2026 15:16:23 +0200
Newsgroups org.infradead.lists.barebox,org.kernel.feeds.b4-sent
Message-ID <[email protected]>
From: Luca Lauro <[email protected]>

Signed-off-by: Luca Lauro <[email protected]>
---
 drivers/usb/host/Kconfig        |   7 ++
 drivers/usb/host/Makefile       |  13 +--
 drivers/usb/host/ehci-marvell.c | 228 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 242 insertions(+), 6 deletions(-)

diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 58f276cdb4..8e46eca8a3 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -22,6 +22,13 @@ config USB_EHCI_ZYNQ
 	help
 	  Enable support for Zynq on-chip EHCI USB controller
 
+config USB_EHCI_MARVELL
+	bool "Marvell USB EHCI driver"
+	depends on ARCH_MVEBU
+	depends on USB_EHCI
+	help
+	  Enable support for Marvell USB EHCI controller
+
 config USB_OHCI
 	bool "OHCI driver"
 	depends on !MMU && HAS_DMA
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index cbddfbe923..5442d44e60 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -1,8 +1,9 @@
 # SPDX-License-Identifier: GPL-2.0-only
-obj-$(CONFIG_USB_EHCI)		+= ehci-hcd.o
-obj-$(CONFIG_USB_EHCI_OMAP)	+= ehci-omap.o
+obj-$(CONFIG_USB_EHCI)			+= ehci-hcd.o
+obj-$(CONFIG_USB_EHCI_OMAP)		+= ehci-omap.o
 obj-$(CONFIG_USB_EHCI_ATMEL)	+= ehci-atmel.o
-obj-$(CONFIG_USB_EHCI_ZYNQ)	+= ehci-zynq.o
-obj-$(CONFIG_USB_OHCI)		+= ohci-hcd.o
-obj-$(CONFIG_USB_OHCI_AT91)	+= ohci-at91.o
-obj-$(CONFIG_USB_XHCI)		+= xhci.o xhci-mem.o xhci-ring.o
+obj-$(CONFIG_USB_EHCI_ZYNQ)		+= ehci-zynq.o
+obj-$(CONFIG_USB_EHCI_MARVELL)	+= ehci-marvell.o
+obj-$(CONFIG_USB_OHCI)			+= ohci-hcd.o
+obj-$(CONFIG_USB_OHCI_AT91)		+= ohci-at91.o
+obj-$(CONFIG_USB_XHCI)			+= xhci.o xhci-mem.o xhci-ring.o
diff --git a/drivers/usb/host/ehci-marvell.c b/drivers/usb/host/ehci-marvell.c
new file mode 100644
index 0000000000..97123558d1
--- /dev/null
+++ b/drivers/usb/host/ehci-marvell.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Marvell Orion / Kirkwood / Armada EHCI host controller for barebox
+ *
+ * Ported from the modern U-Boot ehci-marvell.c implementation and
+ * adapted to the barebox ehci_register() model.
+ */
+
+/*#define DEBUG*/
+
+#include <common.h>
+#include <driver.h>
+#include <init.h>
+#include <io.h>
+#include <of.h>
+#include <linux/usb/usb.h>
+#include <linux/usb/ehci.h>
+#include <linux/mbus.h>
+
+#include "ehci.h"
+
+#define USB_WINDOW_CTRL(i)			(0x320 + ((i) << 4))
+#define USB_WINDOW_BASE(i)			(0x324 + ((i) << 4))
+#define USB_TARGET_DRAM				0x0
+
+#define USB2_SBUSCFG_OFF			0x90
+
+#define USB_SBUSCFG_BAWR_OFF		0x6
+#define USB_SBUSCFG_BARD_OFF		0x3
+#define USB_SBUSCFG_AHBBRST_OFF		0x0
+
+#define USB_SBUSCFG_BAWR_ALIGN_64B	0x4
+#define USB_SBUSCFG_BARD_ALIGN_64B	0x4
+#define USB_SBUSCFG_AHBBRST_INCR16	0x7
+
+/* AC5 special window mapping (taken from U-Boot) */
+#define USB_TO_DRAM_TARGET_ID		0x2
+#define USB_TO_DRAM_ATTR_ID			0x0
+#define USB_DRAM_BASE				0x00000000
+#define USB_DRAM_SIZE				0xfff	/* do not exceed the source address space */
+
+/*
+ * Private state for this Marvell EHCI instance.
+ * The driver only needs the pointer to struct ehci_host.
+ */
+struct marvell_ehci {
+	struct ehci_host *ehci;
+	void __iomem *base;
+	struct device *dev;
+	u32 dram_bus_base;
+};
+
+static void marvell_ehci_sbuscfg_fixup(void __iomem *base)
+{
+	/*
+	 * SBUSCFG: Control for the AMBA system bus interface:
+	 * BAWR = BARD = 4 : Align rd/wr bursts packets larger than 64 bytes
+	 * AHBBRST = 7	 : Align AHB burst for packets larger than 64 bytes
+	 */
+	writel((USB_SBUSCFG_BAWR_ALIGN_64B << USB_SBUSCFG_BAWR_OFF) |
+			(USB_SBUSCFG_BARD_ALIGN_64B << USB_SBUSCFG_BARD_OFF) |
+			(USB_SBUSCFG_AHBBRST_INCR16 << USB_SBUSCFG_AHBBRST_OFF),
+			base + USB2_SBUSCFG_OFF);
+}
+
+/*
+ * Program the USB → DRAM decoding windows using MBUS information.
+ * For AC5, replicate the special 0x0..USB_DRAM_SIZE mapping.
+ */
+static void marvell_usb_brg_adrdec_setup(struct marvell_ehci *priv)
+{
+	const struct mbus_dram_target_info *dram;
+	void __iomem *base = priv->base;
+	int i;
+
+	dev_info(priv->dev, "USB adrdec setup: ENTER\n");
+
+	dram = mvebu_mbus_dram_info();
+	if (!dram) {
+		dev_err(priv->dev, "no MBUS DRAM info\n");
+		return;
+	}
+
+	for (i = 0; i < 4; i++) {
+		writel(0, base + USB_WINDOW_CTRL(i));
+		writel(0, base + USB_WINDOW_BASE(i));
+	}
+
+	if (priv->dev->of_node &&
+		of_device_is_compatible(priv->dev->of_node, "marvell,ac5-ehci")) {
+		/* map DRAM as seen by USB at address 0x0 (as done in U-Boot) */
+		writel((USB_DRAM_SIZE << 16) |
+				(USB_TO_DRAM_ATTR_ID << 8) |
+				(USB_TO_DRAM_TARGET_ID << 4) | 1,
+				base + USB_WINDOW_CTRL(0));
+		writel(USB_DRAM_BASE, base + USB_WINDOW_BASE(0));
+
+		dev_dbg(priv->dev,
+			"AC5 decoding windows: ctrl=0x%08x base=0x%08x\n",
+			readl(base + USB_WINDOW_CTRL(0)),
+			readl(base + USB_WINDOW_BASE(0)));
+	} else {
+		/* generic mvebu case: one window per each DRAM chip-select */
+
+		for (i = 0; i < dram->num_cs; i++) {
+			const struct mbus_dram_window *cs = dram->cs + i;
+
+			/* Write size, attributes and target id to control register */
+			writel(((cs->size - 1) & 0xffff0000) |
+					(cs->mbus_attr << 8) |
+					(dram->mbus_dram_target_id << 4) | 1,
+					base + USB_WINDOW_CTRL(i));
+
+			/* Write base address to base register */
+			writel(cs->base, base + USB_WINDOW_BASE(i));
+		}
+	}
+
+	for (i = 0; i < 4; i++) {
+		u32 ctrl = readl(base + USB_WINDOW_CTRL(i));
+		u32 winb = readl(base + USB_WINDOW_BASE(i));
+		dev_info(priv->dev, "WIN%d: CTRL=0x%08x BASE=0x%08x\n", i, ctrl, winb);
+	}
+}
+
+/*
+ * Initialization hook called by ehci-hcd.c before starting the host.
+ * Only the bridge address decoding is performed here.
+ */
+static int marvell_ehci_init(void *drvdata)
+{
+	struct marvell_ehci *priv = drvdata;
+
+	marvell_usb_brg_adrdec_setup(priv);
+
+	writel((USB_SBUSCFG_BAWR_ALIGN_64B << USB_SBUSCFG_BAWR_OFF) |
+		(USB_SBUSCFG_BARD_ALIGN_64B << USB_SBUSCFG_BARD_OFF) |
+		(USB_SBUSCFG_AHBBRST_INCR16 << USB_SBUSCFG_AHBBRST_OFF),
+		priv->base + 0x100 + 0x90);
+	mdelay(50);
+
+	/*
+	* The SBUSCFG programming (BAWR/BARD/AHBBRST) in U-Boot is only
+	* required for SoCs without hlock (e.g. Armada3700).
+	* It is currently omitted; if needed in the future, a dedicated
+	* post_init or EHCI core hook can be added.
+	*/
+
+	dev_dbg(priv->dev, "marvell_ehci_init done\n");
+
+	return 0;
+}
+
+static int marvell_ehci_probe(struct device *dev)
+{
+	struct marvell_ehci *priv;
+	struct resource *iores;
+	struct ehci_data data = {};
+	void __iomem *base;
+	struct ehci_host *ehci;
+
+	priv = xzalloc(sizeof(*priv));
+	priv->dev = dev;
+	dev->priv = priv;
+
+	iores = dev_request_mem_resource(dev, 0);
+	if (IS_ERR(iores))
+		return PTR_ERR(iores);
+
+	base = IOMEM(iores->start);
+	priv->base = base;
+
+	/*
+	* On Marvell controllers the EHCI block starts at offset 0x100
+	* from the USB base address.
+	*/
+	data.hccr = base + 0x100;
+
+	u32 capbase = readl(data.hccr);			/* HC_CAPBASE */
+	u8 caplength = capbase & 0xff;			/* CAPLENGTH */
+
+	data.hcor = (void __iomem *)((u8 *)data.hccr + caplength);
+
+	dev_info(dev, "EHCI probe: base=%p hccr=%p hcor=%p\n",
+			base, data.hccr, data.hcor);
+
+	dev_info(dev, "EHCI CAPBASE=0x%08x CAPLENGTH=0x%02x\n",
+			capbase, caplength);
+
+	data.init = marvell_ehci_init;
+	data.drvdata = priv;
+
+	if (of_device_is_compatible(dev->of_node, "marvell,armada-3700-ehci"))
+		marvell_ehci_sbuscfg_fixup(base);
+
+	dev_dbg(dev, "marvell_ehci_probe: done\n");
+
+	ehci = ehci_register(dev, &data);
+	if (IS_ERR(ehci))
+		return PTR_ERR(ehci);
+
+	priv->ehci = ehci;
+
+	return 0;
+}
+
+static void marvell_ehci_remove(struct device *dev)
+{
+	struct marvell_ehci *priv = dev->priv;
+
+	if (priv && priv->ehci)
+		ehci_unregister(priv->ehci);
+}
+
+static const struct of_device_id marvell_ehci_dt_ids[] = {
+	{ .compatible = "marvell,orion-ehci" },
+	{ .compatible = "marvell,armada-3700-ehci" },
+	{ .compatible = "marvell,ac5-ehci" },
+	{ /* sentinel */ }
+};
+
+static struct driver marvell_ehci_driver = {
+	.name			= "marvell-ehci",
+	.probe			= marvell_ehci_probe,
+	.remove			= marvell_ehci_remove,
+	.of_compatible	= DRV_OF_COMPAT(marvell_ehci_dt_ids),
+};
+device_platform_driver(marvell_ehci_driver);

-- 
2.47.3