// 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), };