drivers: pci: add pcie support for fu740
authorGreen Wan <green.wan@sifive.com>
Thu, 27 May 2021 13:52:10 +0000 (06:52 -0700)
committerLeo Yu-Chi Liang <ycliang@andestech.com>
Mon, 31 May 2021 08:35:54 +0000 (16:35 +0800)
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
drivers/pci/Makefile
drivers/pci/pcie_dw_sifive.c [new file with mode: 0644]

index d5b6018..b2b7b25 100644 (file)
@@ -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
index 1f74178..c742bb2 100644 (file)
@@ -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 (file)
index 0000000..fac3f18
--- /dev/null
@@ -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),
+};