From 98b840c366d12b8ea3f6f8f462cdfef91b6ebff4 Mon Sep 17 00:00:00 2001 From: Heinrich Toews Date: Tue, 7 Apr 2026 16:35:48 +0200 Subject: [PATCH] mtd: spi-nor: everspin: enable mmap() for MRAM via Cadence OSPI DAC window The Cadence OSPI controller (ti,am654-ospi) provides a Direct Access Controller (DAC) mode: the flash/MRAM address space is visible as a contiguous window in the CPU's physical address space via the AHB bus (resource[1] in the platform device, base address ~0x60000000 on AM62x). When DAC mode is active the driver already uses memcpy_fromio() / memcpy_toio() against this window for all data transfers. There is therefore a 1:1 mapping between MTD offsets and physical addresses: phys_addr = res_ahb->start + mtd_offset Wire this up to allow user-space mmap() on /dev/mtd0: 1. Allocate a struct everspin_mram_priv (contains an embedded struct map_info) via devm_kzalloc() in the late_init fixup hook. 2. Read the AHB window physical base address from the controller's platform_device resource[1] using platform_get_resource(). This is the same resource that cqspi_probe() uses for ioremap, so it is always valid when DAC mode is active. 3. Fill in map_info.phys and map_info.size, store it in nor->priv and point mtd->priv at it. 4. Set mtd->type = MTD_RAM and mtd->flags = MTD_CAP_RAM so that mtdchar_mmap() takes the vm_iomap_memory() path introduced in the companion patch 'mtd: mtdchar: enable mmap() for MTD_RAM devices with a physical map'. If the AHB resource is not found (e.g. non-Cadence controller) the function logs a warning and continues without mmap support; all other functionality is unaffected. After this change user-space can do: int fd = open("/dev/mtd0", O_RDWR | O_SYNC); void *p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); and get direct access to the MRAM without any kernel copy overhead. Tested on AM6232 (PFC-750-8xxx) with Everspin EM008LXO in Octal-STR (8-8-8) mode at 200 MHz. Signed-off-by: Heinrich Toews --- drivers/mtd/spi-nor/everspin.c | 64 ++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/drivers/mtd/spi-nor/everspin.c b/drivers/mtd/spi-nor/everspin.c index 96eda6e3dc5c..7de8f6c2ba53 100644 --- a/drivers/mtd/spi-nor/everspin.c +++ b/drivers/mtd/spi-nor/everspin.c @@ -5,10 +5,18 @@ */ #include +#include #include +#include +#include #include #include "core.h" +/* Private data stored in nor->priv for MRAM mmap support */ +struct everspin_mram_priv { + struct map_info map; +}; + /* Optimization for 200 MHz: 20 Dummy Cycles are typically required */ #define EVERSPIN_MRAM_DUMMY_CYCLES 8 #define EVERSPIN_MRAM_DUMMY_CYCLES_FAST 20 @@ -289,6 +297,9 @@ static int everspin_mram_setup(struct spi_nor *nor, static int everspin_mram_late_init(struct spi_nor *nor) { struct spi_nor_flash_parameter *params = nor->params; + struct platform_device *ctlr_pdev; + struct everspin_mram_priv *priv; + struct resource *res_ahb; dev_info(nor->dev, "Finalizing 8s-8s-8s STR: Write/Read fully functional.\n"); @@ -326,6 +337,59 @@ static int everspin_mram_late_init(struct spi_nor *nor) nor->params->setup = everspin_mram_setup; + /* + * Set up MTD_RAM so that /dev/mtd0 supports mmap(). + * + * The Cadence OSPI controller exposes the flash memory as a directly + * accessible window in the CPU address space (DAC / AHB mode). We + * read the physical base address of that window from the controller's + * platform_device resource[1] – the same resource that cqspi_probe() + * uses for ioremap. This lets mtdchar_mmap() call vm_iomap_memory() + * and hand the physical pages directly to user-space. + */ + priv = devm_kzalloc(nor->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* + * nor->spimem->spi->controller->dev.parent is the platform_device + * of the Cadence QSPI controller. Its IORESOURCE_MEM[1] is the AHB + * (DAC) window – index 0 is the APB register block. + */ + ctlr_pdev = to_platform_device(nor->spimem->spi->controller->dev.parent); + res_ahb = platform_get_resource(ctlr_pdev, IORESOURCE_MEM, 1); + if (!res_ahb) { + dev_warn(nor->dev, + "MRAM: cannot find AHB resource – mmap() disabled\n"); + devm_kfree(nor->dev, priv); + return 0; + } + + priv->map.name = nor->mtd.name; + priv->map.phys = res_ahb->start; + priv->map.size = resource_size(res_ahb); + priv->map.virt = NULL; /* not needed for mmap path */ + + nor->priv = priv; + + /* + * Advertise this device as byte-addressable RAM so that + * mtdchar_mmap() takes the vm_iomap_memory() path. + * MTD_CAP_RAM = MTD_WRITEABLE | MTD_BIT_WRITEABLE | MTD_NO_ERASE + */ + nor->mtd.type = MTD_RAM; + nor->mtd.flags = MTD_CAP_RAM; + + /* + * mtdchar_mmap() casts mtd->priv to struct map_info * when + * mtd->type == MTD_RAM. Point it at our embedded map_info. + */ + nor->mtd.priv = &priv->map; + + dev_info(nor->dev, + "MRAM: AHB window at %pa size %pa – mmap() enabled\n", + &res_ahb->start, &priv->map.size); + return 0; }