1*3318a794SDaniel Machon // SPDX-License-Identifier: GPL-2.0+ 2*3318a794SDaniel Machon 3*3318a794SDaniel Machon #include <linux/align.h> 4*3318a794SDaniel Machon #include <linux/errno.h> 5*3318a794SDaniel Machon #include <linux/io.h> 6*3318a794SDaniel Machon #include <linux/kernel.h> 7*3318a794SDaniel Machon #include <linux/types.h> 8*3318a794SDaniel Machon 9*3318a794SDaniel Machon #include "fdma_pci.h" 10*3318a794SDaniel Machon 11*3318a794SDaniel Machon /* When the switch operates as a PCIe endpoint, the FDMA engine needs to 12*3318a794SDaniel Machon * DMA to/from host memory. The FDMA writes to addresses within the endpoint's 13*3318a794SDaniel Machon * internal Outbound (OB) address space, and the PCIe ATU translates these to 14*3318a794SDaniel Machon * DMA addresses on the PCIe bus, targeting host memory. 15*3318a794SDaniel Machon * 16*3318a794SDaniel Machon * The ATU supports up to six outbound regions. This implementation divides 17*3318a794SDaniel Machon * the OB address space into six equally sized chunks. 18*3318a794SDaniel Machon * 19*3318a794SDaniel Machon * +-------------+------------+------------+-----+------------+ 20*3318a794SDaniel Machon * | Index | Region 0 | Region 1 | ... | Region 5 | 21*3318a794SDaniel Machon * +-------------+------------+------------+-----+------------+ 22*3318a794SDaniel Machon * | Base addr | 0x10000000 | 0x12aa0000 | ... | 0x1d520000 | 23*3318a794SDaniel Machon * | Limit addr | 0x12a9ffff | 0x1553ffff | ... | 0x1ffbffff | 24*3318a794SDaniel Machon * | Target addr | host dma | host dma | ... | host dma | 25*3318a794SDaniel Machon * +-------------+------------+------------+-----+------------+ 26*3318a794SDaniel Machon * 27*3318a794SDaniel Machon * Base addr is the start address of the region within the OB address space. 28*3318a794SDaniel Machon * Limit addr is each region's own upper bound. The value actually 29*3318a794SDaniel Machon * programmed is set per-mapping as base_addr + mapped size - 1, and is 30*3318a794SDaniel Machon * usually smaller than this. 31*3318a794SDaniel Machon * Target addr is the host DMA address that the base addr translates to. 32*3318a794SDaniel Machon */ 33*3318a794SDaniel Machon 34*3318a794SDaniel Machon #define FDMA_PCI_ATU_OB_START 0x10000000 35*3318a794SDaniel Machon #define FDMA_PCI_ATU_OB_END 0x1fffffff 36*3318a794SDaniel Machon 37*3318a794SDaniel Machon #define FDMA_PCI_ATU_ADDR 0x300000 38*3318a794SDaniel Machon #define FDMA_PCI_ATU_IDX_SIZE 0x200 39*3318a794SDaniel Machon #define FDMA_PCI_ATU_ENA_REG 0x4 40*3318a794SDaniel Machon #define FDMA_PCI_ATU_ENA_BIT BIT(31) 41*3318a794SDaniel Machon #define FDMA_PCI_ATU_LWR_BASE_ADDR 0x8 42*3318a794SDaniel Machon #define FDMA_PCI_ATU_UPP_BASE_ADDR 0xc 43*3318a794SDaniel Machon #define FDMA_PCI_ATU_LIMIT_ADDR 0x10 44*3318a794SDaniel Machon #define FDMA_PCI_ATU_LWR_TARGET_ADDR 0x14 45*3318a794SDaniel Machon #define FDMA_PCI_ATU_UPP_TARGET_ADDR 0x18 46*3318a794SDaniel Machon 47*3318a794SDaniel Machon static u32 fdma_pci_atu_region_size(void) 48*3318a794SDaniel Machon { 49*3318a794SDaniel Machon return round_down((FDMA_PCI_ATU_OB_END - FDMA_PCI_ATU_OB_START) / 50*3318a794SDaniel Machon FDMA_PCI_ATU_REGION_MAX, FDMA_PCI_ATU_REGION_ALIGN); 51*3318a794SDaniel Machon } 52*3318a794SDaniel Machon 53*3318a794SDaniel Machon static void __iomem *fdma_pci_atu_addr_get(void __iomem *addr, int offset, 54*3318a794SDaniel Machon int idx) 55*3318a794SDaniel Machon { 56*3318a794SDaniel Machon return addr + FDMA_PCI_ATU_ADDR + FDMA_PCI_ATU_IDX_SIZE * idx + offset; 57*3318a794SDaniel Machon } 58*3318a794SDaniel Machon 59*3318a794SDaniel Machon static void fdma_pci_atu_region_enable(struct fdma_pci_atu_region *region) 60*3318a794SDaniel Machon { 61*3318a794SDaniel Machon writel(FDMA_PCI_ATU_ENA_BIT, 62*3318a794SDaniel Machon fdma_pci_atu_addr_get(region->atu->addr, FDMA_PCI_ATU_ENA_REG, 63*3318a794SDaniel Machon region->idx)); 64*3318a794SDaniel Machon } 65*3318a794SDaniel Machon 66*3318a794SDaniel Machon static void fdma_pci_atu_region_disable(struct fdma_pci_atu_region *region) 67*3318a794SDaniel Machon { 68*3318a794SDaniel Machon writel(0, fdma_pci_atu_addr_get(region->atu->addr, FDMA_PCI_ATU_ENA_REG, 69*3318a794SDaniel Machon region->idx)); 70*3318a794SDaniel Machon } 71*3318a794SDaniel Machon 72*3318a794SDaniel Machon /* Configure the address translation in the ATU. */ 73*3318a794SDaniel Machon static void 74*3318a794SDaniel Machon fdma_pci_atu_configure_translation(struct fdma_pci_atu_region *region) 75*3318a794SDaniel Machon { 76*3318a794SDaniel Machon struct fdma_pci_atu *atu = region->atu; 77*3318a794SDaniel Machon int idx = region->idx; 78*3318a794SDaniel Machon 79*3318a794SDaniel Machon writel(lower_32_bits(region->base_addr), 80*3318a794SDaniel Machon fdma_pci_atu_addr_get(atu->addr, 81*3318a794SDaniel Machon FDMA_PCI_ATU_LWR_BASE_ADDR, idx)); 82*3318a794SDaniel Machon 83*3318a794SDaniel Machon writel(upper_32_bits(region->base_addr), 84*3318a794SDaniel Machon fdma_pci_atu_addr_get(atu->addr, 85*3318a794SDaniel Machon FDMA_PCI_ATU_UPP_BASE_ADDR, idx)); 86*3318a794SDaniel Machon 87*3318a794SDaniel Machon /* The OB address space lies entirely below 4GB, so the limit always 88*3318a794SDaniel Machon * fits the lower limit register and the upper one is left alone. 89*3318a794SDaniel Machon */ 90*3318a794SDaniel Machon writel(region->limit_addr, 91*3318a794SDaniel Machon fdma_pci_atu_addr_get(atu->addr, FDMA_PCI_ATU_LIMIT_ADDR, idx)); 92*3318a794SDaniel Machon 93*3318a794SDaniel Machon writel(lower_32_bits(region->target_addr), 94*3318a794SDaniel Machon fdma_pci_atu_addr_get(atu->addr, 95*3318a794SDaniel Machon FDMA_PCI_ATU_LWR_TARGET_ADDR, idx)); 96*3318a794SDaniel Machon 97*3318a794SDaniel Machon writel(upper_32_bits(region->target_addr), 98*3318a794SDaniel Machon fdma_pci_atu_addr_get(atu->addr, 99*3318a794SDaniel Machon FDMA_PCI_ATU_UPP_TARGET_ADDR, idx)); 100*3318a794SDaniel Machon } 101*3318a794SDaniel Machon 102*3318a794SDaniel Machon /* Find an unused ATU region. */ 103*3318a794SDaniel Machon static struct fdma_pci_atu_region * 104*3318a794SDaniel Machon fdma_pci_atu_region_get_free(struct fdma_pci_atu *atu) 105*3318a794SDaniel Machon { 106*3318a794SDaniel Machon struct fdma_pci_atu_region *regions = atu->regions; 107*3318a794SDaniel Machon 108*3318a794SDaniel Machon for (int i = 0; i < FDMA_PCI_ATU_REGION_MAX; i++) { 109*3318a794SDaniel Machon if (regions[i].in_use) 110*3318a794SDaniel Machon continue; 111*3318a794SDaniel Machon 112*3318a794SDaniel Machon return ®ions[i]; 113*3318a794SDaniel Machon } 114*3318a794SDaniel Machon 115*3318a794SDaniel Machon return ERR_PTR(-ENOSPC); 116*3318a794SDaniel Machon } 117*3318a794SDaniel Machon 118*3318a794SDaniel Machon /* Unmap an ATU region, clearing its translation and disabling it. */ 119*3318a794SDaniel Machon void fdma_pci_atu_region_unmap(struct fdma_pci_atu_region *region) 120*3318a794SDaniel Machon { 121*3318a794SDaniel Machon if (IS_ERR_OR_NULL(region)) 122*3318a794SDaniel Machon return; 123*3318a794SDaniel Machon 124*3318a794SDaniel Machon mutex_lock(®ion->atu->lock); 125*3318a794SDaniel Machon 126*3318a794SDaniel Machon region->target_addr = 0; 127*3318a794SDaniel Machon region->in_use = false; 128*3318a794SDaniel Machon 129*3318a794SDaniel Machon fdma_pci_atu_region_disable(region); 130*3318a794SDaniel Machon fdma_pci_atu_configure_translation(region); 131*3318a794SDaniel Machon 132*3318a794SDaniel Machon mutex_unlock(®ion->atu->lock); 133*3318a794SDaniel Machon } 134*3318a794SDaniel Machon EXPORT_SYMBOL_GPL(fdma_pci_atu_region_unmap); 135*3318a794SDaniel Machon 136*3318a794SDaniel Machon /* Map a host DMA address into a free outbound region. */ 137*3318a794SDaniel Machon struct fdma_pci_atu_region * 138*3318a794SDaniel Machon fdma_pci_atu_region_map(struct fdma_pci_atu *atu, u64 target_addr, int size) 139*3318a794SDaniel Machon { 140*3318a794SDaniel Machon struct fdma_pci_atu_region *region; 141*3318a794SDaniel Machon 142*3318a794SDaniel Machon if (!atu) 143*3318a794SDaniel Machon return ERR_PTR(-EINVAL); 144*3318a794SDaniel Machon 145*3318a794SDaniel Machon if (size <= 0) 146*3318a794SDaniel Machon return ERR_PTR(-EINVAL); 147*3318a794SDaniel Machon 148*3318a794SDaniel Machon if (size > fdma_pci_atu_region_size()) 149*3318a794SDaniel Machon return ERR_PTR(-ERANGE); 150*3318a794SDaniel Machon 151*3318a794SDaniel Machon /* The ATU region base is only ever aligned to FDMA_PCI_ATU_REGION_ALIGN; 152*3318a794SDaniel Machon * require the same alignment of the host target address, since the ATU 153*3318a794SDaniel Machon * translates addr - target_addr + base_addr and any misalignment here 154*3318a794SDaniel Machon * would shift every translated address by the same amount. 155*3318a794SDaniel Machon */ 156*3318a794SDaniel Machon if (!IS_ALIGNED(target_addr, FDMA_PCI_ATU_REGION_ALIGN)) 157*3318a794SDaniel Machon return ERR_PTR(-EINVAL); 158*3318a794SDaniel Machon 159*3318a794SDaniel Machon mutex_lock(&atu->lock); 160*3318a794SDaniel Machon 161*3318a794SDaniel Machon region = fdma_pci_atu_region_get_free(atu); 162*3318a794SDaniel Machon if (IS_ERR(region)) { 163*3318a794SDaniel Machon mutex_unlock(&atu->lock); 164*3318a794SDaniel Machon return region; 165*3318a794SDaniel Machon } 166*3318a794SDaniel Machon 167*3318a794SDaniel Machon region->target_addr = target_addr; 168*3318a794SDaniel Machon region->limit_addr = region->base_addr + size - 1; 169*3318a794SDaniel Machon region->in_use = true; 170*3318a794SDaniel Machon 171*3318a794SDaniel Machon fdma_pci_atu_configure_translation(region); 172*3318a794SDaniel Machon fdma_pci_atu_region_enable(region); 173*3318a794SDaniel Machon 174*3318a794SDaniel Machon mutex_unlock(&atu->lock); 175*3318a794SDaniel Machon 176*3318a794SDaniel Machon return region; 177*3318a794SDaniel Machon } 178*3318a794SDaniel Machon EXPORT_SYMBOL_GPL(fdma_pci_atu_region_map); 179*3318a794SDaniel Machon 180*3318a794SDaniel Machon /* Translate a host DMA address to the corresponding OB address. */ 181*3318a794SDaniel Machon u64 fdma_pci_atu_translate_addr(struct fdma_pci_atu_region *region, u64 addr) 182*3318a794SDaniel Machon { 183*3318a794SDaniel Machon return region->base_addr + (addr - region->target_addr); 184*3318a794SDaniel Machon } 185*3318a794SDaniel Machon EXPORT_SYMBOL_GPL(fdma_pci_atu_translate_addr); 186*3318a794SDaniel Machon 187*3318a794SDaniel Machon /* Initialize ATU, dividing the OB space into equally sized regions. */ 188*3318a794SDaniel Machon void fdma_pci_atu_init(struct fdma_pci_atu *atu, void __iomem *addr) 189*3318a794SDaniel Machon { 190*3318a794SDaniel Machon struct fdma_pci_atu_region *regions = atu->regions; 191*3318a794SDaniel Machon u32 region_size = fdma_pci_atu_region_size(); 192*3318a794SDaniel Machon 193*3318a794SDaniel Machon atu->addr = addr; 194*3318a794SDaniel Machon mutex_init(&atu->lock); 195*3318a794SDaniel Machon 196*3318a794SDaniel Machon for (int i = 0; i < FDMA_PCI_ATU_REGION_MAX; i++) { 197*3318a794SDaniel Machon regions[i].base_addr = 198*3318a794SDaniel Machon FDMA_PCI_ATU_OB_START + (i * region_size); 199*3318a794SDaniel Machon regions[i].limit_addr = 0; 200*3318a794SDaniel Machon regions[i].target_addr = 0; 201*3318a794SDaniel Machon regions[i].idx = i; 202*3318a794SDaniel Machon regions[i].atu = atu; 203*3318a794SDaniel Machon regions[i].in_use = false; 204*3318a794SDaniel Machon 205*3318a794SDaniel Machon fdma_pci_atu_region_disable(®ions[i]); 206*3318a794SDaniel Machon } 207*3318a794SDaniel Machon } 208*3318a794SDaniel Machon EXPORT_SYMBOL_GPL(fdma_pci_atu_init); 209