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 <ht@twx-software.de>
This commit is contained in:
Heinrich Toews
2026-04-07 16:35:48 +02:00
parent 1259dde287
commit 98b840c366
+64
View File
@@ -5,10 +5,18 @@
*/
#include <linux/mtd/spi-nor.h>
#include <linux/mtd/map.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
#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;
}