mtd: spi-nor: everspin: implement 200MHz Octal-STR mode for EM008LX

This patch enables high-speed Octal-STR (8s-8s-8s) operation at 200MHz
for the Everspin EM008LX MRAM series.

To maintain reliable boot-up, the device is identified at a safe
frequency. Upon successful JEDEC ID verification, the driver escalates
the clock frequency to 200MHz and synchronizes the dummy cycles.

Key Improvements:
- Added frequency escalation in params->setup() hook to 200MHz.
- Implemented Octal register access (0x85/0x81) for configuration.
- Synchronized dummy cycles to 20, as required for 200MHz operation.
- Validated with Cadence OSPI controller using a 400MHz reference clock
  and a read-capture delay of 3-5 cycles.

The resulting configuration achieves the maximum specified throughput
of the Everspin MRAM while maintaining data integrity.

Signed-off-by: Heinrich Toews <ht@twx-software.de>
This commit is contained in:
Heinrich Toews
2026-02-24 12:27:29 +01:00
parent 1d2ed385ff
commit f3d8580745
2 changed files with 150 additions and 38 deletions
@@ -183,6 +183,9 @@
pinctrl-0 = <&ospi0_pins_default>;
status = "okay";
/* Setup the clock for 200 MHz (400 MHz Ref / 2) */
assigned-clock-rates = <400000000>;
/* Everspin Tech. EM008LXO
* Order-No EM008LXOAB320IS1R
*/
@@ -196,21 +199,24 @@
compatible = "jedec,spi-nor";
reg = <0x0>;
/* 80 MHz Target */
spi-max-frequency = <80000000>;
/* 50 MHz for Startup */
spi-max-frequency = <50000000>;
/* PHY mode is highly recommended for >50MHz on Cadence controllers */
/* 200 MHz needs a very precise sampling point.
* Start with 4 or 5 since 125MHz already needed 3.
*/
cdns,read-delay = <5>;
/* Enable PHY mode to assist controller internal clocking at
* high speeds
*/
cdns,phy-mode;
/* Read delay: Start with 0.
If you get bit errors, try cdns,read-delay = <1>; */
cdns,read-delay = <0>;
/* Tighten timings for 80MHz operation (T=12.5ns) */
cdns,tshsl-ns = <25>; /* Chip Select high pulse width */
cdns,tsd2d-ns = <25>; /* Delay between two back-to-back transfers */
cdns,tchsh-ns = <6>; /* CS# hold time */
cdns,tslch-ns = <6>; /* CS# setup time */
/* Tighten timings for 200 MHz (T=5ns) */
cdns,tshsl-ns = <200>; /* CS# high pulse width (increased for stability) */
cdns,tsd2d-ns = <200>; /* Delay between back-to-back transfers */
cdns,tchsh-ns = <3>; /* CS# hold time */
cdns,tslch-ns = <3>; /* CS# setup time */
spi-rx-bus-width = <8>;
spi-tx-bus-width = <8>;
+132 -26
View File
@@ -9,10 +9,19 @@
#include <linux/spi/spi-mem.h>
#include "core.h"
#define SPINOR_OP_MT_WR_ANY_REG 0x81 /* Write volatile configuration register */
#define SPINOR_REG_MT_CFR0V 0x00 /* Address for Mode Configuration */
#define SPINOR_REG_MT_CFR1V 0x01 /* Address for Dummy Cycle Configuration */
#define SPINOR_MT_OCT_STR 0xB7 /* Enable Octal STR mode with DS */
/* Optimization for 200 MHz: 20 Dummy Cycles are typically required */
#define EVERSPIN_MRAM_DUMMY_CYCLES 8
#define EVERSPIN_MRAM_DUMMY_CYCLES_FAST 20
#define EVERSPIN_MRAM_SPEED_MHZ 200000000
#define SPINOR_OP_MT_RD_ANY_REG 0x85 /* Read volatile configuration register */
#define SPINOR_OP_MT_WR_ANY_REG 0x81 /* Write volatile configuration register */
#define SPINOR_REG_MT_CFR0V 0x00 /* Address for Mode Configuration */
#define SPINOR_REG_MT_CFR1V 0x01 /* Address for Dummy Cycle Configuration */
#define SPINOR_MT_OCT_STR 0xB7 /* Enable Octal STR mode with DS */
#define SPINOR_OP_EV_OCTAL_FAST_READ 0xCB /* Everspin Octal Fast Read (8-8-8) */
#define SPINOR_OP_EV_OCTAL_PAGE_PROG 0x82 /* Everspin Octal Page Program (8-8-8) */
/**
* everspin_mram_software_reset - Software Reset in Single-SPI mode
@@ -22,8 +31,8 @@ static int everspin_mram_software_reset(struct spi_nor *nor)
struct spi_mem_op op;
int ret;
/* Software Reset Enable (0x66) */
op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(0x66, 1),
/* Software Reset Enable (SPINOR_OP_SRSTEN: 0x66) */
op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRSTEN, 1),
SPI_MEM_OP_NO_ADDR,
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_NO_DATA);
@@ -32,8 +41,8 @@ static int everspin_mram_software_reset(struct spi_nor *nor)
if (ret)
return ret;
/* Software Reset (0x99) */
op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(0x99, 1),
/* Software Reset (SPINOR_OP_SRST: 0x99) */
op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_SRST, 1),
SPI_MEM_OP_NO_ADDR,
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_NO_DATA);
@@ -63,6 +72,47 @@ static int everspin_mram_write_reg(struct spi_nor *nor, u32 addr, u8 val)
return spi_nor_write_any_volatile_reg(nor, &op, SNOR_PROTO_1_1_1);
}
/**
* everspin_mram_read_reg_octal - Reads a register in 8s-8s-8s mode
* Uses Opcode 0x85, 3-byte address, and 8 dummy cycles.
*/
static int everspin_mram_read_reg_octal(struct spi_nor *nor, u32 addr, u8 *val)
{
struct spi_mem_op op =
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_RD_ANY_REG, 8),
SPI_MEM_OP_ADDR(3, addr, 8),
SPI_MEM_OP_DUMMY(8, 8), /* Fixed 8 dummies for reg read */
SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 8));
int ret;
ret = spi_nor_read_any_reg(nor, &op, SNOR_PROTO_8_8_8);
if (ret)
return ret;
*val = nor->bouncebuf[0];
return 0;
}
/**
* everspin_mram_write_reg_octal - Writes a register in 8s-8s-8s mode
* Uses Opcode 0x81, 3-byte address, and 0 dummy cycles.
*/
static int everspin_mram_write_reg_octal(struct spi_nor *nor, u32 addr, u8 val)
{
struct spi_mem_op op =
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_MT_WR_ANY_REG, 8),
SPI_MEM_OP_ADDR(3, addr, 8),
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 8));
nor->bouncebuf[0] = val;
/* WREN is required before writing to volatile registers */
spi_nor_write_enable(nor);
return spi_nor_write_any_volatile_reg(nor, &op, SNOR_PROTO_8_8_8);
}
/**
* everspin_mram_unlock - Clears Block Protection bits
*/
@@ -92,7 +142,8 @@ static void everspin_mram_default_init(struct spi_nor *nor)
*/
everspin_mram_software_reset(nor);
dev_info(nor->dev, "Starting Everspin MRAM initialization ...\n");
dev_info(nor->dev, "Starting Everspin MRAM initialization (Octal-STR, %d Dummies) ...\n",
EVERSPIN_MRAM_DUMMY_CYCLES);
/* Initial SR1 Check */
op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 1),
@@ -108,8 +159,8 @@ static void everspin_mram_default_init(struct spi_nor *nor)
everspin_mram_unlock(nor);
}
/* Configuration: 8 dummy bytes */
everspin_mram_write_reg(nor, SPINOR_REG_MT_CFR1V, 8);
/* Configuration: Set Dummy Cycles in CFR1V */
everspin_mram_write_reg(nor, SPINOR_REG_MT_CFR1V, EVERSPIN_MRAM_DUMMY_CYCLES);
/* Verify WEL */
ret = spi_nor_read_any_reg(nor, &op, SNOR_PROTO_1_1_1);
@@ -127,8 +178,8 @@ static void everspin_mram_default_init(struct spi_nor *nor)
/* Address: None */
SPI_MEM_OP_NO_ADDR,
/* Dummy: 8 cycles, sent on 8 lanes (as per your table) */
SPI_MEM_OP_DUMMY(8, 8),
/* EVERSPIN_MRAM_DUMMY_CYCLES cycles, sent on 8 lanes */
SPI_MEM_OP_DUMMY(EVERSPIN_MRAM_DUMMY_CYCLES, 8),
/* Data: 1 byte (Status), received on 8 lanes */
SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 8)
@@ -152,8 +203,8 @@ static void everspin_mram_default_init(struct spi_nor *nor)
/* No address phase */
SPI_MEM_OP_NO_ADDR,
/* 8 Dummy Cycles, sent on 8 lanes */
SPI_MEM_OP_DUMMY(8, 8),
/* EVERSPIN_MRAM_DUMMY_CYCLES Dummy Cycles, sent on 8 lanes */
SPI_MEM_OP_DUMMY(EVERSPIN_MRAM_DUMMY_CYCLES, 8),
/* Read 3 bytes of ID data, received on 8 lanes */
SPI_MEM_OP_DATA_IN(3, nor->bouncebuf, 8)
@@ -183,6 +234,55 @@ static int everspin_mram_ready_noop(struct spi_nor *nor)
return 1;
}
/**
* everspin_mram_setup - Finalizes the SPI setup and escalates frequency
* @nor: pointer to 'struct spi_nor'
* @hwcaps: pointer to 'struct spi_nor_hwcaps'
*
* This hook is called after default_init/late_init but before the first
* data transfer. It is the ideal place to switch from safe scan frequency
* to high-performance operational frequency.
*/
static int everspin_mram_setup(struct spi_nor *nor,
const struct spi_nor_hwcaps *hwcaps)
{
struct spi_nor_flash_parameter *params = nor->params;
struct spi_device *spi = nor->spimem->spi;
int ret;
u8 val = 0;
/*
* Request high speed frequency.
*/
spi->max_speed_hz = EVERSPIN_MRAM_SPEED_MHZ;
dev_info(nor->dev, "Escalating speed to %u Hz in setup hook\n",
spi->max_speed_hz);
/* Update Dummy Bytes to serve the higher rate EVERSPIN_MRAM_SPEED_MHZ */
nor->read_dummy = EVERSPIN_MRAM_DUMMY_CYCLES_FAST;
spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ], 0,
EVERSPIN_MRAM_DUMMY_CYCLES_FAST,
SPINOR_OP_EV_OCTAL_FAST_READ, SNOR_PROTO_8_8_8);
everspin_mram_write_reg_octal(nor, SPINOR_REG_MT_CFR1V,
EVERSPIN_MRAM_DUMMY_CYCLES_FAST);
everspin_mram_read_reg_octal(nor, SPINOR_REG_MT_CFR1V, &val);
dev_info(nor->dev, "Updating Dummy Bytes to %d (read: %d)\n", nor->read_dummy, val);
/*
* Force the controller driver (cadence-qspi) to re-run
* cqspi_config_baudrate_div() and update hardware registers.
*/
ret = spi_setup(spi);
if (ret) {
dev_err(nor->dev, "Failed to update SPI setup (%d)\n", ret);
return ret;
}
return 0;
}
/**
* everspin_mram_late_init - Final 8-8-8 STR configuration for EM008LXO
*/
@@ -192,33 +292,39 @@ static int everspin_mram_late_init(struct spi_nor *nor)
dev_info(nor->dev, "Finalizing 8s-8s-8s STR: Write/Read fully functional.\n");
/* 1. Bypass core hwcaps restrictions */
/* Bypass core hwcaps restrictions.
* We need this as a workaround.
*/
params->hwcaps.mask |= SNOR_HWCAPS_READ | SNOR_HWCAPS_PP;
/* 2. Global Octal STR Protocol Settings */
/* Global Octal STR Protocol Settings */
nor->read_proto = SNOR_PROTO_8_8_8;
nor->write_proto = SNOR_PROTO_8_8_8;
nor->reg_proto = SNOR_PROTO_8_8_8;
nor->read_opcode = 0xCB;
nor->read_dummy = 8;
nor->program_opcode = 0x82;
nor->read_opcode = SPINOR_OP_EV_OCTAL_FAST_READ;
/* Set global read dummy cycles */
nor->read_dummy = EVERSPIN_MRAM_DUMMY_CYCLES;
nor->program_opcode = SPINOR_OP_EV_OCTAL_PAGE_PROG;
nor->addr_nbytes = 3;
params->addr_nbytes = 3;
/* 3. Disable WIP Polling (Fixes the 40s Timeout) */
/* Disable WIP Polling (Fixes the 40s Timeout) */
params->ready = everspin_mram_ready_noop;
/* 4. Align Page Size for Controller Stability */
/* Align Page Size for Controller Stability */
/* Cadence OSPI handles 256-byte pages more reliably in Octal mode */
params->page_size = 256;
/* 5. Map Opcodes to standard slots for MTD core */
spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ],
0, 8, 0xCB, SNOR_PROTO_8_8_8);
/* Map Opcodes to standard slots for MTD core */
spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ], 0,
EVERSPIN_MRAM_DUMMY_CYCLES,
SPINOR_OP_EV_OCTAL_FAST_READ, SNOR_PROTO_8_8_8);
spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP],
0x82, SNOR_PROTO_8_8_8);
SPINOR_OP_EV_OCTAL_PAGE_PROG, SNOR_PROTO_8_8_8);
nor->params->setup = everspin_mram_setup;
return 0;
}