On Mon, May 3, 2021 at 9:08 PM Neil Armstrong <narmstr...@baylibre.com> wrote: > > On 22/04/2021 11:11, Green Wan wrote: > > 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....@sifive.com> > > --- > > drivers/pci/Kconfig | 10 + > > drivers/pci/Makefile | 1 + > > drivers/pci/pcie_dw_sifive.c | 508 +++++++++++++++++++++++++++++++++++ > > 3 files changed, 519 insertions(+) > > create mode 100644 drivers/pci/pcie_dw_sifive.c > > > > diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig > > index cdcdd8f456..b11dd5c02d 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 96d61821fe..7351f8027d 100644 > > --- a/drivers/pci/Makefile > > +++ b/drivers/pci/Makefile > > @@ -53,3 +53,4 @@ obj-$(CONFIG_PCIE_DW_ROCKCHIP) += pcie_dw_rockchip.o > > 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_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..599eb0ece2 > > --- /dev/null > > +++ b/drivers/pci/pcie_dw_sifive.c > > @@ -0,0 +1,508 @@ > > +// 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 <amik...@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" }, > > + { .compatible = "sifive,fu740-pcie-ecam" }, > > Are you sure the controller in ECAM mode should use this driver ? > The Linux bindings only declare "sifive,fu740-pcie" >
You are right. I should remove it. Thanks. - Green > > + {} > > +}; > > + > > +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), > > +}; > > > > otherwise, > > Reviewed-by: Neil Armstrong <narmstr...@baylibre.com> > > Neil