When lan966x or lan969x operates as a PCIe endpoint, the internal FDMA engine cannot directly access host memory. Instead, DMA addresses must be translated through the PCIe Address Translation Unit (ATU). The ATU provides outbound windows that map internal addresses to PCIe bus addresses. The ATU outbound address space (0x10000000-0x1fffffff) is divided into six equally-sized regions (~42MB each). When FDMA buffers are allocated, a free ATU region is claimed and programmed with the DMA target address. The FDMA engine then uses the region's base address in its descriptors, and the ATU translates these to the actual DMA addresses on the PCIe bus. Add the required functions and helpers that combine the DMA allocation with the ATU region mapping, effectively adding support for PCIe FDMA. This implementation will also be used by the lan969x, when PCIe FDMA is added for that platform in the future. Signed-off-by: Daniel Machon --- drivers/net/ethernet/microchip/fdma/Makefile | 4 + drivers/net/ethernet/microchip/fdma/fdma_api.c | 33 +++++ drivers/net/ethernet/microchip/fdma/fdma_api.h | 16 +++ drivers/net/ethernet/microchip/fdma/fdma_pci.c | 177 +++++++++++++++++++++++++ drivers/net/ethernet/microchip/fdma/fdma_pci.h | 41 ++++++ 5 files changed, 271 insertions(+) diff --git a/drivers/net/ethernet/microchip/fdma/Makefile b/drivers/net/ethernet/microchip/fdma/Makefile index cc9a736be357..eed4df6f7158 100644 --- a/drivers/net/ethernet/microchip/fdma/Makefile +++ b/drivers/net/ethernet/microchip/fdma/Makefile @@ -5,3 +5,7 @@ obj-$(CONFIG_FDMA) += fdma.o fdma-y += fdma_api.o + +ifdef CONFIG_MCHP_LAN966X_PCI +fdma-y += fdma_pci.o +endif diff --git a/drivers/net/ethernet/microchip/fdma/fdma_api.c b/drivers/net/ethernet/microchip/fdma/fdma_api.c index e78c3590da9e..072d36773835 100644 --- a/drivers/net/ethernet/microchip/fdma/fdma_api.c +++ b/drivers/net/ethernet/microchip/fdma/fdma_api.c @@ -127,6 +127,39 @@ void fdma_free_phys(struct fdma *fdma) } EXPORT_SYMBOL_GPL(fdma_free_phys); +#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI) +/* Allocate coherent DMA memory and map it in the ATU. */ +int fdma_alloc_coherent_and_map(struct device *dev, struct fdma *fdma, + struct fdma_pci_atu *atu) +{ + int err; + + err = fdma_alloc_coherent(dev, fdma); + if (err) + return err; + + fdma->atu_region = fdma_pci_atu_region_map(atu, + fdma->dma, + fdma->size); + + if (IS_ERR(fdma->atu_region)) { + fdma_free_coherent(dev, fdma); + return PTR_ERR(fdma->atu_region); + } + + return 0; +} +EXPORT_SYMBOL_GPL(fdma_alloc_coherent_and_map); + +/* Free coherent DMA memory and unmap the memory in the ATU. */ +void fdma_free_coherent_and_unmap(struct device *dev, struct fdma *fdma) +{ + fdma_pci_atu_region_unmap(fdma->atu_region); + fdma_free_coherent(dev, fdma); +} +EXPORT_SYMBOL_GPL(fdma_free_coherent_and_unmap); +#endif + /* Get the size of the FDMA memory */ u32 fdma_get_size(struct fdma *fdma) { diff --git a/drivers/net/ethernet/microchip/fdma/fdma_api.h b/drivers/net/ethernet/microchip/fdma/fdma_api.h index 94f1a6596097..0e0f8af7463f 100644 --- a/drivers/net/ethernet/microchip/fdma/fdma_api.h +++ b/drivers/net/ethernet/microchip/fdma/fdma_api.h @@ -7,6 +7,10 @@ #include #include +#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI) +#include "fdma_pci.h" +#endif + /* This provides a common set of functions and data structures for interacting * with the Frame DMA engine on multiple Microchip switchcores. * @@ -109,6 +113,11 @@ struct fdma { u32 channel_id; struct fdma_ops ops; + +#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI) + /* PCI ATU region for this FDMA instance. */ + struct fdma_pci_atu_region *atu_region; +#endif }; /* Advance the DCB index and wrap if required. */ @@ -234,9 +243,16 @@ int __fdma_dcb_add(struct fdma *fdma, int dcb_idx, u64 info, u64 status, int fdma_alloc_coherent(struct device *dev, struct fdma *fdma); int fdma_alloc_phys(struct fdma *fdma); +#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI) +int fdma_alloc_coherent_and_map(struct device *dev, struct fdma *fdma, + struct fdma_pci_atu *atu); +#endif void fdma_free_coherent(struct device *dev, struct fdma *fdma); void fdma_free_phys(struct fdma *fdma); +#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI) +void fdma_free_coherent_and_unmap(struct device *dev, struct fdma *fdma); +#endif u32 fdma_get_size(struct fdma *fdma); u32 fdma_get_size_contiguous(struct fdma *fdma); diff --git a/drivers/net/ethernet/microchip/fdma/fdma_pci.c b/drivers/net/ethernet/microchip/fdma/fdma_pci.c new file mode 100644 index 000000000000..d9d3b61a73ef --- /dev/null +++ b/drivers/net/ethernet/microchip/fdma/fdma_pci.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include +#include +#include +#include +#include +#include + +#include "fdma_pci.h" + +/* When the switch operates as a PCIe endpoint, the FDMA engine needs to + * DMA to/from host memory. The FDMA writes to addresses within the endpoint's + * internal Outbound (OB) address space, and the PCIe ATU translates these to + * DMA addresses on the PCIe bus, targeting host memory. + * + * The ATU supports up to six outbound regions. This implementation divides + * the OB address space into six equally sized chunks. + * + * +-------------+------------+------------+-----+------------+ + * | Index | Region 0 | Region 1 | ... | Region 5 | + * +-------------+------------+------------+-----+------------+ + * | Base addr | 0x10000000 | 0x12aa0000 | ... | 0x1d520000 | + * | Limit addr | 0x12a9ffff | 0x1553ffff | ... | 0x1ffbffff | + * | Target addr | host dma | host dma | ... | host dma | + * +-------------+------------+------------+-----+------------+ + * + * Base addr is the start address of the region within the OB address space. + * Limit addr is the end address of the region within the OB address space. + * Target addr is the host DMA address that the base addr translates to. + */ + +#define FDMA_PCI_ATU_REGION_ALIGN BIT(16) /* 64KB */ +#define FDMA_PCI_ATU_OB_START 0x10000000 +#define FDMA_PCI_ATU_OB_END 0x1fffffff + +#define FDMA_PCI_ATU_ADDR 0x300000 +#define FDMA_PCI_ATU_IDX_SIZE 0x200 +#define FDMA_PCI_ATU_ENA_REG 0x4 +#define FDMA_PCI_ATU_ENA_BIT BIT(31) +#define FDMA_PCI_ATU_LWR_BASE_ADDR 0x8 +#define FDMA_PCI_ATU_UPP_BASE_ADDR 0xc +#define FDMA_PCI_ATU_LIMIT_ADDR 0x10 +#define FDMA_PCI_ATU_LWR_TARGET_ADDR 0x14 +#define FDMA_PCI_ATU_UPP_TARGET_ADDR 0x18 + +static u32 fdma_pci_atu_region_size(void) +{ + return round_down((FDMA_PCI_ATU_OB_END - FDMA_PCI_ATU_OB_START) / + FDMA_PCI_ATU_REGION_MAX, FDMA_PCI_ATU_REGION_ALIGN); +} + +static void __iomem *fdma_pci_atu_addr_get(void __iomem *addr, int offset, + int idx) +{ + return addr + FDMA_PCI_ATU_ADDR + FDMA_PCI_ATU_IDX_SIZE * idx + offset; +} + +static void fdma_pci_atu_region_enable(struct fdma_pci_atu_region *region) +{ + writel(FDMA_PCI_ATU_ENA_BIT, + fdma_pci_atu_addr_get(region->atu->addr, FDMA_PCI_ATU_ENA_REG, + region->idx)); +} + +static void fdma_pci_atu_region_disable(struct fdma_pci_atu_region *region) +{ + writel(0, fdma_pci_atu_addr_get(region->atu->addr, FDMA_PCI_ATU_ENA_REG, + region->idx)); +} + +/* Configure the address translation in the ATU. */ +static void +fdma_pci_atu_configure_translation(struct fdma_pci_atu_region *region) +{ + struct fdma_pci_atu *atu = region->atu; + int idx = region->idx; + + writel(lower_32_bits(region->base_addr), + fdma_pci_atu_addr_get(atu->addr, + FDMA_PCI_ATU_LWR_BASE_ADDR, idx)); + + writel(upper_32_bits(region->base_addr), + fdma_pci_atu_addr_get(atu->addr, + FDMA_PCI_ATU_UPP_BASE_ADDR, idx)); + + /* Upper limit register only needed with REGION_SIZE > 4GB. */ + writel(region->limit_addr, + fdma_pci_atu_addr_get(atu->addr, FDMA_PCI_ATU_LIMIT_ADDR, idx)); + + writel(lower_32_bits(region->target_addr), + fdma_pci_atu_addr_get(atu->addr, + FDMA_PCI_ATU_LWR_TARGET_ADDR, idx)); + + writel(upper_32_bits(region->target_addr), + fdma_pci_atu_addr_get(atu->addr, + FDMA_PCI_ATU_UPP_TARGET_ADDR, idx)); +} + +/* Find an unused ATU region (target_addr == 0). */ +static struct fdma_pci_atu_region * +fdma_pci_atu_region_get_free(struct fdma_pci_atu *atu) +{ + struct fdma_pci_atu_region *regions = atu->regions; + + for (int i = 0; i < FDMA_PCI_ATU_REGION_MAX; i++) { + if (regions[i].target_addr) + continue; + + return ®ions[i]; + } + + return ERR_PTR(-ENOMEM); +} + +/* Unmap an ATU region, clearing its translation and disabling it. */ +void fdma_pci_atu_region_unmap(struct fdma_pci_atu_region *region) +{ + region->target_addr = 0; + + fdma_pci_atu_configure_translation(region); + fdma_pci_atu_region_disable(region); +} +EXPORT_SYMBOL_GPL(fdma_pci_atu_region_unmap); + +/* Map a host DMA address into a free outbound region. */ +struct fdma_pci_atu_region * +fdma_pci_atu_region_map(struct fdma_pci_atu *atu, u64 target_addr, int size) +{ + struct fdma_pci_atu_region *region; + + if (!atu) + return ERR_PTR(-EINVAL); + + if (size > fdma_pci_atu_region_size()) + return ERR_PTR(-E2BIG); + + region = fdma_pci_atu_region_get_free(atu); + if (IS_ERR(region)) + return region; + + region->target_addr = target_addr; + + /* Enable first, according to datasheet section 3.24.7.4.1 */ + fdma_pci_atu_region_enable(region); + fdma_pci_atu_configure_translation(region); + + return region; +} +EXPORT_SYMBOL_GPL(fdma_pci_atu_region_map); + +/* Translate a host DMA address to the corresponding OB address. */ +u64 fdma_pci_atu_translate_addr(struct fdma_pci_atu_region *region, u64 addr) +{ + return region->base_addr + (addr - region->target_addr); +} +EXPORT_SYMBOL_GPL(fdma_pci_atu_translate_addr); + +/* Initialize ATU, dividing the OB space into equally sized regions. */ +void fdma_pci_atu_init(struct fdma_pci_atu *atu, void __iomem *addr) +{ + struct fdma_pci_atu_region *regions = atu->regions; + u32 region_size = fdma_pci_atu_region_size(); + + atu->addr = addr; + + for (int i = 0; i < FDMA_PCI_ATU_REGION_MAX; i++) { + regions[i].base_addr = + FDMA_PCI_ATU_OB_START + (i * region_size); + regions[i].limit_addr = + regions[i].base_addr + region_size - 1; + regions[i].idx = i; + regions[i].atu = atu; + } +} +EXPORT_SYMBOL_GPL(fdma_pci_atu_init); diff --git a/drivers/net/ethernet/microchip/fdma/fdma_pci.h b/drivers/net/ethernet/microchip/fdma/fdma_pci.h new file mode 100644 index 000000000000..359950ccabac --- /dev/null +++ b/drivers/net/ethernet/microchip/fdma/fdma_pci.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef _FDMA_PCI_H_ +#define _FDMA_PCI_H_ + +#include + +#define FDMA_PCI_ATU_REGION_MAX 6 +#define FDMA_PCI_DB_ALIGN 128 +#define FDMA_PCI_DB_SIZE(mtu) ALIGN(mtu, FDMA_PCI_DB_ALIGN) + +struct fdma_pci_atu; + +struct fdma_pci_atu_region { + struct fdma_pci_atu *atu; + u64 base_addr; /* Base addr of the OB window */ + u64 limit_addr; /* Limit addr of the OB window */ + u64 target_addr; /* Host DMA address this region maps to */ + int idx; +}; + +struct fdma_pci_atu { + void __iomem *addr; + struct fdma_pci_atu_region regions[FDMA_PCI_ATU_REGION_MAX]; +}; + +/* Initialize ATU, dividing OB space into regions. */ +void fdma_pci_atu_init(struct fdma_pci_atu *atu, void __iomem *addr); + +/* Unmap an ATU region, clearing its translation and disabling it. */ +void fdma_pci_atu_region_unmap(struct fdma_pci_atu_region *region); + +/* Map a host DMA address into a free ATU region. */ +struct fdma_pci_atu_region *fdma_pci_atu_region_map(struct fdma_pci_atu *atu, + u64 target_addr, + int size); + +/* Translate a host DMA address to the OB address space. */ +u64 fdma_pci_atu_translate_addr(struct fdma_pci_atu_region *region, u64 addr); + +#endif -- 2.34.1