From 133e49bfc4ff65a5d1afe3a10fd4f391c876181a Mon Sep 17 00:00:00 2001 From: Heinrich Toews Date: Tue, 17 Feb 2026 12:22:47 +0100 Subject: [PATCH] mtd: spi-nor: everspin: refactor initialization and enable Octal STR mode Refactor the Everspin MRAM initialization sequence to support transition into Octal STR (8s-0-8s) mode. The following changes are implemented: - Add everspin_mram_software_reset() to ensure the device is in a known state (Single-SPI) before configuration. - Update register addresses and opcodes to align with the datasheet and legacy driver requirements (e.g., using 0x81 for WRAR). - Refactor everspin_mram_write_reg() to use SNOR_PROTO_1_1_1 for initial configuration and ensure Write Enable (WREN) is issued. - Implement Octal STR mode activation by configuring CFR1V (Dummy Cycles) and CFR0V (Mode). - Add verification steps by reading the Status Register and ID in SNOR_PROTO_8_8_8 mode with appropriate dummy cycles. This ensures the MRAM is correctly detected and switched to high-speed octal operation during the probe phase. Signed-off-by: Heinrich Toews --- drivers/mtd/spi-nor/everspin.c | 240 ++++++++++++++++++++++++++++++++- 1 file changed, 233 insertions(+), 7 deletions(-) diff --git a/drivers/mtd/spi-nor/everspin.c b/drivers/mtd/spi-nor/everspin.c index d18d23ee7c9e..de8f391a53d4 100644 --- a/drivers/mtd/spi-nor/everspin.c +++ b/drivers/mtd/spi-nor/everspin.c @@ -5,9 +5,229 @@ */ #include - +#include +#include #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 */ + +/** + * 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 (0x66) */ + op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(0x66, 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 (0x99) */ + op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(0x99, 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_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 ...\n"); + + /* 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: 8 dummy bytes */ + everspin_mram_write_reg(nor, SPINOR_REG_MT_CFR1V, 8); + + /* 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, + + /* Dummy: 8 cycles, sent on 8 lanes (as per your table) */ + SPI_MEM_OP_DUMMY(8, 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, + + /* 8 Dummy Cycles, sent on 8 lanes */ + SPI_MEM_OP_DUMMY(8, 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_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"); + + /* 1. Bypass core hwcaps restrictions */ + params->hwcaps.mask |= SNOR_HWCAPS_READ | SNOR_HWCAPS_PP; + + /* 2. 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->addr_nbytes = 3; + params->addr_nbytes = 3; + + /* 3. Disable WIP Polling (Fixes the 40s Timeout) */ + params->ready = everspin_mram_ready_noop; + + /* 4. 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(¶ms->reads[SNOR_CMD_READ], + 0, 8, 0xCB, SNOR_PROTO_8_8_8); + + spi_nor_set_pp_settings(¶ms->page_programs[SNOR_CMD_PP], + 0x82, SNOR_PROTO_8_8_8); + + 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 +239,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, }, };