Merge pull request #419 from BU-Automation/topic/ht/pfc400/mram-em008lxo-rebase

Add full MRAM STR (8s-8s-8s) Support with 200 Mhz Clock Speed
This commit is contained in:
Oleg KARFICH
2026-02-25 15:21:40 +01:00
committed by GitHub Enterprise
4 changed files with 442 additions and 18 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
*/
@@ -195,17 +198,29 @@
compatible = "jedec,spi-nor";
reg = <0x0>;
spi-tx-bus-width = <8>;
spi-rx-bus-width = <8>;
spi-max-frequency = <25000000>;
cdns,tshsl-ns = <60>;
cdns,tsd2d-ns = <60>;
cdns,tchsh-ns = <60>;
cdns,tslch-ns = <60>;
cdns,read-delay = <4>;
/* 50 MHz for Startup */
spi-max-frequency = <50000000>;
/* 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;
/* 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>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
@@ -213,7 +228,7 @@
partition@0 {
label = "ospi.mram.mem";
reg = <0x0 0x800000>;
reg = <0x0 0x100000>; /* 1MB total capacity */
};
};
};
+63 -2
View File
@@ -3442,13 +3442,74 @@ static void spi_nor_set_mtd_info(struct spi_nor *nor)
mtd->_put_device = spi_nor_put_device;
}
/**
* spi_nor_everspin_reboot_fix - Forces Everspin MRAM out of Octal mode
* @nor: pointer to 'struct spi_nor'
*
* This function sends a "blind" Software Reset sequence in Octal-STR (8-8-8).
* It is required for warm reboots because the MRAM stays in Octal mode
* while the controller restarts in Single-SPI mode.
*/
/**
* spi_nor_everspin_reboot_fix - Blind reset for MRAM Octal-STR escape
* @nor: pointer to 'struct spi_nor'
*/
static void spi_nor_everspin_reboot_fix(struct spi_nor *nor)
{
struct spi_mem_op op =
SPI_MEM_OP(SPI_MEM_OP_CMD(0x66, 8),
SPI_MEM_OP_NO_ADDR,
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_NO_DATA);
int ret;
if (!nor->spimem || !nor->dev)
return;
/*
* Logging as dev_info so it appears in dmesg during boot.
* This helps verify if the fix is being executed.
*/
dev_info(nor->dev, "MRAM Reboot-Fix: Sending blind Octal-STR reset (0x66->0x99)...\n");
/* 1. Reset Enable (0x66) in Octal-STR */
ret = spi_mem_exec_op(nor->spimem, &op);
/* 2. Reset (0x99) in Octal-STR */
op.cmd.opcode = 0x99;
ret |= spi_mem_exec_op(nor->spimem, &op);
if (ret) {
/*
* Note: A failure here is expected if the controller is
* not yet ready for 8-lane commands or if the chip
* is already in Single-SPI mode.
*/
dev_dbg(nor->dev, "MRAM Reboot-Fix: Octal reset op returned %d (normal for Single-SPI)\n", ret);
}
/* Essential delay: allow MRAM internal state machine to recover */
usleep_range(1000, 2000);
dev_info(nor->dev, "MRAM Reboot-Fix: Completed. Chip should be in Single-SPI mode now.\n");
}
static int spi_nor_hw_reset(struct spi_nor *nor)
{
struct gpio_desc *reset;
reset = devm_gpiod_get_optional(nor->dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR_OR_NULL(reset))
return PTR_ERR_OR_ZERO(reset);
if (IS_ERR(reset))
return PTR_ERR(reset);
if (!reset) {
/*
* FALLBACK: No dedicated reset-pin found in DT.
* Execute the blind Octal-STR reset to recover from warm reboots.
*/
spi_nor_everspin_reboot_fix(nor);
return 0;
}
/*
* Experimental delay values by looking at different flash device
+339 -7
View File
@@ -5,9 +5,335 @@
*/
#include <linux/mtd/spi-nor.h>
#include <linux/delay.h>
#include <linux/spi/spi-mem.h>
#include "core.h"
/* 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
*/
static int everspin_mram_software_reset(struct spi_nor *nor)
{
struct spi_mem_op op;
int ret;
/* 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);
ret = spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto);
if (ret)
return ret;
/* 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);
ret = spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto);
if (ret)
return ret;
udelay(100);
return 0;
}
/**
* everspin_mram_write_reg - Writes to configuration registers (4-byte addr)
*/
static int everspin_mram_write_reg(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, 1),
SPI_MEM_OP_ADDR(3, addr, 1),
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1));
nor->bouncebuf[0] = val;
spi_nor_write_enable(nor);
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
*/
static int everspin_mram_unlock(struct spi_nor *nor)
{
struct spi_mem_op op;
spi_nor_write_enable(nor);
nor->bouncebuf[0] = 0x00;
op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 1),
SPI_MEM_OP_NO_ADDR,
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_OUT(1, nor->bouncebuf, 1));
return spi_nor_write_any_volatile_reg(nor, &op, nor->reg_proto);
}
static void everspin_mram_default_init(struct spi_nor *nor)
{
struct spi_mem_op op;
struct spi_mem_op op_ri; /* for read id */
int ret;
/* Do a Software Reset to get sure we are in a clean state.
* Not necessary if a hardware reset is already done in NOR layer.
*/
everspin_mram_software_reset(nor);
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),
SPI_MEM_OP_NO_ADDR,
SPI_MEM_OP_NO_DUMMY,
SPI_MEM_OP_DATA_IN(1, nor->bouncebuf, 1));
ret = spi_nor_read_any_reg(nor, &op, SNOR_PROTO_1_1_1);
if (!ret) {
u8 sr1 = nor->bouncebuf[0];
dev_info(nor->dev, "Initial SR1: 0x%02x (BP-Bits: 0x%x)\n", sr1, (sr1 & 0x3c) >> 2);
if (sr1 & GENMASK(5, 2))
everspin_mram_unlock(nor);
}
/* 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);
if (!ret)
dev_info(nor->dev, "SR1 after config: 0x%02x (WEL should be 0x00)\n", nor->bouncebuf[0]);
/* Setup Octal-STR with DS */
everspin_mram_write_reg(nor, SPINOR_REG_MT_CFR0V, SPINOR_MT_OCT_STR);
/* Status Register Read (05h) in Octal STR Mode (8s-0-8s) with 8 Dummies */
struct spi_mem_op op_rsr = SPI_MEM_OP(
/* Command: Opcode 05h, sent on 8 lanes */
SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 8),
/* Address: None */
SPI_MEM_OP_NO_ADDR,
/* 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)
);
/* Execute using the framework helper with Octal-STR protocol */
ret = spi_nor_read_any_reg(nor, &op_rsr, SNOR_PROTO_8_8_8);
if (!ret) {
/* Access the result from the DMA-safe bounce buffer */
u8 status = nor->bouncebuf[0];
dev_info(nor->dev, "MRAM Status Register (8s-0-8s): 0x%02x\n", status);
}
dev_info(nor->dev, "Going to access Read ID (0x9f) in 8s-0-8s Mode ...\n");
/* Read ID (0x9f) access in Octal-STR mode (8s-0-8s) with 8 Dummy Bytes */
op_ri = (struct spi_mem_op)SPI_MEM_OP(
/* Opcode 0x9F, 1 Byte long, sent on 8 lanes */
SPI_MEM_OP_CMD(SPINOR_OP_RDID, 8),
/* No address phase */
SPI_MEM_OP_NO_ADDR,
/* 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)
);
/* Protokoll 8-8-8 für 8s-0-8s (Octal STR) */
ret = spi_nor_read_any_reg(nor, &op_ri, SNOR_PROTO_8_8_8);
if (!ret) {
/* Access the 3 bytes from bouncebuf */
u8 manufacturer_id = nor->bouncebuf[0];
u8 memory_type = nor->bouncebuf[1];
u8 capacity = nor->bouncebuf[2];
/* Log the result in hex format */
dev_info(nor->dev, "MRAM INFO: Octal Read ID: %02x %02x %02x.\n",
manufacturer_id, memory_type, capacity);
} else {
dev_info(nor->dev, "Read ID Access in 8s-0-8s mode FAILED!\n");
}
}
/**
* everspin_mram_ready_noop - MRAM is always ready, no polling needed
*/
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
*/
static int everspin_mram_late_init(struct spi_nor *nor)
{
struct spi_nor_flash_parameter *params = nor->params;
dev_info(nor->dev, "Finalizing 8s-8s-8s STR: Write/Read fully functional.\n");
/* Bypass core hwcaps restrictions.
* We need this as a workaround.
*/
params->hwcaps.mask |= SNOR_HWCAPS_READ | SNOR_HWCAPS_PP;
/* 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 = 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;
/* Disable WIP Polling (Fixes the 40s Timeout) */
params->ready = everspin_mram_ready_noop;
/* Align Page Size for Controller Stability */
/* Cadence OSPI handles 256-byte pages more reliably in Octal mode */
params->page_size = 256;
/* 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],
SPINOR_OP_EV_OCTAL_PAGE_PROG, SNOR_PROTO_8_8_8);
nor->params->setup = everspin_mram_setup;
return 0;
}
static const struct spi_nor_fixups everspin_mram_fixups = {
.default_init = everspin_mram_default_init,
.late_init = everspin_mram_late_init,
};
static const struct flash_info everspin_nor_parts[] = {
/* Everspin */
{ "mr25h128", CAT25_INFO(16 * 1024, 1, 256, 2) },
@@ -19,22 +345,28 @@ static const struct flash_info everspin_nor_parts[] = {
static const struct flash_info everspin_mram_parts[] = {
/* Everspin */
{ "em256lx", INFO(0x6bbb19, 0, 32 * 1024 * 1024, 1)
FLAGS(SPI_NOR_NO_ERASE)
FLAGS(SPI_NOR_NO_ERASE)
.fixups = &everspin_mram_fixups,
},
{ "em128lx", INFO(0x6bbb18, 0, 16 * 1024 * 1024, 1)
FLAGS(SPI_NOR_NO_ERASE)
FLAGS(SPI_NOR_NO_ERASE)
.fixups = &everspin_mram_fixups,
},
{ "em064lx", INFO(0x6bbb17, 0, 8 * 1024 * 1024, 1)
FLAGS(SPI_NOR_NO_ERASE)
FLAGS(SPI_NOR_NO_ERASE)
.fixups = &everspin_mram_fixups,
},
{ "em032lx", INFO(0x6bbb16, 0, 4 * 1024 * 1024, 1)
FLAGS(SPI_NOR_NO_ERASE)
FLAGS(SPI_NOR_NO_ERASE)
.fixups = &everspin_mram_fixups,
},
{ "em016lx", INFO(0x6bbb15, 0, 2 * 1024 * 1024, 1)
FLAGS(SPI_NOR_NO_ERASE)
FLAGS(SPI_NOR_NO_ERASE)
.fixups = &everspin_mram_fixups,
},
{ "em008lx", INFO(0x6bbb14, 0, 1 * 1024 * 1024, 1)
FLAGS(SPI_NOR_NO_ERASE)
FLAGS(SPI_NOR_NO_ERASE)
.fixups = &everspin_mram_fixups,
},
};
+16
View File
@@ -1195,6 +1195,7 @@ static void cqspi_config_baudrate_div(struct cqspi_st *cqspi)
const unsigned int ref_clk_hz = cqspi->master_ref_clk_hz;
void __iomem *reg_base = cqspi->iobase;
u32 reg, div;
u32 actual_clk;
/* Recalculate the baudrate divisor based on QSPI specification. */
div = DIV_ROUND_UP(ref_clk_hz, 2 * cqspi->sclk) - 1;
@@ -1207,10 +1208,25 @@ static void cqspi_config_baudrate_div(struct cqspi_st *cqspi)
cqspi->sclk, ref_clk_hz/((div+1)*2));
}
/* Calculate actual clock for logging purposes */
actual_clk = ref_clk_hz / (2 * (div + 1));
dev_info(&cqspi->pdev->dev,
"CQSPI Clock Config: Ref=%u Hz, Target=%u Hz, Divisor=%u, Result=%u Hz\n",
ref_clk_hz, cqspi->sclk, div, actual_clk);
reg = readl(reg_base + CQSPI_REG_CONFIG);
/* Log old register value for deep debugging */
dev_info(&cqspi->pdev->dev, "Old CONFIG_REG: 0x%08x\n", reg);
reg &= ~(CQSPI_REG_CONFIG_BAUD_MASK << CQSPI_REG_CONFIG_BAUD_LSB);
reg |= (div & CQSPI_REG_CONFIG_BAUD_MASK) << CQSPI_REG_CONFIG_BAUD_LSB;
writel(reg, reg_base + CQSPI_REG_CONFIG);
dev_info(&cqspi->pdev->dev, "New CONFIG_REG: 0x%08x (BaudDiv field updated)\n",
readl(reg_base + CQSPI_REG_CONFIG));
}
static void cqspi_readdata_capture(struct cqspi_st *cqspi,