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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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), +};