[PATCH 16/19] usb: ehci: add Marvell EHCI host controller driver

Luca Lauro via B4 Relay devnull+famlauro93l.gmail.com at kernel.org
Thu Jul 23 06:57:54 PDT 2026


From: Luca Lauro <famlauro93l at gmail.com>

---
 drivers/usb/host/Kconfig        |   7 ++
 drivers/usb/host/Makefile       |  13 +--
 drivers/usb/host/ehci-marvell.c | 229 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 243 insertions(+), 6 deletions(-)

diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 58f276cdb4..21bf876d69 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..932581d691 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..8accfd697d
--- /dev/null
+++ b/drivers/usb/host/ehci-marvell.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Marvell Orion / Kirkwood / Armada EHCI host for barebox
+ *
+ * Porting basato su ehci-marvell.c di U-Boot moderno,
+ * adattato al modello ehci_register() di barebox.
+ */
+/*#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 (copiato da 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   /* non oltrepassare lo spazio sorgente */
+
+/*
+ * Stato privato per questa istanza EHCI Marvell.
+ * Non serve conoscere struct ehci_host: basta il puntatore.
+ */
+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);
+}
+
+/*
+ * Programma le decoding windows USB -> DRAM usando le info MBUS.
+ * Per AC5 replica lo speciale mapping 0x0..USB_DRAM_SIZE.
+ */
+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")) {
+        /* mappa DRAM vista da USB a 0x0 (come 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 {
+        /* caso generico mvebu: una window per ciascun CS DRAM */
+
+        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);
+    }
+}
+
+/*
+ * Hook di init chiamato da ehci-hcd.c prima di avviare l'host.
+ * Qui facciamo solo la parte “bridge address decoding”.
+ */
+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);
+
+    /*
+     * La programmazione di SBUSCFG (BAWR/BARD/AHBBRST) in U-Boot
+     * serve solo per SoC senza hlock (Armada3700, ecc.).
+     * Per ora la lasciamo fuori: se in futuro servirà, possiamo
+     * aggiungere un post_init o un hook dedicato nel core EHCI.
+     */
+
+    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;
+
+    memset(&data, 0, sizeof(data));
+
+    /*
+     * Sui controller Marvell il blocco EHCI inizia a offset 0x100
+     * rispetto alla base USB.
+     */
+    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





More information about the barebox mailing list