// SPDX-License-Identifier: GPL-2.0 /* * PCIe host controller driver for Mobiveil PCIe Host controller * * Copyright (c) 2018 Mobiveil Inc. * Copyright 2019 NXP * * Author: Subrahmanya Lingappa * Refactor: Zhiqiang Hou */ #include #include #include #include #include #include "pcie-mobiveil.h" /* * mobiveil_pcie_sel_page - routine to access paged register * * Registers whose address greater than PAGED_ADDR_BNDRY (0xc00) are paged, * for this scheme to work extracted higher 6 bits of the offset will be * written to pg_sel field of PAB_CTRL register and rest of the lower 10 * bits enabled with PAGED_ADDR_BNDRY are used as offset of the register. */ static void mobiveil_pcie_sel_page(struct mobiveil_pcie *pcie, u8 pg_idx) { u32 val; val = readl(pcie->csr_axi_slave_base + PAB_CTRL); val &= ~(PAGE_SEL_MASK << PAGE_SEL_SHIFT); val |= (pg_idx & PAGE_SEL_MASK) << PAGE_SEL_SHIFT; writel(val, pcie->csr_axi_slave_base + PAB_CTRL); } static void *mobiveil_pcie_comp_addr(struct mobiveil_pcie *pcie, u32 off) { if (off < PAGED_ADDR_BNDRY) { /* For directly accessed registers, clear the pg_sel field */ mobiveil_pcie_sel_page(pcie, 0); return pcie->csr_axi_slave_base + off; } mobiveil_pcie_sel_page(pcie, OFFSET_TO_PAGE_IDX(off)); return pcie->csr_axi_slave_base + OFFSET_TO_PAGE_ADDR(off); } static int mobiveil_pcie_read(void __iomem *addr, int size, u32 *val) { if ((uintptr_t)addr & (size - 1)) { *val = 0; return PCIBIOS_BAD_REGISTER_NUMBER; } switch (size) { case 4: *val = readl(addr); break; case 2: *val = readw(addr); break; case 1: *val = readb(addr); break; default: *val = 0; return PCIBIOS_BAD_REGISTER_NUMBER; } return PCIBIOS_SUCCESSFUL; } static int mobiveil_pcie_write(void __iomem *addr, int size, u32 val) { if ((uintptr_t)addr & (size - 1)) return PCIBIOS_BAD_REGISTER_NUMBER; switch (size) { case 4: writel(val, addr); break; case 2: writew(val, addr); break; case 1: writeb(val, addr); break; default: return PCIBIOS_BAD_REGISTER_NUMBER; } return PCIBIOS_SUCCESSFUL; } u32 csr_read(struct mobiveil_pcie *pcie, u32 off, size_t size) { void *addr; u32 val; int ret; addr = mobiveil_pcie_comp_addr(pcie, off); ret = mobiveil_pcie_read(addr, size, &val); if (ret) dev_err(&pcie->pdev->dev, "read CSR address failed\n"); return val; } void csr_write(struct mobiveil_pcie *pcie, u32 val, u32 off, size_t size) { void *addr; int ret; addr = mobiveil_pcie_comp_addr(pcie, off); ret = mobiveil_pcie_write(addr, size, val); if (ret) dev_err(&pcie->pdev->dev, "write CSR address failed\n"); } bool mobiveil_pcie_link_up(struct mobiveil_pcie *pcie) { if (pcie->ops->link_up) return pcie->ops->link_up(pcie); return (csr_readl(pcie, LTSSM_STATUS) & LTSSM_STATUS_L0_MASK) == LTSSM_STATUS_L0; } void program_ib_windows(struct mobiveil_pcie *pcie, int win_num, u64 cpu_addr, u64 pci_addr, u32 type, u64 size) { u32 value; u64 size64 = ~(size - 1); if (win_num >= pcie->ppio_wins) { dev_err(&pcie->pdev->dev, "ERROR: max inbound windows reached !\n"); return; } value = csr_readl(pcie, PAB_PEX_AMAP_CTRL(win_num)); value &= ~(AMAP_CTRL_TYPE_MASK << AMAP_CTRL_TYPE_SHIFT | WIN_SIZE_MASK); value |= type << AMAP_CTRL_TYPE_SHIFT | 1 << AMAP_CTRL_EN_SHIFT | (lower_32_bits(size64) & WIN_SIZE_MASK); csr_writel(pcie, value, PAB_PEX_AMAP_CTRL(win_num)); csr_writel(pcie, upper_32_bits(size64), PAB_EXT_PEX_AMAP_SIZEN(win_num)); csr_writel(pcie, lower_32_bits(cpu_addr), PAB_PEX_AMAP_AXI_WIN(win_num)); csr_writel(pcie, upper_32_bits(cpu_addr), PAB_EXT_PEX_AMAP_AXI_WIN(win_num)); csr_writel(pcie, lower_32_bits(pci_addr), PAB_PEX_AMAP_PEX_WIN_L(win_num)); csr_writel(pcie, upper_32_bits(pci_addr), PAB_PEX_AMAP_PEX_WIN_H(win_num)); pcie->ib_wins_configured++; } /* * routine to program the outbound windows */ void __program_ob_windows(struct mobiveil_pcie *pcie, u8 func_no, int win_num, u64 cpu_addr, u64 pci_addr, u32 type, u64 size) { u32 value; u64 size64 = ~(size - 1); /* * program Enable Bit to 1, Type Bit to (00) base 2, AXI Window Size Bit * to 4 KB in PAB_AXI_AMAP_CTRL register */ value = csr_readl(pcie, PAB_AXI_AMAP_CTRL(win_num)); value &= ~(WIN_TYPE_MASK << WIN_TYPE_SHIFT | WIN_SIZE_MASK); value |= 1 << WIN_ENABLE_SHIFT | type << WIN_TYPE_SHIFT | (lower_32_bits(size64) & WIN_SIZE_MASK); csr_writel(pcie, value, PAB_AXI_AMAP_CTRL(win_num)); csr_writel(pcie, upper_32_bits(size64), PAB_EXT_AXI_AMAP_SIZE(win_num)); csr_writel(pcie, func_no, PAB_AXI_AMAP_PCI_HDR_PARAM(win_num)); /* * program AXI window base with appropriate value in * PAB_AXI_AMAP_AXI_WIN0 register */ csr_writel(pcie, lower_32_bits(cpu_addr) & (~AXI_WINDOW_ALIGN_MASK), PAB_AXI_AMAP_AXI_WIN(win_num)); csr_writel(pcie, upper_32_bits(cpu_addr), PAB_EXT_AXI_AMAP_AXI_WIN(win_num)); csr_writel(pcie, lower_32_bits(pci_addr), PAB_AXI_AMAP_PEX_WIN_L(win_num)); csr_writel(pcie, upper_32_bits(pci_addr), PAB_AXI_AMAP_PEX_WIN_H(win_num)); } void program_ob_windows(struct mobiveil_pcie *pcie, int win_num, u64 cpu_addr, u64 pci_addr, u32 type, u64 size) { if (win_num >= pcie->apio_wins) { dev_err(&pcie->pdev->dev, "ERROR: max outbound windows reached !\n"); return; } __program_ob_windows(pcie, 0, win_num, cpu_addr, pci_addr, type, size); pcie->ob_wins_configured++; } void program_ob_windows_ep(struct mobiveil_pcie *pcie, u8 func_no, int win_num, u64 cpu_addr, u64 pci_addr, u32 type, u64 size) { if (size & (size - 1)) size = 1 << (1 + ilog2(size)); __program_ob_windows(pcie, func_no, win_num, cpu_addr, pci_addr, type, size); } void program_ib_windows_ep(struct mobiveil_pcie *pcie, u8 func_no, int bar, u64 phys) { csr_writel(pcie, upper_32_bits(phys), PAB_EXT_PEX_BAR_AMAP(func_no, bar)); csr_writel(pcie, lower_32_bits(phys) | PEX_BAR_AMAP_EN, PAB_PEX_BAR_AMAP(func_no, bar)); } void mobiveil_pcie_disable_ib_win_ep(struct mobiveil_pcie *pcie, u8 func_no, u8 bar) { u32 val; val = csr_readl(pcie, PAB_PEX_BAR_AMAP(func_no, bar)); val &= ~(1 << 0); csr_writel(pcie, val, PAB_PEX_BAR_AMAP(func_no, bar)); } void mobiveil_pcie_disable_ob_win(struct mobiveil_pcie *pcie, int win_num) { u32 val; val = csr_readl(pcie, PAB_AXI_AMAP_CTRL(win_num)); val &= ~(1 << WIN_ENABLE_SHIFT); csr_writel(pcie, val, PAB_AXI_AMAP_CTRL(win_num)); } void mobiveil_pcie_enable_bridge_pio(struct mobiveil_pcie *pcie) { u32 val; val = csr_readl(pcie, PAB_CTRL); val |= 1 << AMBA_PIO_ENABLE_SHIFT; val |= 1 << PEX_PIO_ENABLE_SHIFT; csr_writel(pcie, val, PAB_CTRL); } void mobiveil_pcie_enable_engine_apio(struct mobiveil_pcie *pcie) { u32 val; val = csr_readl(pcie, PAB_AXI_PIO_CTRL); val |= APIO_EN_MASK; csr_writel(pcie, val, PAB_AXI_PIO_CTRL); } void mobiveil_pcie_enable_engine_ppio(struct mobiveil_pcie *pcie) { u32 val; val = csr_readl(pcie, PAB_PEX_PIO_CTRL); val |= 1 << PIO_ENABLE_SHIFT; csr_writel(pcie, val, PAB_PEX_PIO_CTRL); } void mobiveil_pcie_enable_msi_ep(struct mobiveil_pcie *pcie) { u32 val; val = csr_readl(pcie, PAB_INTP_AMBA_MISC_ENB); val |= PAB_INTP_PAMR; csr_writel(pcie, val, PAB_INTP_AMBA_MISC_ENB); } int mobiveil_bringup_link(struct mobiveil_pcie *pcie) { int retries; /* check if the link is up or not */ for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) { if (mobiveil_pcie_link_up(pcie)) return 0; usleep_range(LINK_WAIT_MIN, LINK_WAIT_MAX); } dev_err(&pcie->pdev->dev, "link never came up\n"); return -ETIMEDOUT; }