@@ -95,6 +95,15 @@ config USB_XHCI_FSL
depends on !SPL_NO_USB
help
Enables support for the on-chip xHCI controller on NXP Layerscape SoCs.
+
+config USB_XHCI_IMX8
+ bool "XHCI support for imx8"
+ depends on ARCH_IMX8
+ default y
+ help
+ Enables support for the on-chip xHCI controller on imx8qm and
+ imx8qxp SoCs.
+
endif # USB_XHCI_HCD
config USB_EHCI_HCD
@@ -55,6 +55,7 @@ obj-$(CONFIG_USB_XHCI_OMAP) += xhci-omap.o
obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o
obj-$(CONFIG_USB_XHCI_RCAR) += xhci-rcar.o
obj-$(CONFIG_USB_XHCI_STI) += dwc3-sti-glue.o
+obj-$(CONFIG_USB_XHCI_IMX8) += xhci-imx8.o
# designware
obj-$(CONFIG_USB_DWC2) += dwc2.o
new file mode 100644
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2019 NXP
+ *
+ * NXP i.MX8 USB HOST xHCI Controller (Cadence IP)
+ *
+ * Author: Peter Chen <peter.chen@nxp.com>
+ */
+
+#include <common.h>
+#include <usb.h>
+#include <malloc.h>
+#include <dm.h>
+#include <dm/device-internal.h>
+#include <clk.h>
+#include <power-domain.h>
+#include <generic-phy.h>
+#include <asm/arch/clock.h>
+#include <linux/compat.h>
+#include "xhci.h"
+
+/* Declare global data pointer */
+DECLARE_GLOBAL_DATA_PTR;
+
+/* Host registers */
+#define HCIVERSION_CAPLENGTH 0x10000
+#define USBSTS 0x10084
+
+/* None-core registers */
+#define USB3_CORE_CTRL1 0x00
+#define USB3_CORE_STATUS 0x0c
+#define USB3_SSPHY_STATUS 0x4c
+
+struct xhci_imx8_data {
+ void __iomem *usb3_ctrl_base;
+ void __iomem *usb3_core_base;
+ struct clk_bulk clks;
+ struct phy phy;
+};
+
+static struct xhci_imx8_data imx8_data;
+
+static void imx8_xhci_init(void)
+{
+ u32 tmp_data;
+ int timeout_us = 100000;
+
+ tmp_data = readl(imx8_data.usb3_ctrl_base + USB3_SSPHY_STATUS);
+ writel(tmp_data, imx8_data.usb3_ctrl_base + USB3_SSPHY_STATUS);
+ tmp_data = readl(imx8_data.usb3_ctrl_base + USB3_SSPHY_STATUS);
+ while ((tmp_data & 0xf0000000) != 0xf0000000 && timeout_us-- > 0) {
+ udelay(10);
+ tmp_data = readl(imx8_data.usb3_ctrl_base + USB3_SSPHY_STATUS);
+ }
+
+ if (timeout_us <= 0)
+ printf("clkvld is incorrect = 0x%x\n", tmp_data);
+
+ tmp_data = readl(imx8_data.usb3_ctrl_base + USB3_CORE_CTRL1);
+ tmp_data = (tmp_data & 0xfffffff8) | 0x202;
+ writel(tmp_data, imx8_data.usb3_ctrl_base + USB3_CORE_CTRL1);
+ clrbits_le32(imx8_data.usb3_ctrl_base + USB3_CORE_CTRL1, 1 << 26);
+ generic_phy_init(&imx8_data.phy);
+
+ /* clear all sw_rst */
+ clrbits_le32(imx8_data.usb3_ctrl_base + USB3_CORE_CTRL1, 0xFC << 24);
+
+ debug("wait xhci_power_on_ready\n");
+ tmp_data = readl(imx8_data.usb3_ctrl_base + USB3_CORE_STATUS);
+ timeout_us = 100000;
+ while (!(tmp_data & 0x1000) && timeout_us-- > 0) {
+ tmp_data = readl(imx8_data.usb3_ctrl_base + USB3_CORE_STATUS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ printf("wait xhci_power_on_ready timeout\n");
+ debug("xhci_power_on_ready\n");
+
+ tmp_data = readl(imx8_data.usb3_core_base + USBSTS);
+ debug("waiting CNR 0x%x\n", tmp_data);
+ timeout_us = 100000;
+ while (tmp_data & 0x800 && timeout_us-- > 0) {
+ tmp_data = readl(imx8_data.usb3_core_base + USBSTS);
+ udelay(1);
+ }
+
+ if (timeout_us <= 0)
+ printf("wait CNR timeout\n");
+ debug("check CNR has finished\n");
+}
+
+static void imx8_xhci_reset(void)
+{
+ /* Set CORE ctrl to default value, that all rst are hold */
+ writel(0xfc000001, imx8_data.usb3_ctrl_base + USB3_CORE_CTRL1);
+}
+
+static int xhci_imx8_clk_init(struct udevice *dev)
+{
+ int ret;
+
+ ret = clk_get_bulk(dev, &imx8_data.clks);
+ if (ret)
+ return ret;
+
+ ret = clk_enable_bulk(&imx8_data.clks);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int xhci_imx8_get_reg_addr(struct udevice *dev)
+{
+ imx8_data.usb3_ctrl_base =
+ (void __iomem *)devfdt_get_addr_index(dev, 0);
+ imx8_data.usb3_core_base =
+ (void __iomem *)devfdt_get_addr_index(dev, 4);
+
+ return 0;
+}
+
+static int xhci_imx8_probe(struct udevice *dev)
+{
+ struct xhci_hccr *hccr;
+ struct xhci_hcor *hcor;
+ struct udevice usbotg_dev;
+ struct power_domain pd;
+ int usbotg_off;
+ int ret = 0;
+ int len;
+
+ usbotg_off = fdtdec_lookup_phandle(gd->fdt_blob,
+ dev_of_offset(dev),
+ "cdns3,usb");
+ if (usbotg_off < 0)
+ return -EINVAL;
+ usbotg_dev.node = offset_to_ofnode(usbotg_off);
+ usbotg_dev.parent = dev->parent;
+ xhci_imx8_get_reg_addr(&usbotg_dev);
+
+#if CONFIG_IS_ENABLED(POWER_DOMAIN)
+ if (!power_domain_get(&usbotg_dev, &pd)) {
+ if (power_domain_on(&pd))
+ return -EINVAL;
+ }
+#endif
+
+ ret = generic_phy_get_by_index(&usbotg_dev, 0, &imx8_data.phy);
+ if (ret && ret != -ENOENT) {
+ printf("Failed to get USB PHY for %s\n", dev->name);
+ return ret;
+ }
+#if CONFIG_IS_ENABLED(CLK)
+ xhci_imx8_clk_init(&usbotg_dev);
+#endif
+
+ imx8_xhci_init();
+
+ hccr = (struct xhci_hccr *)(imx8_data.usb3_core_base +
+ HCIVERSION_CAPLENGTH);
+ len = HC_LENGTH(xhci_readl(&hccr->cr_capbase));
+ hcor = (struct xhci_hcor *)((uintptr_t)hccr + len);
+ printf("XHCI-imx8 init hccr 0x%p and hcor 0x%p hc_length %d\n",
+ (uint32_t *)hccr, (uint32_t *)hcor, len);
+
+ return xhci_register(dev, hccr, hcor);
+}
+
+static int xhci_imx8_remove(struct udevice *dev)
+{
+ int ret = xhci_deregister(dev);
+ if (!ret)
+ imx8_xhci_reset();
+
+#if CONFIG_IS_ENABLED(CLK)
+ clk_release_bulk(&imx8_data.clks);
+#endif
+ if (generic_phy_valid(&imx8_data.phy))
+ device_remove(imx8_data.phy.dev, DM_REMOVE_NORMAL);
+
+ return ret;
+}
+
+static const struct udevice_id xhci_usb_ids[] = {
+ { .compatible = "Cadence,usb3-host", },
+ { }
+};
+
+U_BOOT_DRIVER(xhci_imx8) = {
+ .name = "xhci_imx8",
+ .id = UCLASS_USB,
+ .of_match = xhci_usb_ids,
+ .probe = xhci_imx8_probe,
+ .remove = xhci_imx8_remove,
+ .ops = &xhci_usb_ops,
+ .platdata_auto_alloc_size = sizeof(struct usb_platdata),
+ .priv_auto_alloc_size = sizeof(struct xhci_ctrl),
+ .flags = DM_FLAG_ALLOC_PRIV_DMA,
+};