| // 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 <l.subrahmanya@mobiveil.co.in> | |
| * Hou Zhiqiang <Zhiqiang.Hou@nxp.com> | |
| */ | |
| #include <linux/delay.h> | |
| #include <linux/init.h> | |
| #include <linux/kernel.h> | |
| #include <linux/pci.h> | |
| #include <linux/platform_device.h> | |
| #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 __iomem *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 mobiveil_csr_read(struct mobiveil_pcie *pcie, u32 off, size_t size) | |
| { | |
| void __iomem *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 mobiveil_csr_write(struct mobiveil_pcie *pcie, u32 val, u32 off, | |
| size_t size) | |
| { | |
| void __iomem *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 (mobiveil_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 = mobiveil_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); | |
| mobiveil_csr_writel(pcie, value, PAB_PEX_AMAP_CTRL(win_num)); | |
| mobiveil_csr_writel(pcie, upper_32_bits(size64), | |
| PAB_EXT_PEX_AMAP_SIZEN(win_num)); | |
| mobiveil_csr_writel(pcie, lower_32_bits(cpu_addr), | |
| PAB_PEX_AMAP_AXI_WIN(win_num)); | |
| mobiveil_csr_writel(pcie, upper_32_bits(cpu_addr), | |
| PAB_EXT_PEX_AMAP_AXI_WIN(win_num)); | |
| mobiveil_csr_writel(pcie, lower_32_bits(pci_addr), | |
| PAB_PEX_AMAP_PEX_WIN_L(win_num)); | |
| mobiveil_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, int win_num, | |
| u64 cpu_addr, u64 pci_addr, u32 type, u64 size) | |
| { | |
| u32 value; | |
| u64 size64 = ~(size - 1); | |
| if (win_num >= pcie->apio_wins) { | |
| dev_err(&pcie->pdev->dev, | |
| "ERROR: max outbound windows reached !\n"); | |
| return; | |
| } | |
| /* | |
| * 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 = mobiveil_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); | |
| mobiveil_csr_writel(pcie, value, PAB_AXI_AMAP_CTRL(win_num)); | |
| mobiveil_csr_writel(pcie, upper_32_bits(size64), | |
| PAB_EXT_AXI_AMAP_SIZE(win_num)); | |
| /* | |
| * program AXI window base with appropriate value in | |
| * PAB_AXI_AMAP_AXI_WIN0 register | |
| */ | |
| mobiveil_csr_writel(pcie, | |
| lower_32_bits(cpu_addr) & (~AXI_WINDOW_ALIGN_MASK), | |
| PAB_AXI_AMAP_AXI_WIN(win_num)); | |
| mobiveil_csr_writel(pcie, upper_32_bits(cpu_addr), | |
| PAB_EXT_AXI_AMAP_AXI_WIN(win_num)); | |
| mobiveil_csr_writel(pcie, lower_32_bits(pci_addr), | |
| PAB_AXI_AMAP_PEX_WIN_L(win_num)); | |
| mobiveil_csr_writel(pcie, upper_32_bits(pci_addr), | |
| PAB_AXI_AMAP_PEX_WIN_H(win_num)); | |
| pcie->ob_wins_configured++; | |
| } | |
| int mobiveil_bringup_link(struct mobiveil_pcie *pcie) | |
| { | |
| int retries; | |
| /* check if the link is up or not */ | |
| for (retries = 0; retries < PCIE_LINK_WAIT_MAX_RETRIES; retries++) { | |
| if (mobiveil_pcie_link_up(pcie)) | |
| return 0; | |
| msleep(PCIE_LINK_WAIT_SLEEP_MS); | |
| } | |
| dev_err(&pcie->pdev->dev, "link never came up\n"); | |
| return -ETIMEDOUT; | |
| } |