/* SPDX-License-Identifier: GPL-2.0-only */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ROOT_PORT_BDF 0x0 #define ATU_CTRL2 PCIE_ATU_UNR_REGION_CTRL2 static struct qcom_pcie_cntlr_t qcom_pcie_cfg; static inline void dw_pcie_dbi_rd_wr(bool enable) { uint32_t val; pcie_cntlr_cfg_t *pcierc = qcom_pcie_cfg.cntlr_cfg; val = read32(pcierc->dbi_base + PCIE_DBI_MISC_CONTROL_1_OFF); if (enable) val |= PCIE_DBI_RO_WR_EN; else val &= ~PCIE_DBI_RO_WR_EN; write32(pcierc->dbi_base + PCIE_DBI_MISC_CONTROL_1_OFF, val); } static void dw_pcie_writel_iatu(unsigned int index, uint32_t reg, uint32_t val) { pcie_cntlr_cfg_t *pcierc = qcom_pcie_cfg.cntlr_cfg; unsigned int offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index); write32(pcierc->atu_base + offset + reg, val); } static uint32_t dw_pcie_readl_iatu(unsigned int index, uint32_t reg) { pcie_cntlr_cfg_t *pcierc = qcom_pcie_cfg.cntlr_cfg; unsigned int offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index); return read32(pcierc->atu_base + offset + reg); } static void qcom_dw_pcie_prog_outbound_atu(unsigned int index, unsigned int type, uint64_t cpu_addr, uint64_t pcie_addr, uint32_t size) { dw_pcie_writel_iatu(index, PCIE_ATU_UNR_LOWER_BASE, lower_32_bits(cpu_addr)); dw_pcie_writel_iatu(index, PCIE_ATU_UNR_UPPER_BASE, upper_32_bits(cpu_addr)); dw_pcie_writel_iatu(index, PCIE_ATU_UNR_LIMIT, lower_32_bits(cpu_addr + size - 1)); dw_pcie_writel_iatu(index, PCIE_ATU_UNR_LOWER_TARGET, lower_32_bits(pcie_addr)); dw_pcie_writel_iatu(index, PCIE_ATU_UNR_UPPER_TARGET, upper_32_bits(pcie_addr)); dw_pcie_writel_iatu(index, PCIE_ATU_UNR_REGION_CTRL1, type); dw_pcie_writel_iatu(index, PCIE_ATU_UNR_REGION_CTRL2, PCIE_ATU_ENABLE); /* Ensure ATU enable takes effect before any subsequent config access */ if (retry(LINK_WAIT_MAX_IATU_RETRIES, (dw_pcie_readl_iatu(index, ATU_CTRL2) & PCIE_ATU_ENABLE), udelay(LINK_WAIT_IATU))) return; printk(BIOS_ERR, "PCIe o/b iATU couldn't be enabled after 5ms\n"); } static void qcom_pcie_configure_gpios(pcie_cntlr_cfg_t *cfg) { gpio_configure(cfg->perst, 0, GPIO_NO_PULL, GPIO_16MA, GPIO_OUTPUT); } /** * qcom_dw_pcie_configure() - Configure link capabilities and speed * * Configure the link capabilities and speed in the PCIe root complex. */ static void qcom_dw_pcie_configure(uint32_t cap_speed) { pcie_cntlr_cfg_t *pcierc = qcom_pcie_cfg.cntlr_cfg; dw_pcie_dbi_rd_wr(true); clrsetbits32(pcierc->dbi_base + PCIE_LINK_CAPABILITY, TARGET_LINK_SPEED_MASK, cap_speed); clrsetbits32(pcierc->dbi_base + PCIE_LINK_CTL_2, TARGET_LINK_SPEED_MASK, cap_speed); dw_pcie_dbi_rd_wr(false); printk(BIOS_INFO, "PCIe Link speed configured in Gen %d\n", cap_speed); } /** * is_pcie_link_up() - Return the link state * * Return: true for active link and false for no link */ static bool is_pcie_link_up(struct qcom_pcie_cntlr_t *pci) { /* Read link status register */ return !!(read32(pci->cntlr_cfg->elbi + PCIE3X2_ELBI_SYS_STTS) & XMLH_LINK_UP); } /** * wait_link_up() - Wait for the link to come up * * Return: true for active line and false for no link (timeout) */ static bool wait_link_up(struct qcom_pcie_cntlr_t *pci) { /* Check if the link is up or not */ if (retry(LINK_WAIT_MAX_RETRIES, is_pcie_link_up(pci), mdelay(PCIE_LINK_UP_MS))) return true; printk(BIOS_ERR, "PCIe link is not up even after 1sec\n"); return false; } static enum cb_err qcom_pcie_dw_link_up(struct qcom_pcie_cntlr_t *pcie) { if (is_pcie_link_up(pcie)) { printk(BIOS_INFO, "PCIe Link is already up\n"); return CB_SUCCESS; } /* DW pre link configurations */ qcom_dw_pcie_configure(LINK_SPEED_GEN_2); /* enable link training */ setbits32(pcie->cntlr_cfg->parf + PCIE_PARF_LTSSM, LTSSM_EN); /* Check that link was established */ if (wait_link_up(pcie)) { printk(BIOS_INFO, "PCIe link is up\n"); return CB_SUCCESS; } /* * Link can be established in Gen 1 as it failed to establish in Gen2. * So allow some time to do it. */ udelay(100); return CB_ERR; } /** * Returns root port config space address or endpoint config space address. * For endpoint config address, mapping would be done with ATU. */ static void *qcom_dw_pcie_get_config_addr(struct qcom_pcie_cntlr_t *pcierc, pci_devfn_t dev) { unsigned int atu_type, cfg_size; void *cfg_address; uint32_t bus, busdev, devfn; pcie_cntlr_cfg_t *cntlr = pcierc->cntlr_cfg; bus = PCI_DEV2SEGBUS(dev); devfn = PCI_DEV2DEVFN(dev); busdev = PCIE_ATU_BUS(bus) | PCIE_ATU_DEV(PCI_SLOT(devfn)) | PCIE_ATU_FUNC(PCI_FUNC(devfn)); /* Accessing root port configuration space */ if (!bus && !devfn) { cfg_address = pcierc->cntlr_cfg->dbi_base; return cfg_address; } /* For local bus use CFG0 type */ atu_type = (bus == 1) ? PCIE_ATU_TYPE_CFG0 : PCIE_ATU_TYPE_CFG1; cfg_address = cntlr->cfg_base + (cntlr->cfg_size * devfn); cfg_size = cntlr->cfg_size; qcom_dw_pcie_prog_outbound_atu(PCIE_ATU_REGION_INDEX1, atu_type, (uint64_t)cfg_address, busdev, cfg_size); return cfg_address; } static void qcom_dw_pcie_setup_rc(struct qcom_pcie_cntlr_t *pcie) { uint32_t val; pcie_cntlr_cfg_t *pcierc = pcie->cntlr_cfg; /* * Enable DBI read-only registers for writing/updating configuration. * Write permission gets disabled towards the end of this function. */ dw_pcie_dbi_rd_wr(true); val = read32(pcierc->dbi_base + PCIE_PORT_LINK_CONTROL); /* Set the number of lanes */ val &= ~PORT_LINK_MODE_MASK; switch (pcierc->lanes) { case 1: val |= PORT_LINK_MODE_1_LANES; break; case 2: val |= PORT_LINK_MODE_2_LANES; break; case 4: val |= PORT_LINK_MODE_4_LANES; break; case 8: val |= PORT_LINK_MODE_8_LANES; break; default: printk(BIOS_INFO, "PCIe num-lanes %u: invalid value\n", pcierc->lanes); return; } write32(pcierc->dbi_base + PCIE_PORT_LINK_CONTROL, val); /* Set link width speed control register */ val = read32(pcierc->dbi_base + PCIE_LINK_WIDTH_SPEED_CONTROL); val &= ~PORT_LOGIC_LINK_WIDTH_MASK; switch (pcierc->lanes) { case 1: val |= PORT_LOGIC_LINK_WIDTH_1_LANES; break; case 2: val |= PORT_LOGIC_LINK_WIDTH_2_LANES; break; case 4: val |= PORT_LOGIC_LINK_WIDTH_4_LANES; break; case 8: val |= PORT_LOGIC_LINK_WIDTH_8_LANES; break; default: printk(BIOS_INFO, "PCIe invalid lanes option\n"); return; } write32(pcierc->dbi_base + PCIE_LINK_WIDTH_SPEED_CONTROL, val); /* Setup bus numbers */ clrsetbits32(pcierc->dbi_base + PCI_PRIMARY_BUS, TYPE1_HDR_BUS_NUM_MASK, ROOT_PORT_BUS_NUM); /* Disable SMMU */ write32(pcierc->parf + PCIE_PARF_BDF_TO_SID_CFG, BDF_TO_SID_BYPASS); /* Configure ATU for outbound accesses */ qcom_dw_pcie_prog_outbound_atu(PCIE_ATU_REGION_INDEX0, PCIE_ATU_TYPE_MEM, pcierc->mem.phys_start, ROOT_PORT_BDF, pcierc->mem.size); /* Program correct class for RC */ write16(pcierc->dbi_base + PCI_CLASS_DEVICE, PCI_CLASS_BRIDGE_PCI); /* Disable write permission right after the update */ dw_pcie_dbi_rd_wr(false); } static void qcom_qmp_phy_config_lane(void *base, const struct qcom_qmp_phy_init_tbl tbl[], unsigned int num, uint8_t ln_mask) { unsigned int i; const struct qcom_qmp_phy_init_tbl *t = tbl; for (i = 0; i < num; i++, t++) { if (!(t->lane_mask & ln_mask)) continue; write32(base + t->offset, t->val); } } static void qcom_qmp_phy_configure(void *base, const struct qcom_qmp_phy_init_tbl tbl[], unsigned int num) { qcom_qmp_phy_config_lane(base, tbl, num, 0xff); } static enum cb_err qcom_qmp_phy_power_on(pcie_qmp_phy_cfg_t *qphy) { uint64_t lock_us; /* Release powerdown mode and allow endpoint refclk drive */ write32(qphy->pcs + QPHY_PCS_PWR_DWN_CNTRL, SW_PWRDN | REFCLK_DRV_DSBL); /* Serdes configuration */ qcom_qmp_phy_configure(qphy->serdes, qphy->serdes_tbl, qphy->serdes_tbl_num); /* Tx, Rx, and PCS configurations */ qcom_qmp_phy_config_lane(qphy->tx0, qphy->tx_tbl, qphy->tx_tbl_num, 1); qcom_qmp_phy_config_lane(qphy->tx0, qphy->tx_tbl_sec, qphy->tx_tbl_num_sec, 1); qcom_qmp_phy_config_lane(qphy->tx1, qphy->tx_tbl, qphy->tx_tbl_num, 2); qcom_qmp_phy_config_lane(qphy->tx1, qphy->tx_tbl_sec, qphy->tx_tbl_num_sec, 2); qcom_qmp_phy_config_lane(qphy->rx0, qphy->rx_tbl, qphy->rx_tbl_num, 1); qcom_qmp_phy_config_lane(qphy->rx0, qphy->rx_tbl_sec, qphy->rx_tbl_num_sec, 1); qcom_qmp_phy_config_lane(qphy->rx1, qphy->rx_tbl, qphy->rx_tbl_num, 2); qcom_qmp_phy_config_lane(qphy->rx1, qphy->rx_tbl_sec, qphy->rx_tbl_num_sec, 2); qcom_qmp_phy_configure(qphy->pcs, qphy->pcs_tbl, qphy->pcs_tbl_num); qcom_qmp_phy_configure(qphy->pcs, qphy->pcs_tbl_sec, qphy->pcs_tbl_num_sec); qcom_qmp_phy_configure(qphy->pcs_misc, qphy->pcs_misc_tbl, qphy->pcs_misc_tbl_num); qcom_qmp_phy_configure(qphy->pcs_misc, qphy->pcs_misc_tbl_sec, qphy->pcs_tbl_num_sec); /* Release software reset of PCS/Serdes */ clrbits32(qphy->pcs + QPHY_SW_RESET, SW_RESET); /* start PCS/Serdes to operation mode */ write32(qphy->pcs + QPHY_START_CTRL, PCS_START | SERDES_START); /* * Wait for PHY initialization to be done * PCS_STATUS: wait for 1ms for PHY STATUS bit to be set */ lock_us = wait_us(1000, !(read32(qphy->qmp_phy_base + QPHY_PCS_STATUS) & PHY_STATUS)); if (!lock_us) { printk(BIOS_ERR, "PCIe QMP PHY PLL failed to lock in 1ms\n"); return CB_ERR; } printk(BIOS_DEBUG, "PCIe QPHY Initialized took %lldus\n", lock_us); qcom_dw_pcie_enable_pipe_clock(); return CB_SUCCESS; } /* * Reset the PCIe PHY core. * Allow 100us delay to ensure reset is asserted. */ static void qcom_qmp_reset_phy(struct qcom_pcie_cntlr_t *pcie) { clock_reset_bcr(pcie->cntlr_cfg->qmp_phy_bcr, 1); udelay(100); clock_reset_bcr(pcie->cntlr_cfg->qmp_phy_bcr, 0); } /* * Reset the PCIe controller core. * Allow 100us delay to ensure reset is asserted. */ static void qcom_dw_pcie_reset_core(struct qcom_pcie_cntlr_t *pcie) { clock_reset_bcr(pcie->cntlr_cfg->pcie_bcr, 1); udelay(100); clock_reset_bcr(pcie->cntlr_cfg->pcie_bcr, 0); } static enum cb_err qcom_dw_pcie_enable(struct qcom_pcie_cntlr_t *pcie) { int ret; pcie_cntlr_cfg_t *pcierc = pcie->cntlr_cfg; pcie_qmp_phy_cfg_t *qmp_phy = pcie->qmp_phy_cfg; /* assert PCIe reset link to keep EP in reset */ gpio_set(pcierc->perst, 0); /* Enable PCIe controller and SoC specific clocks */ ret = qcom_dw_pcie_enable_clock(); if (ret) { printk(BIOS_ERR, "Enable PCIe clocks failed\n"); return ret; } /* Reset the controller */ qcom_dw_pcie_reset_core(pcie); /* configure PCIe to RC mode */ write32(pcierc->parf + PCIE_PARF_DEVICE_TYPE, DEVICE_TYPE_RC); /* Power on the PHY */ clrbits32(pcierc->parf + PCIE_PARF_PHY_CTRL, PHY_PWR_DOWN); /* MAC PHY_POWERDOWN MUX DISABLE */ clrbits32(pcierc->parf + PCIE_PARF_SYS_CTRL, MAC_PHY_PWRDOWN_MUX_EN); /* Bypass MHI as its not needed */ setbits32(pcierc->parf + PCIE_PARF_MHI_CLOCK_RESET_CTRL, MHI_BYPASS); qcom_qmp_reset_phy(pcie); /* Initialize QMP PHY */ ret = qcom_qmp_phy_power_on(qmp_phy); if (ret) { printk(BIOS_ERR, "PCIe PHY init failed\n"); return ret; } qcom_dw_pcie_setup_rc(pcie); /* de-assert PCIe reset link to bring EP out of reset */ gpio_set(pcierc->perst, 1); /* establisth the link */ ret = qcom_pcie_dw_link_up(pcie); if (ret) { printk(BIOS_ERR, "PCIe Link init failed\n"); return ret; } return CB_SUCCESS; } /** * Fill coreboot table with PCIe info. * It allows exporting this info to payloads. */ enum cb_err fill_lb_pcie(struct lb_pcie *pcie) { if (!mainboard_needs_pcie_init()) return CB_ERR_NOT_IMPLEMENTED; pcie_cntlr_cfg_t *pcierc = qcom_pcie_cfg.cntlr_cfg; pcie->ctrl_base = (uintptr_t)pcierc->dbi_base; return CB_SUCCESS; } /* map_bus function for mapping pcie_s_{read/write}_configXX() functions */ volatile union pci_bank *pci_map_bus(pci_devfn_t dev) { void *config_addr = NULL; config_addr = qcom_dw_pcie_get_config_addr(&qcom_pcie_cfg, dev); return (void *)config_addr; } /* PCI domain ops read_resources callback */ void qcom_pci_domain_read_resources(struct device *dev) { struct resource *res; pcie_cntlr_cfg_t *pcierc = qcom_pcie_cfg.cntlr_cfg; /* Initialize the system-wide I/O space constraints. */ res = new_resource(dev, IOINDEX_SUBTRACTIVE(0, 0)); res->base = pcierc->io.phys_start; res->limit = pcierc->io.size - 1; res->flags = IORESOURCE_IO | IORESOURCE_SUBTRACTIVE | IORESOURCE_ASSIGNED; /* Initialize the system-wide memory resources constraints. */ res = new_resource(dev, IOINDEX_SUBTRACTIVE(1, 0)); res->base = pcierc->mem.phys_start; res->limit = pcierc->mem.size - 1; res->flags = IORESOURCE_MEM | IORESOURCE_SUBTRACTIVE | IORESOURCE_ASSIGNED; } /* PCI domain ops enable callback */ void qcom_setup_pcie_host(struct device *dev) { gcom_pcie_get_config(&qcom_pcie_cfg); /* Ensure PCIe endpoints are powered-on before initiating PCIe link training */ gcom_pcie_power_on_ep(); printk(BIOS_INFO, "Setup PCIe in RC mode\n"); /* Configure PERST gpio */ qcom_pcie_configure_gpios(qcom_pcie_cfg.cntlr_cfg); if (!qcom_dw_pcie_enable(&qcom_pcie_cfg)) printk(BIOS_NOTICE, "PCIe enumerated succussfully..\n"); else printk(BIOS_EMERG, "Failed to enable PCIe\n"); }