diff mbox series

[v12,4/8] drivers: pci: add pcie support for fu740

Message ID 20210527135215.370524-5-green.wan@sifive.com
State Accepted
Commit 416395c772018c6bf52aad36aca163115001793f
Delegated to: Andes
Headers show
Series Add FU740 chip and HiFive Unmatched board support | expand

Commit Message

Green Wan May 27, 2021, 1:52 p.m. UTC
Add pcie driver for SiFive fu740, the driver depends on
fu740 gpio, clk and reset driver to do init. Force running at Gen1
for better capatible enumeration.

Several devices are tested:
a) M.2 NVMe SSD
b) USB-to-PCI adapter
c) Ethernet adapter (E1000 compatible)

Signed-off-by: Green Wan <green.wan@sifive.com>
Reviewed-by: Neil Armstrong <narmstrong@baylibre.com>
---
 drivers/pci/Kconfig          |  10 +
 drivers/pci/Makefile         |   1 +
 drivers/pci/pcie_dw_sifive.c | 507 +++++++++++++++++++++++++++++++++++
 3 files changed, 518 insertions(+)
 create mode 100644 drivers/pci/pcie_dw_sifive.c
diff mbox series

Patch

diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index d5b6018b3d..b2b7b253f8 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -97,6 +97,16 @@  config PCIE_DW_MVEBU
 	  Armada-8K SoCs. The PCIe controller on Armada-8K is based on
 	  DesignWare hardware.
 
+config PCIE_DW_SIFIVE
+	bool "Enable SiFive FU740 PCIe"
+	depends on CLK_SIFIVE_PRCI
+	depends on RESET_SIFIVE
+	depends on SIFIVE_GPIO
+	select PCIE_DW_COMMON
+	help
+	  Say Y here if you want to enable PCIe controller support on
+	  FU740.
+
 config PCIE_FSL
 	bool "FSL PowerPC PCIe support"
 	depends on DM_PCI
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 1f741786a0..c742bb2c94 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -54,3 +54,4 @@  obj-$(CONFIG_PCIE_DW_MESON) += pcie_dw_meson.o
 obj-$(CONFIG_PCI_BRCMSTB) += pcie_brcmstb.o
 obj-$(CONFIG_PCI_OCTEONTX) += pci_octeontx.o
 obj-$(CONFIG_PCIE_OCTEON) += pcie_octeon.o
+obj-$(CONFIG_PCIE_DW_SIFIVE) += pcie_dw_sifive.o
diff --git a/drivers/pci/pcie_dw_sifive.c b/drivers/pci/pcie_dw_sifive.c
new file mode 100644
index 0000000000..fac3f18237
--- /dev/null
+++ b/drivers/pci/pcie_dw_sifive.c
@@ -0,0 +1,507 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * SiFive FU740 DesignWare PCIe Controller
+ *
+ * Copyright (C) 2020-2021 SiFive, Inc.
+ *
+ * Based in early part on the i.MX6 PCIe host controller shim which is:
+ *
+ * Copyright (C) 2013 Kosagi
+ *		http://www.kosagi.com
+ *
+ * Based on driver from author: Alan Mikhak <amikhak@wirelessfabric.com>
+ */
+#include <asm/io.h>
+#include <asm-generic/gpio.h>
+#include <clk.h>
+#include <common.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <generic-phy.h>
+#include <linux/bitops.h>
+#include <linux/log2.h>
+#include <pci.h>
+#include <pci_ep.h>
+#include <pci_ids.h>
+#include <regmap.h>
+#include <reset.h>
+#include <syscon.h>
+
+#include "pcie_dw_common.h"
+
+struct pcie_sifive {
+	/* Must be first member of the struct */
+	struct pcie_dw dw;
+
+	/* private control regs */
+	void __iomem *priv_base;
+
+	/* reset, power, clock resources */
+	int sys_int_pin;
+	struct gpio_desc pwren_gpio;
+	struct gpio_desc reset_gpio;
+	struct clk aux_ck;
+	struct reset_ctl reset;
+};
+
+enum pcie_sifive_devtype {
+	SV_PCIE_UNKNOWN_TYPE = 0,
+	SV_PCIE_ENDPOINT_TYPE = 1,
+	SV_PCIE_HOST_TYPE = 3
+};
+
+#define ASSERTION_DELAY		100
+#define PCIE_PERST_ASSERT	0x0
+#define PCIE_PERST_DEASSERT	0x1
+#define PCIE_PHY_RESET		0x1
+#define PCIE_PHY_RESET_DEASSERT	0x0
+#define GPIO_LOW		0x0
+#define GPIO_HIGH		0x1
+#define PCIE_PHY_SEL		0x1
+
+#define sv_info(sv, fmt, arg...)	printf(fmt, ## arg)
+#define sv_warn(sv, fmt, arg...)	printf(fmt, ## arg)
+#define sv_debug(sv, fmt, arg...)	debug(fmt, ## arg)
+#define sv_err(sv, fmt, arg...)		printf(fmt, ## arg)
+
+/* Doorbell Interface */
+#define DBI_OFFSET			0x0
+#define DBI_SIZE			0x1000
+
+#define PL_OFFSET			0x700
+
+#define PHY_DEBUG_R0			(PL_OFFSET + 0x28)
+
+#define PHY_DEBUG_R1			(PL_OFFSET + 0x2c)
+#define PHY_DEBUG_R1_LINK_UP		(0x1 << 4)
+#define PHY_DEBUG_R1_LINK_IN_TRAINING	(0x1 << 29)
+
+#define PCIE_MISC_CONTROL_1		0x8bc
+#define DBI_RO_WR_EN			BIT(0)
+
+/* pcie reset */
+#define PCIEX8MGMT_PERST_N		0x0
+
+/* LTSSM */
+#define PCIEX8MGMT_APP_LTSSM_ENABLE	0x10
+#define LTSSM_ENABLE_BIT		BIT(0)
+
+/* phy reset */
+#define PCIEX8MGMT_APP_HOLD_PHY_RST	0x18
+
+/* device type */
+#define PCIEX8MGMT_DEVICE_TYPE		0x708
+#define DEVICE_TYPE_EP			0x0
+#define DEVICE_TYPE_RC			0x4
+
+/* phy control registers*/
+#define PCIEX8MGMT_PHY0_CR_PARA_ADDR	0x860
+#define PCIEX8MGMT_PHY0_CR_PARA_RD_EN	0x870
+#define PCIEX8MGMT_PHY0_CR_PARA_RD_DATA	0x878
+#define PCIEX8MGMT_PHY0_CR_PARA_SEL	0x880
+#define PCIEX8MGMT_PHY0_CR_PARA_WR_DATA	0x888
+#define PCIEX8MGMT_PHY0_CR_PARA_WR_EN	0x890
+#define PCIEX8MGMT_PHY0_CR_PARA_ACK	0x898
+#define PCIEX8MGMT_PHY1_CR_PARA_ADDR	0x8a0
+#define PCIEX8MGMT_PHY1_CR_PARA_RD_EN	0x8b0
+#define PCIEX8MGMT_PHY1_CR_PARA_RD_DATA	0x8b8
+#define PCIEX8MGMT_PHY1_CR_PARA_SEL	0x8c0
+#define PCIEX8MGMT_PHY1_CR_PARA_WR_DATA	0x8c8
+#define PCIEX8MGMT_PHY1_CR_PARA_WR_EN	0x8d0
+#define PCIEX8MGMT_PHY1_CR_PARA_ACK	0x8d8
+
+#define PCIEX8MGMT_LANE_NUM		8
+#define PCIEX8MGMT_LANE			0x1008
+#define PCIEX8MGMT_LANE_OFF		0x100
+#define PCIEX8MGMT_TERM_MODE		0x0e21
+
+#define PCIE_CAP_BASE			0x70
+#define PCI_CONFIG(r)			(DBI_OFFSET + (r))
+#define PCIE_CAPABILITIES(r)		PCI_CONFIG(PCIE_CAP_BASE + (r))
+
+/* Link capability */
+#define PF0_PCIE_CAP_LINK_CAP		PCIE_CAPABILITIES(0xc)
+#define PCIE_LINK_CAP_MAX_SPEED_MASK	0xf
+#define PCIE_LINK_CAP_MAX_SPEED_GEN1	BIT(0)
+#define PCIE_LINK_CAP_MAX_SPEED_GEN2	BIT(1)
+#define PCIE_LINK_CAP_MAX_SPEED_GEN3	BIT(2)
+#define PCIE_LINK_CAP_MAX_SPEED_GEN4	BIT(3)
+
+static enum pcie_sifive_devtype pcie_sifive_get_devtype(struct pcie_sifive *sv)
+{
+	u32 val;
+
+	val = readl(sv->priv_base + PCIEX8MGMT_DEVICE_TYPE);
+	switch (val) {
+	case DEVICE_TYPE_RC:
+		return SV_PCIE_HOST_TYPE;
+	case DEVICE_TYPE_EP:
+		return SV_PCIE_ENDPOINT_TYPE;
+	default:
+		return SV_PCIE_UNKNOWN_TYPE;
+	}
+}
+
+static void pcie_sifive_priv_set_state(struct pcie_sifive *sv, u32 reg,
+				       u32 bits, int state)
+{
+	u32 val;
+
+	val = readl(sv->priv_base + reg);
+	val = state ? (val | bits) : (val & !bits);
+	writel(val, sv->priv_base + reg);
+}
+
+static void pcie_sifive_assert_reset(struct pcie_sifive *sv)
+{
+	dm_gpio_set_value(&sv->reset_gpio, GPIO_LOW);
+	writel(PCIE_PERST_ASSERT, sv->priv_base + PCIEX8MGMT_PERST_N);
+	mdelay(ASSERTION_DELAY);
+}
+
+static void pcie_sifive_power_on(struct pcie_sifive *sv)
+{
+	dm_gpio_set_value(&sv->pwren_gpio, GPIO_HIGH);
+	mdelay(ASSERTION_DELAY);
+}
+
+static void pcie_sifive_deassert_reset(struct pcie_sifive *sv)
+{
+	writel(PCIE_PERST_DEASSERT, sv->priv_base + PCIEX8MGMT_PERST_N);
+	dm_gpio_set_value(&sv->reset_gpio, GPIO_HIGH);
+	mdelay(ASSERTION_DELAY);
+}
+
+static int pcie_sifive_setphy(const u8 phy, const u8 write,
+			      const u16 addr, const u16 wrdata,
+			      u16 *rddata, struct pcie_sifive *sv)
+{
+	unsigned char ack = 0;
+
+	if (!(phy == 0 || phy == 1))
+		return -2;
+
+	/* setup phy para */
+	writel(addr, sv->priv_base +
+	       (phy ? PCIEX8MGMT_PHY1_CR_PARA_ADDR :
+		PCIEX8MGMT_PHY0_CR_PARA_ADDR));
+
+	if (write)
+		writel(wrdata, sv->priv_base +
+		       (phy ? PCIEX8MGMT_PHY1_CR_PARA_WR_DATA :
+			PCIEX8MGMT_PHY0_CR_PARA_WR_DATA));
+
+	/* enable access if write */
+	if (write)
+		writel(1, sv->priv_base +
+		       (phy ? PCIEX8MGMT_PHY1_CR_PARA_WR_EN :
+			PCIEX8MGMT_PHY0_CR_PARA_WR_EN));
+	else
+		writel(1, sv->priv_base +
+		       (phy ? PCIEX8MGMT_PHY1_CR_PARA_RD_EN :
+			PCIEX8MGMT_PHY0_CR_PARA_RD_EN));
+
+	/* wait for wait_idle */
+	do {
+		u32 val;
+
+		val = readl(sv->priv_base +
+			    (phy ? PCIEX8MGMT_PHY1_CR_PARA_ACK :
+			     PCIEX8MGMT_PHY0_CR_PARA_ACK));
+		if (val) {
+			ack = 1;
+			if (!write)
+				readl(sv->priv_base +
+				      (phy ? PCIEX8MGMT_PHY1_CR_PARA_RD_DATA :
+				       PCIEX8MGMT_PHY0_CR_PARA_RD_DATA));
+			mdelay(1);
+		}
+	} while (!ack);
+
+	/* clear */
+	if (write)
+		writel(0, sv->priv_base +
+		       (phy ? PCIEX8MGMT_PHY1_CR_PARA_WR_EN :
+			PCIEX8MGMT_PHY0_CR_PARA_WR_EN));
+	else
+		writel(0, sv->priv_base +
+		       (phy ? PCIEX8MGMT_PHY1_CR_PARA_RD_EN :
+			PCIEX8MGMT_PHY0_CR_PARA_RD_EN));
+
+	while (readl(sv->priv_base +
+		     (phy ? PCIEX8MGMT_PHY1_CR_PARA_ACK :
+		      PCIEX8MGMT_PHY0_CR_PARA_ACK))) {
+		/* wait for ~wait_idle */
+	}
+
+	return 0;
+}
+
+static void pcie_sifive_init_phy(struct pcie_sifive *sv)
+{
+	int lane;
+
+	/* enable phy cr_para_sel interfaces */
+	writel(PCIE_PHY_SEL, sv->priv_base + PCIEX8MGMT_PHY0_CR_PARA_SEL);
+	writel(PCIE_PHY_SEL, sv->priv_base + PCIEX8MGMT_PHY1_CR_PARA_SEL);
+	mdelay(1);
+
+	/* set PHY AC termination mode */
+	for (lane = 0; lane < PCIEX8MGMT_LANE_NUM; lane++) {
+		pcie_sifive_setphy(0, 1,
+				   PCIEX8MGMT_LANE +
+				   (PCIEX8MGMT_LANE_OFF * lane),
+				   PCIEX8MGMT_TERM_MODE, NULL, sv);
+		pcie_sifive_setphy(1, 1,
+				   PCIEX8MGMT_LANE +
+				   (PCIEX8MGMT_LANE_OFF * lane),
+				   PCIEX8MGMT_TERM_MODE, NULL, sv);
+	}
+}
+
+static int pcie_sifive_check_link(struct pcie_sifive *sv)
+{
+	u32 val;
+
+	val = readl(sv->dw.dbi_base + PHY_DEBUG_R1);
+	return (val & PHY_DEBUG_R1_LINK_UP) &&
+		!(val & PHY_DEBUG_R1_LINK_IN_TRAINING);
+}
+
+static void pcie_sifive_force_gen1(struct pcie_sifive *sv)
+{
+	u32 val, linkcap;
+
+	/*
+	 * Force Gen1 operation when starting the link. In case the link is
+	 * started in Gen2 mode, there is a possibility the devices on the
+	 * bus will not be detected at all. This happens with PCIe switches.
+	 */
+
+	/* ctrl_ro_wr_enable */
+	val = readl(sv->dw.dbi_base + PCIE_MISC_CONTROL_1);
+	val |= DBI_RO_WR_EN;
+	writel(val, sv->dw.dbi_base + PCIE_MISC_CONTROL_1);
+
+	/* configure link cap */
+	linkcap = readl(sv->dw.dbi_base + PF0_PCIE_CAP_LINK_CAP);
+	linkcap |= PCIE_LINK_CAP_MAX_SPEED_MASK;
+	writel(linkcap, sv->dw.dbi_base + PF0_PCIE_CAP_LINK_CAP);
+
+	/* ctrl_ro_wr_disable */
+	val &= ~DBI_RO_WR_EN;
+	writel(val, sv->dw.dbi_base + PCIE_MISC_CONTROL_1);
+}
+
+static void pcie_sifive_print_phy_debug(struct pcie_sifive *sv)
+{
+	sv_err(sv, "PHY DEBUG_R0=0x%08x DEBUG_R1=0x%08x\n",
+	       readl(sv->dw.dbi_base + PHY_DEBUG_R0),
+	       readl(sv->dw.dbi_base + PHY_DEBUG_R1));
+}
+
+static int pcie_sifive_wait_for_link(struct pcie_sifive *sv)
+{
+	u32 val;
+	int timeout;
+
+	/* Wait for the link to train */
+	mdelay(20);
+	timeout = 20;
+
+	do {
+		mdelay(1);
+	} while (--timeout && !pcie_sifive_check_link(sv));
+
+	val = readl(sv->dw.dbi_base + PHY_DEBUG_R1);
+	if (!(val & PHY_DEBUG_R1_LINK_UP) ||
+	    (val & PHY_DEBUG_R1_LINK_IN_TRAINING)) {
+		sv_info(sv, "Failed to negotiate PCIe link!\n");
+		pcie_sifive_print_phy_debug(sv);
+		writel(PCIE_PHY_RESET,
+		       sv->priv_base + PCIEX8MGMT_APP_HOLD_PHY_RST);
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static int pcie_sifive_start_link(struct pcie_sifive *sv)
+{
+	if (pcie_sifive_check_link(sv))
+		return -EALREADY;
+
+	pcie_sifive_force_gen1(sv);
+
+	/* set ltssm */
+	pcie_sifive_priv_set_state(sv, PCIEX8MGMT_APP_LTSSM_ENABLE,
+				   LTSSM_ENABLE_BIT, 1);
+	return 0;
+}
+
+static int pcie_sifive_init_port(struct udevice *dev,
+				 enum pcie_sifive_devtype mode)
+{
+	struct pcie_sifive *sv = dev_get_priv(dev);
+	int ret;
+
+	/* Power on reset */
+	pcie_sifive_assert_reset(sv);
+	pcie_sifive_power_on(sv);
+	pcie_sifive_deassert_reset(sv);
+
+	/* Enable pcieauxclk */
+	ret = clk_enable(&sv->aux_ck);
+	if (ret)
+		dev_err(dev, "unable to enable pcie_aux clock\n");
+
+	/*
+	 * assert hold_phy_rst (hold the controller LTSSM in reset
+	 * after power_up_rst_n for register programming with cr_para)
+	 */
+	writel(PCIE_PHY_RESET, sv->priv_base + PCIEX8MGMT_APP_HOLD_PHY_RST);
+
+	/* deassert power_up_rst_n */
+	ret = reset_deassert(&sv->reset);
+	if (ret < 0) {
+		dev_err(dev, "failed to deassert reset");
+		return -EINVAL;
+	}
+
+	pcie_sifive_init_phy(sv);
+
+	/* disable pcieauxclk */
+	clk_disable(&sv->aux_ck);
+
+	/* deassert hold_phy_rst */
+	writel(PCIE_PHY_RESET_DEASSERT,
+	       sv->priv_base + PCIEX8MGMT_APP_HOLD_PHY_RST);
+
+	/* enable pcieauxclk */
+	clk_enable(&sv->aux_ck);
+
+	/* Set desired mode while core is not operational */
+	if (mode == SV_PCIE_HOST_TYPE)
+		writel(DEVICE_TYPE_RC,
+		       sv->priv_base + PCIEX8MGMT_DEVICE_TYPE);
+	else
+		writel(DEVICE_TYPE_EP,
+		       sv->priv_base + PCIEX8MGMT_DEVICE_TYPE);
+
+	/* Confirm desired mode from operational core */
+	if (pcie_sifive_get_devtype(sv) != mode)
+		return -EINVAL;
+
+	pcie_dw_setup_host(&sv->dw);
+
+	if (pcie_sifive_start_link(sv) == -EALREADY)
+		sv_info(sv, "PCIe link is already up\n");
+	else if (pcie_sifive_wait_for_link(sv) == -ETIMEDOUT)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int pcie_sifive_probe(struct udevice *dev)
+{
+	struct pcie_sifive *sv = dev_get_priv(dev);
+	struct udevice *parent = pci_get_controller(dev);
+	struct pci_controller *hose = dev_get_uclass_priv(parent);
+	int err;
+
+	sv->dw.first_busno = dev_seq(dev);
+	sv->dw.dev = dev;
+
+	err = pcie_sifive_init_port(dev, SV_PCIE_HOST_TYPE);
+	if (err) {
+		sv_info(sv, "Failed to init port.\n");
+		return err;
+	}
+
+	printf("PCIE-%d: Link up (Gen%d-x%d, Bus%d)\n",
+	       dev_seq(dev), pcie_dw_get_link_speed(&sv->dw),
+	       pcie_dw_get_link_width(&sv->dw),
+	       hose->first_busno);
+
+	return pcie_dw_prog_outbound_atu_unroll(&sv->dw,
+						PCIE_ATU_REGION_INDEX0,
+						PCIE_ATU_TYPE_MEM,
+						sv->dw.mem.phys_start,
+						sv->dw.mem.bus_start,
+						sv->dw.mem.size);
+}
+
+static void __iomem *get_fdt_addr(struct udevice *dev, const char *name)
+{
+	fdt_addr_t addr;
+
+	addr = dev_read_addr_name(dev, name);
+
+	return (addr == FDT_ADDR_T_NONE) ? NULL : (void __iomem *)addr;
+}
+
+static int pcie_sifive_of_to_plat(struct udevice *dev)
+{
+	struct pcie_sifive *sv = dev_get_priv(dev);
+	int err;
+
+	/* get designware DBI base addr */
+	sv->dw.dbi_base = get_fdt_addr(dev, "dbi");
+	if (!sv->dw.dbi_base)
+		return -EINVAL;
+
+	/* get private control base addr */
+	sv->priv_base = get_fdt_addr(dev, "mgmt");
+	if (!sv->priv_base)
+		return -EINVAL;
+
+	gpio_request_by_name(dev, "pwren-gpios", 0, &sv->pwren_gpio,
+			     GPIOD_IS_OUT);
+
+	if (!dm_gpio_is_valid(&sv->pwren_gpio)) {
+		sv_info(sv, "pwren_gpio is invalid\n");
+		return -EINVAL;
+	}
+
+	gpio_request_by_name(dev, "reset-gpios", 0, &sv->reset_gpio,
+			     GPIOD_IS_OUT);
+
+	if (!dm_gpio_is_valid(&sv->reset_gpio)) {
+		sv_info(sv, "reset_gpio is invalid\n");
+		return -EINVAL;
+	}
+
+	err = clk_get_by_index(dev, 0, &sv->aux_ck);
+	if (err) {
+		sv_info(sv, "clk_get_by_index(aux_ck) failed: %d\n", err);
+		return err;
+	}
+
+	err = reset_get_by_index(dev, 0, &sv->reset);
+	if (err) {
+		sv_info(sv, "reset_get_by_index(reset) failed: %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static const struct dm_pci_ops pcie_sifive_ops = {
+	.read_config	= pcie_dw_read_config,
+	.write_config	= pcie_dw_write_config,
+};
+
+static const struct udevice_id pcie_sifive_ids[] = {
+	{ .compatible = "sifive,fu740-pcie" },
+	{}
+};
+
+U_BOOT_DRIVER(pcie_sifive) = {
+	.name		= "pcie_sifive",
+	.id		= UCLASS_PCI,
+	.of_match	= pcie_sifive_ids,
+	.ops		= &pcie_sifive_ops,
+	.of_to_plat	= pcie_sifive_of_to_plat,
+	.probe		= pcie_sifive_probe,
+	.priv_auto	= sizeof(struct pcie_sifive),
+};