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

Luca Lauro <[email protected]>
Newsgroups org.kernel.feeds.b4-sent,org.infradead.lists.barebox
Message-ID <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.