From 133e49bfc4ff65a5d1afe3a10fd4f391c876181a Mon Sep 17 00:00:00 2001 From: Heinrich Toews Date: Tue, 17 Feb 2026 12:22:47 +0100 Subject: [PATCH 1/6] 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, }, }; From 0918552295609b124ccc14b579223e413d3e0d36 Mon Sep 17 00:00:00 2001 From: Heinrich Toews Date: Thu, 19 Feb 2026 17:50:22 +0100 Subject: [PATCH 2/6] mtd: spi-nor: core: add blind octal reset fix for Everspin MRAM Everspin Octal-STR MRAMs (like EM008LXO) stay in Octal mode after a warm reboot if the hardware reset pin is not toggled (e.g., when connected to nPORZ-MCU). This causes the subsequent JEDEC ID read to fail, as the controller restarts in Single-SPI mode while the device still expects Octal-STR commands. This patch introduces spi_nor_everspin_reboot_fix(), which is called within spi_nor_hw_reset() as a fallback if no dedicated reset GPIO is defined in the Device Tree. The fix sends a "blind" Software Reset sequence (0x66 -> 0x99) using 8-8-8 protocol. If the device is in Octal-STR mode, it will recover to Single-SPI. If it is already in Single-SPI mode, the 8-lane command is ignored by the device, making this a safe recovery path for warm reboots. Signed-off-by: Heinrich Toews --- drivers/mtd/spi-nor/core.c | 65 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c index e5871e5ac428..58a54e1075c9 100644 --- a/drivers/mtd/spi-nor/core.c +++ b/drivers/mtd/spi-nor/core.c @@ -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 From 7587fa56700e5fb265d98f000853b00866f5541f Mon Sep 17 00:00:00 2001 From: Heinrich Toews Date: Tue, 24 Feb 2026 10:43:23 +0100 Subject: [PATCH 3/6] arm: dts: pfc-750-84xx-wosm: mram: adjust timings for 80 Mhz 8s-8s-8s mode Signed-off-by: Heinrich Toews --- .../dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/arch/arm64/boot/dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi b/arch/arm64/boot/dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi index b3e3a9e75c88..cea5c30431a5 100644 --- a/arch/arm64/boot/dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi +++ b/arch/arm64/boot/dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi @@ -195,17 +195,26 @@ 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>; + /* 80 MHz Target */ + spi-max-frequency = <80000000>; + + /* PHY mode is highly recommended for >50MHz on Cadence controllers */ 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 */ + + spi-rx-bus-width = <8>; + spi-tx-bus-width = <8>; + partitions { compatible = "fixed-partitions"; #address-cells = <1>; From 1d2ed385ff96bb3f42c5b55de0f44141a0deb33e Mon Sep 17 00:00:00 2001 From: Heinrich Toews Date: Tue, 24 Feb 2026 10:44:33 +0100 Subject: [PATCH 4/6] arm: dts: pfc-750-84xx-wosm: mram: change total capacity to 1MB Signed-off-by: Heinrich Toews --- arch/arm64/boot/dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi b/arch/arm64/boot/dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi index cea5c30431a5..08e6c0d56c80 100644 --- a/arch/arm64/boot/dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi +++ b/arch/arm64/boot/dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi @@ -222,7 +222,7 @@ partition@0 { label = "ospi.mram.mem"; - reg = <0x0 0x800000>; + reg = <0x0 0x100000>; /* 1MB total capacity */ }; }; }; From f3d85807457cfffbfd88f36768cfb7c737000063 Mon Sep 17 00:00:00 2001 From: Heinrich Toews Date: Tue, 24 Feb 2026 12:27:29 +0100 Subject: [PATCH 5/6] 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 --- .../dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi | 30 ++-- drivers/mtd/spi-nor/everspin.c | 158 +++++++++++++++--- 2 files changed, 150 insertions(+), 38 deletions(-) diff --git a/arch/arm64/boot/dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi b/arch/arm64/boot/dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi index 08e6c0d56c80..c4d4f05af7bb 100644 --- a/arch/arm64/boot/dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi +++ b/arch/arm64/boot/dts/ti/k3-am623-pfc-750-84xx-wosm.dtsi @@ -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>; diff --git a/drivers/mtd/spi-nor/everspin.c b/drivers/mtd/spi-nor/everspin.c index de8f391a53d4..96eda6e3dc5c 100644 --- a/drivers/mtd/spi-nor/everspin.c +++ b/drivers/mtd/spi-nor/everspin.c @@ -9,10 +9,19 @@ #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 */ +/* 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(¶ms->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(¶ms->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(¶ms->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(¶ms->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; } From 4e58c348b27857dc07514fc901ee88884810abdb Mon Sep 17 00:00:00 2001 From: Heinrich Toews Date: Tue, 24 Feb 2026 16:45:17 +0100 Subject: [PATCH 6/6] spi: cadence-qspi: add logging for clk configuration changes Signed-off-by: Heinrich Toews --- drivers/spi/spi-cadence-quadspi.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c index 3e0480aadf9d..541afcf32f9a 100644 --- a/drivers/spi/spi-cadence-quadspi.c +++ b/drivers/spi/spi-cadence-quadspi.c @@ -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,