/* * Copyright 2020 Broadcom * * SPDX-License-Identifier: Apache-2.0 */ #include #define LOG_LEVEL CONFIG_PCIE_EP_LOG_LEVEL #include LOG_MODULE_REGISTER(iproc_pcie); #include #include "pcie_ep_bcm_iproc.h" #include "pcie_ep_bcm_iproc_regs.h" #define DT_DRV_COMPAT brcm_iproc_pcie_ep /* Helper macro to read 64-bit data using two 32-bit data read */ #define sys_read64(addr) (((uint64_t)(sys_read32(addr + 4)) << 32) | \ sys_read32(addr)) static int iproc_pcie_conf_read(const struct device *dev, uint32_t offset, uint32_t *data) { const struct iproc_pcie_ep_config *cfg = dev->config; /* Write offset to Configuration Indirect Address register */ pcie_write32(offset, &cfg->base->paxb_config_ind_addr); /* Read data from Configuration Indirect Data register */ *data = pcie_read32(&cfg->base->paxb_config_ind_data); return 0; } static void iproc_pcie_conf_write(const struct device *dev, uint32_t offset, uint32_t data) { const struct iproc_pcie_ep_config *cfg = dev->config; /* Write offset to Configuration Indirect Address register */ pcie_write32(offset, &cfg->base->paxb_config_ind_addr); /* Write data to Configuration Indirect Data register */ pcie_write32(data, &cfg->base->paxb_config_ind_data); } static int iproc_pcie_map_addr(const struct device *dev, uint64_t pcie_addr, uint64_t *mapped_addr, uint32_t size, enum pcie_ob_mem_type ob_mem_type) { const struct iproc_pcie_ep_config *cfg = dev->config; struct iproc_pcie_ep_ctx *ctx = dev->data; uint64_t pcie_ob_base, pcie_ob_size, pcie_addr_start, offset; uint32_t mapped_size; enum pcie_outbound_map idx; k_spinlock_key_t key; int ret; key = k_spin_lock(&ctx->ob_map_lock); /* We support 2 outbound windows, * one in highmem region and another in lowmem region */ if ((ob_mem_type == PCIE_OB_HIGHMEM || ob_mem_type == PCIE_OB_ANYMEM) && !ctx->highmem_in_use) { idx = PCIE_MAP_HIGHMEM_IDX; pcie_ob_base = cfg->map_high_base; pcie_ob_size = cfg->map_high_size; } else if ((ob_mem_type == PCIE_OB_LOWMEM || ob_mem_type == PCIE_OB_ANYMEM) && !ctx->lowmem_in_use) { idx = PCIE_MAP_LOWMEM_IDX; pcie_ob_base = cfg->map_low_base; pcie_ob_size = cfg->map_low_size; } else { ret = -EBUSY; goto out; } /* check if the selected OB window supports size we want to map */ if (size > pcie_ob_size) { ret = -ENOTSUP; goto out; } /* Host PCIe address should be aligned to outbound window size */ pcie_addr_start = pcie_addr & ~(pcie_ob_size - 1); /* Program OARR with PCIe outbound address */ pcie_write32(((pcie_ob_base & ~(pcie_ob_size - 1)) | PAXB_OARR_VALID), &cfg->base->paxb_oarr[idx].lower); pcie_write32(pcie_ob_base >> 32, &cfg->base->paxb_oarr[idx].upper); /* Program OMAP with Host PCIe address */ pcie_write32((uint32_t)pcie_addr_start, &cfg->base->paxb_omap[idx].lower); pcie_write32((uint32_t)(pcie_addr_start >> 32), &cfg->base->paxb_omap[idx].upper); /* Mark usage of outbound window */ if (idx == PCIE_MAP_HIGHMEM_IDX) { ctx->highmem_in_use = true; } else { ctx->lowmem_in_use = true; } /* offset holds extra size mapped due to alignment requirement */ offset = pcie_addr - pcie_addr_start; *mapped_addr = pcie_ob_base + offset; mapped_size = pcie_ob_size - offset; ret = ((mapped_size >= size) ? size : mapped_size); out: k_spin_unlock(&ctx->ob_map_lock, key); return ret; } static void iproc_pcie_unmap_addr(const struct device *dev, uint64_t mapped_addr) { struct iproc_pcie_ep_ctx *ctx = dev->data; k_spinlock_key_t key; key = k_spin_lock(&ctx->ob_map_lock); /* * When doing Host writes using PCIe outbound window, it is seen * that before the writes gets completed using the existing outbound * window mapping, next mapping is overwriting it, causing few bytes * write failure with former mapping. * * To safeguard outbound window mapping, perform PCIe read in unmap, * which ensures that all PCIe writes before the read * are completed with this window. */ sys_read8(mapped_addr); if (mapped_addr >> 32) { ctx->highmem_in_use = false; } else { ctx->lowmem_in_use = false; } k_spin_unlock(&ctx->ob_map_lock, key); } static int iproc_pcie_generate_msi(const struct device *dev, const uint32_t msi_num) { int ret = 0; #ifdef CONFIG_PCIE_EP_BCM_IPROC_V2 uint64_t addr; uint32_t data; iproc_pcie_conf_read(dev, MSI_ADDR_H, &data); addr = ((uint64_t)data) << 32; iproc_pcie_conf_read(dev, MSI_ADDR_L, &data); addr = addr | data; if (data == 0) { /* * This is mostly the case where the test is being run * from device before host driver sets up MSI. * Returning zero instead of error because of this. */ LOG_WRN("MSI is not setup, skipping MSI"); return 0; } iproc_pcie_conf_read(dev, MSI_DATA, &data); data |= msi_num; ret = pcie_ep_xfer_data_memcpy(dev, addr, (uintptr_t *)&data, sizeof(data), PCIE_OB_LOWMEM, DEVICE_TO_HOST); #else const struct iproc_pcie_ep_config *cfg = dev->config; pcie_write32(msi_num, &cfg->base->paxb_pcie_sys_msi_req); #endif return ret; } static int iproc_pcie_generate_msix(const struct device *dev, const uint32_t msix_num) { uint64_t addr; uint32_t data, msix_offset; int ret; msix_offset = MSIX_TABLE_BASE + (msix_num * MSIX_TABLE_ENTRY_SIZE); addr = sys_read64(msix_offset); if (addr == 0) { /* * This is mostly the case where the test is being run * from device before host driver has setup MSIX table. * Returning zero instead of error because of this. */ LOG_WRN("MSIX table is not setup, skipping MSIX\n"); return 0; } data = sys_read32(msix_offset + MSIX_TBL_DATA_OFF); ret = pcie_ep_xfer_data_memcpy(dev, addr, (uintptr_t *)&data, sizeof(data), PCIE_OB_LOWMEM, DEVICE_TO_HOST); return ret; } static int iproc_pcie_raise_irq(const struct device *dev, enum pci_ep_irq_type irq_type, uint32_t irq_num) { struct iproc_pcie_ep_ctx *ctx = dev->data; k_spinlock_key_t key; int ret; key = k_spin_lock(&ctx->raise_irq_lock); switch (irq_type) { case PCIE_EP_IRQ_MSI: ret = iproc_pcie_generate_msi(dev, irq_num); break; case PCIE_EP_IRQ_MSIX: ret = iproc_pcie_generate_msix(dev, irq_num); break; case PCIE_EP_IRQ_LEGACY: ret = -ENOTSUP; break; default: LOG_ERR("Unknown IRQ type\n"); ret = -EINVAL; } k_spin_unlock(&ctx->raise_irq_lock, key); return ret; } static int iproc_pcie_register_reset_cb(const struct device *dev, enum pcie_reset reset, pcie_ep_reset_callback_t cb, void *arg) { struct iproc_pcie_ep_ctx *ctx = dev->data; if (reset < PCIE_PERST || reset >= PCIE_RESET_MAX) return -EINVAL; LOG_DBG("Registering the callback for reset %d", reset); ctx->reset_cb[reset] = cb; ctx->reset_data[reset] = arg; return 0; } #if DT_INST_IRQ_HAS_NAME(0, perst) static void iproc_pcie_perst(const struct device *dev) { struct iproc_pcie_ep_ctx *ctx = dev->data; void *reset_data; uint32_t data; data = sys_read32(CRMU_MCU_EXTRA_EVENT_STATUS); if (data & PCIE0_PERST_INTR) { LOG_DBG("PERST interrupt [0x%x]", data); sys_write32(PCIE0_PERST_INTR, CRMU_MCU_EXTRA_EVENT_CLEAR); if (ctx->reset_cb[PCIE_PERST] != NULL) { reset_data = ctx->reset_data[PCIE_PERST]; ctx->reset_cb[PCIE_PERST](reset_data); } } } #endif #if DT_INST_IRQ_HAS_NAME(0, perst_inband) static void iproc_pcie_hot_reset(const struct device *dev) { struct iproc_pcie_ep_ctx *ctx = dev->data; void *reset_data; uint32_t data; data = sys_read32(CRMU_MCU_EXTRA_EVENT_STATUS); if (data & PCIE0_PERST_INB_INTR) { LOG_DBG("INBAND PERST interrupt [0x%x]", data); sys_write32(PCIE0_PERST_INB_INTR, CRMU_MCU_EXTRA_EVENT_CLEAR); if (ctx->reset_cb[PCIE_PERST_INB] != NULL) { reset_data = ctx->reset_data[PCIE_PERST_INB]; ctx->reset_cb[PCIE_PERST_INB](reset_data); } } } #endif #if DT_INST_IRQ_HAS_NAME(0, flr) static void iproc_pcie_flr(const struct device *dev) { const struct iproc_pcie_ep_config *cfg = dev->config; struct iproc_pcie_ep_ctx *ctx = dev->data; void *reset_data; uint32_t data; data = pcie_read32(&cfg->base->paxb_paxb_intr_status); if (data & PCIE0_FLR_INTR) { LOG_DBG("FLR interrupt[0x%x]", data); pcie_write32(PCIE0_FLR_INTR, &cfg->base->paxb_paxb_intr_clear); if (ctx->reset_cb[PCIE_FLR] != NULL) { reset_data = ctx->reset_data[PCIE_FLR]; ctx->reset_cb[PCIE_FLR](reset_data); } } else { /* * Other interrupts like PAXB ECC Error interrupt * could show up at the beginning which are harmless. * So simply clearing those interrupts here */ LOG_DBG("PAXB interrupt[0x%x]", data); pcie_write32(data, &cfg->base->paxb_paxb_intr_clear); } /* Clear FLR in Progress bit */ iproc_pcie_conf_read(dev, PCIE_DEV_CTRL_OFFSET, &data); data |= FLR_IN_PROGRESS; iproc_pcie_conf_write(dev, PCIE_DEV_CTRL_OFFSET, data); } #endif DEVICE_DECLARE(iproc_pcie_ep_0); static void iproc_pcie_reset_config(const struct device *dev) { uint32_t data; const struct iproc_pcie_ep_config *cfg = dev->config; /* Clear any possible prior pending interrupts */ sys_write32(PCIE0_PERST_INTR | PCIE0_PERST_INB_INTR, CRMU_MCU_EXTRA_EVENT_CLEAR); pcie_write32(PCIE0_FLR_INTR, &cfg->base->paxb_paxb_intr_clear); /* Enable PERST and Inband PERST interrupts */ data = sys_read32(PCIE_PERSTB_INTR_CTL_STS); data |= (PCIE0_PERST_FE_INTR | PCIE0_PERST_INB_FE_INTR); sys_write32(data, PCIE_PERSTB_INTR_CTL_STS); data = sys_read32(CRMU_MCU_EXTRA_EVENT_MASK); data &= ~(PCIE0_PERST_INTR | PCIE0_PERST_INB_INTR); sys_write32(data, CRMU_MCU_EXTRA_EVENT_MASK); /* Set auto clear FLR and auto clear CRS post FLR */ iproc_pcie_conf_read(dev, PCIE_TL_CTRL0_OFFSET, &data); data |= (AUTO_CLR_CRS_POST_FLR | AUTO_CLR_FLR_AFTER_DELAY); iproc_pcie_conf_write(dev, PCIE_TL_CTRL0_OFFSET, data); /* Enable Function Level Reset */ data = pcie_read32(&cfg->base->paxb_paxb_intr_en); data |= PCIE0_FLR_INTR; pcie_write32(data, &cfg->base->paxb_paxb_intr_en); #if DT_INST_IRQ_HAS_NAME(0, perst) IRQ_CONNECT(DT_INST_IRQ_BY_NAME(0, perst, irq), DT_INST_IRQ_BY_NAME(0, perst, priority), iproc_pcie_perst, DEVICE_GET(iproc_pcie_ep_0), 0); irq_enable(DT_INST_IRQ_BY_NAME(0, perst, irq)); #endif #if DT_INST_IRQ_HAS_NAME(0, perst_inband) IRQ_CONNECT(DT_INST_IRQ_BY_NAME(0, perst_inband, irq), DT_INST_IRQ_BY_NAME(0, perst_inband, priority), iproc_pcie_hot_reset, DEVICE_GET(iproc_pcie_ep_0), 0); irq_enable(DT_INST_IRQ_BY_NAME(0, perst_inband, irq)); #endif #if DT_INST_IRQ_HAS_NAME(0, flr) IRQ_CONNECT(DT_INST_IRQ_BY_NAME(0, flr, irq), DT_INST_IRQ_BY_NAME(0, flr, priority), iproc_pcie_flr, DEVICE_GET(iproc_pcie_ep_0), 0); irq_enable(DT_INST_IRQ_BY_NAME(0, flr, irq)); #endif } #ifdef CONFIG_PCIE_EP_BCM_IPROC_INIT_CFG static void iproc_pcie_msix_config(const struct device *dev) { /* * Configure capability of generating 16 messages, * MSI-X Table offset 0x10000 on BAR2, * MSI-X PBA offset 0x10800 on BAR2. */ iproc_pcie_conf_write(dev, MSIX_CONTROL, (MSIX_TABLE_SIZE - 1)); iproc_pcie_conf_write(dev, MSIX_TBL_OFF_BIR, MSIX_TBL_B2_10000); iproc_pcie_conf_write(dev, MSIX_PBA_OFF_BIR, MSIX_PBA_B2_10800); } static void iproc_pcie_msi_config(const struct device *dev) { uint32_t data; /* Configure capability of generating 16 messages */ iproc_pcie_conf_read(dev, ID_VAL4_OFFSET, &data); data = (data & ~(MSI_COUNT_MASK)) | (MSI_COUNT_VAL << MSI_COUNT_SHIFT); iproc_pcie_conf_write(dev, ID_VAL4_OFFSET, data); } #endif static int iproc_pcie_mode_check(const struct iproc_pcie_ep_config *cfg) { uint32_t data; data = pcie_read32(&cfg->base->paxb_strap_status); LOG_DBG("PAXB_STRAP_STATUS = 0x%08X\n", data); if (data & PCIE_RC_MODE_MASK) { return -ENOTSUP; } return 0; } static int iproc_pcie_ep_init(const struct device *dev) { const struct iproc_pcie_ep_config *cfg = dev->config; struct iproc_pcie_ep_ctx *ctx = dev->data; int ret; uint32_t data; ret = iproc_pcie_mode_check(cfg); if (ret) { LOG_ERR("ERROR: Only PCIe EP mode is supported\n"); goto err_out; } iproc_pcie_conf_read(dev, PCIE_LINK_STATUS_CONTROL, &data); LOG_INF("PCIe linkup speed 0x%x\n", ((data >> PCIE_LINKSPEED_SHIFT) & PCIE_LINKSPEED_MASK)); LOG_INF("PCIe linkup width 0x%x\n", ((data >> PCIE_LINKWIDTH_SHIFT) & PCIE_LINKWIDTH_MASK)); #ifdef CONFIG_PCIE_EP_BCM_IPROC_INIT_CFG iproc_pcie_msi_config(dev); iproc_pcie_msix_config(dev); #endif iproc_pcie_reset_config(dev); ctx->highmem_in_use = false; ctx->lowmem_in_use = false; LOG_INF("PCIe initialized successfully\n"); err_out: return ret; } static struct iproc_pcie_ep_ctx iproc_pcie_ep_ctx_0; static struct iproc_pcie_ep_config iproc_pcie_ep_config_0 = { .id = 0, .base = (struct iproc_pcie_reg *)DT_INST_REG_ADDR(0), .reg_size = DT_INST_REG_SIZE(0), .map_low_base = DT_INST_REG_ADDR_BY_NAME(0, map_lowmem), .map_low_size = DT_INST_REG_SIZE_BY_NAME(0, map_lowmem), .map_high_base = DT_INST_REG_ADDR_BY_NAME(0, map_highmem), .map_high_size = DT_INST_REG_SIZE_BY_NAME(0, map_highmem), }; static struct pcie_ep_driver_api iproc_pcie_ep_api = { .conf_read = iproc_pcie_conf_read, .conf_write = iproc_pcie_conf_write, .map_addr = iproc_pcie_map_addr, .unmap_addr = iproc_pcie_unmap_addr, .raise_irq = iproc_pcie_raise_irq, .register_reset_cb = iproc_pcie_register_reset_cb, }; DEVICE_AND_API_INIT(iproc_pcie_ep_0, DT_INST_LABEL(0), &iproc_pcie_ep_init, &iproc_pcie_ep_ctx_0, &iproc_pcie_ep_config_0, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &iproc_pcie_ep_api);