xref: /linux/drivers/net/ethernet/microchip/fdma/fdma_pci.c (revision 3318a794bb15e329d09dba3aaddd5ef8e22eace4)
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 &regions[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(&region->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(&region->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(&regions[i]);
206*3318a794SDaniel Machon 	}
207*3318a794SDaniel Machon }
208*3318a794SDaniel Machon EXPORT_SYMBOL_GPL(fdma_pci_atu_init);
209