External SPI flash on nRF52833 works after power cycle, but fails after sys_reboot() and returns JEDEC FF FF FF

Hello,

we are seeing a reproducible issue with an external SPI NOR flash on our own custom hardware based on the Nordic nRF52833.

Short summary

The external flash works normally after a real power cycle.
JEDEC read is correct, and erase/program/readback/restore also work.
If the application then calls sys_reboot(SYS_REBOOT_COLD), the same flash stops responding correctly after reboot and returns only FF FF FF.
A real power cycle makes it work again.

Hardware

- Custom board
- MCU: Nordic nRF52833
- External flash: Winbond W25Q128JVSIQ
- Shared SPI1 bus
- Flash signals:
- FLASH-CS = P0.17
- SPI-CLK = P0.04
- SPI-MISO = P0.05
- SPI-MOSI = P0.12
- Flash is powered from 3V3
- Flash has no dedicated reset line on this board 
- WP and HOLD have a pull-up to 3v3 and are otherwise unconnected.
- CS has a pull-up to 3v3 

Software

- nRF Connect SDK: v3.2.4
- Zephyr: v4.2.99
- Custom made debug software (see attached) 
- The attached test uses direct SPI transactions on SPI1
- SPI frequency in this test is only 125 kHz
- Same Issue occurs with the official zephyr spi_flash sample. 

Reproduction steps

1. Power cycle the board
2. Run the attached test firmware
3. On first boot:
- read JEDEC-ID
- read SR1
- perform a small functional flash test
- backup one sector
- erase sector
- program one page
- read back and verify
- restore original sector
4. The application then calls sys_reboot(SYS_REBOOT_COLD)
5. On second boot, the application runs exactly the same sequence again

Expected result

The second boot should behave the same as the first boot.
JEDEC and SR1 should still be valid, and the same functional flash test should pass.

Actual result

JEDEC return with id=FF FF FF after sys_reboot.

RTT Output:

*** Booting nRF Connect SDK v3.2.4-4c3fc0d44534 ***
*** Using Zephyr OS v4.2.99-9673eec75908 ***

SPI flash V9 style simple reset reproducer
RESETREAS=0x00000001
SPI1 CS polarity FLASH=0x00000001
SPI device ready
FLASH CS ready
=== FIRST BOOT ===

=== [FIRST BOOT] ===
[FIRST BOOT] FLASH_CS dt_flags=0x00000001 active_low=1
[FIRST BOOT] PORT P0 OUT=0x40020840 DIR=0xC0021958 IN=0x000400E0
[FIRST BOOT] PORT P1 OUT=0x00000060 DIR=0x00000060 IN=0x00000000
[FIRST BOOT] CAN_CS pin=11 PIN_CNF=0x00000003 OUT=1 DIR=1 IN=0
[FIRST BOOT] FLASH_CS pin=17 PIN_CNF=0x00000003 OUT=1 DIR=1 IN=0
[FIRST BOOT] NINA_CS pin=37 PIN_CNF=0x00000003 OUT=1 DIR=1 IN=0
[FIRST BOOT] SCK pin=4 PIN_CNF=0x00000001 OUT=0 DIR=1 IN=0
[FIRST BOOT] MOSI pin=12 PIN_CNF=0x00000003 OUT=0 DIR=1 IN=0
[FIRST BOOT] MISO pin=5 PIN_CNF=0x00000000 OUT=0 DIR=0 IN=1
[FIRST BOOT] SPIM1 ENABLE=0x00000000 FREQ=0x04000000 CONFIG=0x00000000
[FIRST BOOT] SPIM1 PSEL SCK=0x00000004 MOSI=0x0000000C MISO=0x00000005
[FIRST BOOT] JEDEC ret=0 id=EF 40 18 | SR1 ret=0 val=00
[FLASH TEST] start sector=0x00FFF000 page=0x00FFF000
[FLASH TEST] PASS
FIRST BOOT PASS, rebooting now

=== before_sys_reboot ===
before_sys_reboot FLASH_CS dt_flags=0x00000001 active_low=1
before_sys_reboot PORT P0 OUT=0x40020840 DIR=0xC0021958 IN=0x000400E0
before_sys_reboot PORT P1 OUT=0x00000060 DIR=0x00000060 IN=0x00000000
before_sys_reboot CAN_CS pin=11 PIN_CNF=0x00000003 OUT=1 DIR=1 IN=0
before_sys_reboot FLASH_CS pin=17 PIN_CNF=0x00000003 OUT=1 DIR=1 IN=0
before_sys_reboot NINA_CS pin=37 PIN_CNF=0x00000003 OUT=1 DIR=1 IN=0
before_sys_reboot SCK pin=4 PIN_CNF=0x00000001 OUT=0 DIR=1 IN=0
before_sys_reboot MOSI pin=12 PIN_CNF=0x00000003 OUT=0 DIR=1 IN=0
before_sys_reboot MISO pin=5 PIN_CNF=0x00000000 OUT=0 DIR=0 IN=1
before_sys_reboot SPIM1 ENABLE=0x00000000 FREQ=0x02000000 CONFIG=0x00000000
before_sys_reboot SPIM1 PSEL SCK=0x00000004 MOSI=0x0000000C MISO=0x00000005
*** Booting nRF Connect SDK v3.2.4-4c3fc0d44534 ***
*** Using Zephyr OS v4.2.99-9673eec75908 ***

SPI flash V9 style simple reset reproducer
RESETREAS=0x00000004
SPI1 CS polarity FLASH=0x00000001
SPI device ready
FLASH CS ready
=== SECOND BOOT AFTER sys_reboot() ===

=== [SECOND BOOT] ===
[SECOND BOOT] FLASH_CS dt_flags=0x00000001 active_low=1
[SECOND BOOT] PORT P0 OUT=0x40020840 DIR=0xC0021958 IN=0x000400E0
[SECOND BOOT] PORT P1 OUT=0x00000060 DIR=0x00000060 IN=0x00000000
[SECOND BOOT] CAN_CS pin=11 PIN_CNF=0x00000003 OUT=1 DIR=1 IN=0
[SECOND BOOT] FLASH_CS pin=17 PIN_CNF=0x00000003 OUT=1 DIR=1 IN=0
[SECOND BOOT] NINA_CS pin=37 PIN_CNF=0x00000003 OUT=1 DIR=1 IN=0
[SECOND BOOT] SCK pin=4 PIN_CNF=0x00000001 OUT=0 DIR=1 IN=0
[SECOND BOOT] MOSI pin=12 PIN_CNF=0x00000003 OUT=0 DIR=1 IN=0
[SECOND BOOT] MISO pin=5 PIN_CNF=0x00000000 OUT=0 DIR=0 IN=1
[SECOND BOOT] SPIM1 ENABLE=0x00000000 FREQ=0x04000000 CONFIG=0x00000000
[SECOND BOOT] SPIM1 PSEL SCK=0x00000004 MOSI=0x0000000C MISO=0x00000005
[SECOND BOOT] JEDEC ret=0 id=FF FF FF | SR1 ret=0 val=FF
SECOND BOOT FAILED AFTER SOFTWARE RESET

Questions

1. Have you seen similar behaviour on custom nRF52 hardware where external SPI flash works after power cycle but fails after sys_reboot()?
2. Is there any known reset sequencing issue, peripheral state issue, or SPI pin state issue during sys_reboot() that could explain this?
3. Would you recommend explicitly disconnecting SPIM PSEL, forcing SPI pins to defined input states, or doing anything special before sys_reboot()?
4. Is there any Nordic recommended method to guarantee a safe pre-reset state for an external SPI NOR flash that remains powered while the MCU resets?

prj.conf:

CONFIG_SPI=y
CONFIG_GPIO=y
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_PRINTK=y
CONFIG_LOG=y
CONFIG_LOG_MODE_IMMEDIATE=y

CONFIG_MAIN_STACK_SIZE=2048

CONFIG_BT=n
CONFIG_CAN=n
CONFIG_FLASH=n
CONFIG_FLASH_MAP=n
CONFIG_SPI_NOR=n
CONFIG_BOOTLOADER_MCUBOOT=n

CONFIG_REBOOT=y

app.overlay:

/dts-v1/;
#include <nordic/nrf52833_qiaa.dtsi>
#include "grivix_poco_v3-pinctrl.dtsi"
#include <zephyr/dt-bindings/led/led.h>

/ {
	model = "Grivix POCO V3";
	compatible = "Grivix,grivix-poco-v3";

	
	chosen {
		zephyr,sram = &sram0;
		zephyr,flash = &flash0;
		nordic,pm-ext-flash   = &w25q128;
		zephyr,console = &uart0;
		zephyr,shell-uart = &uart0;
		//zephyr,led-strip = &int_rgb_led; handle in code.
		//zephyr,led-strip = &ext_led_strip;
	};


	zephyr,user {
		magn-en-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
		vout-en-gpios = <&gpio0 19 GPIO_ACTIVE_HIGH>;
		spb12v-en-gpios = <&gpio0 20 GPIO_ACTIVE_HIGH>;
		usb-sel-gpios = <&gpio0 21 GPIO_ACTIVE_LOW>;
		ext-led-en-gpios = <&gpio0 22 GPIO_ACTIVE_HIGH>;
		in1-sig-gpios = <&gpio0 26 GPIO_ACTIVE_HIGH>;
		in2-sig-gpios = <&gpio0 27 GPIO_ACTIVE_HIGH>;
	};


&flash0 {
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        boot_partition: partition@0 {
            label = "mcuboot";
            reg = <0x00000000 0x0000D000>;
        };

        slot0_partition: partition@d000 {
            label = "image-0";
            reg = <0x0000D000 0x0006F000>;
        };

        storage_partition: partition@7c000 {
            label = "storage";
            reg = <0x0007C000 0x00004000>;
        };
    };
};


&spi1 {
	compatible = "nordic,nrf-spim";
    status = "okay";
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&spi1_default>;
    pinctrl-1 = <&spi1_sleep>;
    cs-gpios = <&gpio0 11 GPIO_ACTIVE_LOW>,  // can
				<&gpio0 17 GPIO_ACTIVE_LOW>, //flash
				<&gpio1 05 GPIO_ACTIVE_LOW>; //nina-w156 wifi/bt

    mcp2518fd: mcp2518fd@0 {
        compatible = "microchip,mcp251xfd";
        reg = <0>;
        spi-max-frequency = <20000000>;
        osc-freq = <40000000>;               		// externes Quarz, z. B. 40 MHz
        int-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>;  	// INT-Pin anpassen
        xstby-enable;                       	 	// Standby Pin aktivieren
        status = "disabled";
        label = "can0";

        bitrate = <250000>;
        sample-point = <875>;
    };

	w25q128: flash@1 {
		compatible = "jedec,spi-nor";
		reg = <1>;
		spi-max-frequency = <1000000>;
		size = <0x8000000>;
		jedec-id = [ef 40 18];
		status = "okay";

		partitions {
			compatible = "fixed-partitions";
			#address-cells = <1>;
			#size-cells = <1>;

			slot1_partition: partition@0 {
				label = "image-1";
				reg = <0x00000000 0x0006F000>;
			};
		};
	};


	nina_spi: nina_w156-spi@2 {
		compatible = "u-blox,nina-w156-spi";
		reg = <2>;
		spi-max-frequency = <8000000>; /* 80 MHz max laut Datenblatt aktuell auf 8MHz */
		norx-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>; // nina-w156 NORX Pin
		drdy-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; // nina-w156 DRDY Pin
		status = "disabled"; 
		label = "nina-w156-spi";
	};
};

&gpiote0 {
	status = "okay";
};

&gpio0 {
	status = "okay";
	gpiote-instance = <&gpiote0>;
};

&gpio1 {
	status = "okay";
	gpiote-instance = <&gpiote0>;
};

&clock {
	status = "okay";
};


main.c

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/sys/util.h>
#include <zephyr/init.h>
#include <zephyr/linker/section_tags.h>

#include <hal/nrf_gpio.h>
#include <hal/nrf_power.h>

#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>

#define SPI1_NODE DT_NODELABEL(spi1)

#define SPI1_SCK_PIN    4
#define SPI1_MISO_PIN   5
#define SPI1_MOSI_PIN   12

#define CAN_CS_PIN      11
#define FLASH_CS_PIN    17
#define NINA_CS_PIN     NRF_GPIO_PIN_MAP(1, 5)

#define RED_LED_NODE    DT_ALIAS(led_red)
#define YELLOW_LED_NODE DT_ALIAS(led_yellow)

#define FLASH_SPI_SLAVE             1
#define FLASH_SPI_FREQ_HZ           125000U

#define FLASH_EXPECTED_JEDEC_MFG    0xEF
#define FLASH_EXPECTED_JEDEC_TYPE   0x40
#define FLASH_EXPECTED_JEDEC_CAP    0x18
#define FLASH_EXPECTED_SR1          0x00

#define FLASH_READ_MAX_LEN          16U
#define FLASH_PAGE_SIZE             256U
#define FLASH_SECTOR_SIZE           4096U

/*
 * Only use this address if this sector is safe to use on your board.
 * The test backs it up and restores it afterwards.
 */
#define FLASH_TEST_SECTOR_ADDR      0x00FFF000u
#define FLASH_TEST_PAGE_OFFSET      0x000U

#define FLASH_ERASE_TIMEOUT_MS      5000U
#define FLASH_PROGRAM_TIMEOUT_MS    100U
#define FLASH_POST_OP_DELAY_MS      2U

#define STARTUP_SETTLE_DELAY_MS     100U
#define PRE_REBOOT_DELAY_MS         100U

#define POST_RESET_MAGIC            0x51AFC0DEu

static const struct device *spi_dev = DEVICE_DT_GET(SPI1_NODE);

static const struct gpio_dt_spec flash_cs =
	GPIO_DT_SPEC_GET_BY_IDX(SPI1_NODE, cs_gpios, 1);

static const struct gpio_dt_spec led_red =
	GPIO_DT_SPEC_GET(RED_LED_NODE, gpios);

static const struct gpio_dt_spec led_yellow =
	GPIO_DT_SPEC_GET(YELLOW_LED_NODE, gpios);

__noinit static struct {
	uint32_t magic;
	uint32_t magic_inv;
} g_post_reset_marker;

static uint8_t g_flash_backup[FLASH_SECTOR_SIZE];
static uint8_t g_flash_readback[FLASH_SECTOR_SIZE];

static bool gpio_dt_is_active_low(const struct gpio_dt_spec *spec)
{
	return (spec->dt_flags & GPIO_ACTIVE_LOW) != 0U;
}

static void log_reset_reason(void)
{
	uint32_t resetreas = nrf_power_resetreas_get(NRF_POWER);

	printk("RESETREAS=0x%08X\n", resetreas);

	if (resetreas != 0U) {
		nrf_power_resetreas_clear(NRF_POWER, resetreas);
	}
}

static void log_pin_state(const char *tag, const char *name, uint32_t abs_pin)
{
	uint32_t local_pin;
	NRF_GPIO_Type *port;

	if (abs_pin < 32U) {
		local_pin = abs_pin;
		port = NRF_P0;
	} else {
		local_pin = abs_pin - 32U;
		port = NRF_P1;
	}

	printk("%s %-8s pin=%u PIN_CNF=0x%08X OUT=%u DIR=%u IN=%u\n",
	       tag,
	       name,
	       (unsigned int)abs_pin,
	       (unsigned int)port->PIN_CNF[local_pin],
	       (unsigned int)((port->OUT >> local_pin) & 0x1U),
	       (unsigned int)((port->DIR >> local_pin) & 0x1U),
	       (unsigned int)((port->IN  >> local_pin) & 0x1U));
}

static void log_bus_state(const char *tag)
{
	printk("\n=== %s ===\n", tag);
	printk("%s FLASH_CS dt_flags=0x%08X active_low=%d\n",
	       tag,
	       (unsigned int)flash_cs.dt_flags,
	       gpio_dt_is_active_low(&flash_cs) ? 1 : 0);

	printk("%s PORT P0 OUT=0x%08X DIR=0x%08X IN=0x%08X\n",
	       tag,
	       (unsigned int)NRF_P0->OUT,
	       (unsigned int)NRF_P0->DIR,
	       (unsigned int)NRF_P0->IN);

#if defined(NRF_P1)
	printk("%s PORT P1 OUT=0x%08X DIR=0x%08X IN=0x%08X\n",
	       tag,
	       (unsigned int)NRF_P1->OUT,
	       (unsigned int)NRF_P1->DIR,
	       (unsigned int)NRF_P1->IN);
#endif

	log_pin_state(tag, "CAN_CS", CAN_CS_PIN);
	log_pin_state(tag, "FLASH_CS", FLASH_CS_PIN);
	log_pin_state(tag, "NINA_CS", NINA_CS_PIN);
	log_pin_state(tag, "SCK", SPI1_SCK_PIN);
	log_pin_state(tag, "MOSI", SPI1_MOSI_PIN);
	log_pin_state(tag, "MISO", SPI1_MISO_PIN);

#if defined(NRF_SPIM1)
	printk("%s SPIM1 ENABLE=0x%08X FREQ=0x%08X CONFIG=0x%08X\n",
	       tag,
	       (unsigned int)NRF_SPIM1->ENABLE,
	       (unsigned int)NRF_SPIM1->FREQUENCY,
	       (unsigned int)NRF_SPIM1->CONFIG);

	printk("%s SPIM1 PSEL SCK=0x%08X MOSI=0x%08X MISO=0x%08X\n",
	       tag,
	       (unsigned int)NRF_SPIM1->PSEL.SCK,
	       (unsigned int)NRF_SPIM1->PSEL.MOSI,
	       (unsigned int)NRF_SPIM1->PSEL.MISO);
#endif
}

static void post_reset_marker_arm(void)
{
	g_post_reset_marker.magic = POST_RESET_MAGIC;
	g_post_reset_marker.magic_inv = ~POST_RESET_MAGIC;
}

static bool post_reset_marker_consume(void)
{
	bool valid = (g_post_reset_marker.magic == POST_RESET_MAGIC) &&
		     (g_post_reset_marker.magic_inv == ~POST_RESET_MAGIC);

	g_post_reset_marker.magic = 0U;
	g_post_reset_marker.magic_inv = 0U;

	return valid;
}

static int spi1_safe_state_prepare(void)
{
	nrf_gpio_cfg_output(CAN_CS_PIN);
	nrf_gpio_cfg_output(FLASH_CS_PIN);
	nrf_gpio_cfg_output(NINA_CS_PIN);

	nrf_gpio_pin_set(CAN_CS_PIN);
	nrf_gpio_pin_set(FLASH_CS_PIN);
	nrf_gpio_pin_set(NINA_CS_PIN);

	nrf_gpio_cfg_output(SPI1_SCK_PIN);
	nrf_gpio_cfg_output(SPI1_MOSI_PIN);
	nrf_gpio_cfg_input(SPI1_MISO_PIN, NRF_GPIO_PIN_PULLDOWN);

	nrf_gpio_pin_clear(SPI1_SCK_PIN);
	nrf_gpio_pin_clear(SPI1_MOSI_PIN);

	return 0;
}

SYS_INIT(spi1_safe_state_prepare, PRE_KERNEL_1, 0);

static bool leds_init(void)
{
	if (!gpio_is_ready_dt(&led_red) || !gpio_is_ready_dt(&led_yellow)) {
		printk("LED GPIO not ready\n");
		return false;
	}

	if (gpio_pin_configure_dt(&led_red, GPIO_OUTPUT_INACTIVE) != 0) {
		printk("RED LED config failed\n");
		return false;
	}

	if (gpio_pin_configure_dt(&led_yellow, GPIO_OUTPUT_INACTIVE) != 0) {
		printk("YELLOW LED config failed\n");
		return false;
	}

	return true;
}

static void leds_show_running(void)
{
	gpio_pin_set_dt(&led_red, 1);
	gpio_pin_set_dt(&led_yellow, 0);
}

static void leds_show_success(void)
{
	gpio_pin_set_dt(&led_red, 0);
	gpio_pin_set_dt(&led_yellow, 1);
}

static void leds_show_failure(void)
{
	gpio_pin_set_dt(&led_red, 1);
	gpio_pin_set_dt(&led_yellow, 0);
}

static void hold_forever(void)
{
	while (1) {
		k_sleep(K_SECONDS(1));
	}
}

static struct spi_config flash_spi_cfg_make(void)
{
	struct spi_config cfg = {
		.frequency = FLASH_SPI_FREQ_HZ,
		.operation = SPI_OP_MODE_MASTER |
			     SPI_WORD_SET(8) |
			     SPI_TRANSFER_MSB,
		.slave = FLASH_SPI_SLAVE,
	};

	return cfg;
}

static bool flash_interface_init(void)
{
	printk("SPI1 CS polarity FLASH=0x%08X\n", (unsigned int)flash_cs.dt_flags);

	if (!device_is_ready(spi_dev)) {
		printk("SPI device not ready\n");
		return false;
	}

	if (!gpio_is_ready_dt(&flash_cs)) {
		printk("FLASH CS GPIO not ready\n");
		return false;
	}

	if (gpio_pin_configure_dt(&flash_cs, GPIO_OUTPUT_INACTIVE) != 0) {
		printk("FLASH CS GPIO config failed\n");
		return false;
	}

	printk("SPI device ready\n");
	printk("FLASH CS ready\n");
	return true;
}

static void flash_cs_assert(void)
{
	gpio_pin_set_dt(&flash_cs, 1);
	k_busy_wait(2);
}

static void flash_cs_deassert(void)
{
	k_busy_wait(2);
	gpio_pin_set_dt(&flash_cs, 0);
	k_busy_wait(2);
}

static void manual_bus_idle(void)
{
	flash_cs_deassert();
	nrf_gpio_pin_clear(SPI1_SCK_PIN);
	nrf_gpio_pin_clear(SPI1_MOSI_PIN);
	k_msleep(2);
}

static int flash_cmd_write1_cfg(const struct spi_config *cfg, uint8_t cmd)
{
	struct spi_buf tx_buf = {
		.buf = &cmd,
		.len = 1,
	};

	struct spi_buf_set tx = {
		.buffers = &tx_buf,
		.count = 1,
	};

	flash_cs_assert();
	int ret = spi_write(spi_dev, cfg, &tx);
	flash_cs_deassert();

	return ret;
}

static int flash_cmd_read_cfg(const struct spi_config *cfg,
			      uint8_t cmd,
			      uint8_t *data,
			      size_t len)
{
	uint8_t tx_raw[1 + FLASH_READ_MAX_LEN] = {0};
	uint8_t rx_raw[1 + FLASH_READ_MAX_LEN] = {0};

	if ((data == NULL) || (len == 0U) || (len > FLASH_READ_MAX_LEN)) {
		return -EINVAL;
	}

	tx_raw[0] = cmd;

	struct spi_buf tx_buf = {
		.buf = tx_raw,
		.len = len + 1U,
	};

	struct spi_buf rx_buf = {
		.buf = rx_raw,
		.len = len + 1U,
	};

	struct spi_buf_set tx = {
		.buffers = &tx_buf,
		.count = 1,
	};

	struct spi_buf_set rx = {
		.buffers = &rx_buf,
		.count = 1,
	};

	flash_cs_assert();
	int ret = spi_transceive(spi_dev, cfg, &tx, &rx);
	flash_cs_deassert();

	if (ret == 0) {
		for (size_t i = 0; i < len; i++) {
			data[i] = rx_raw[i + 1U];
		}
	}

	return ret;
}

static int flash_cmd_write_addr_n_cfg(const struct spi_config *cfg,
				      uint8_t cmd,
				      uint32_t addr,
				      const uint8_t *data,
				      size_t len)
{
	uint8_t tx_raw[4 + FLASH_PAGE_SIZE];

	if (len > FLASH_PAGE_SIZE) {
		return -EINVAL;
	}

	tx_raw[0] = cmd;
	tx_raw[1] = (uint8_t)((addr >> 16) & 0xFF);
	tx_raw[2] = (uint8_t)((addr >> 8) & 0xFF);
	tx_raw[3] = (uint8_t)(addr & 0xFF);

	if ((data != NULL) && (len > 0U)) {
		memcpy(&tx_raw[4], data, len);
	}

	struct spi_buf tx_buf = {
		.buf = tx_raw,
		.len = 4U + len,
	};

	struct spi_buf_set tx = {
		.buffers = &tx_buf,
		.count = 1,
	};

	flash_cs_assert();
	int ret = spi_write(spi_dev, cfg, &tx);
	flash_cs_deassert();

	return ret;
}

static int flash_cmd_read_addr_n_cfg(const struct spi_config *cfg,
				     uint8_t cmd,
				     uint32_t addr,
				     uint8_t *data,
				     size_t len)
{
	uint8_t tx_raw[4 + FLASH_PAGE_SIZE] = {0};
	uint8_t rx_raw[4 + FLASH_PAGE_SIZE] = {0};

	if ((data == NULL) || (len == 0U) || (len > FLASH_PAGE_SIZE)) {
		return -EINVAL;
	}

	tx_raw[0] = cmd;
	tx_raw[1] = (uint8_t)((addr >> 16) & 0xFF);
	tx_raw[2] = (uint8_t)((addr >> 8) & 0xFF);
	tx_raw[3] = (uint8_t)(addr & 0xFF);

	struct spi_buf tx_buf = {
		.buf = tx_raw,
		.len = 4U + len,
	};

	struct spi_buf rx_buf = {
		.buf = rx_raw,
		.len = 4U + len,
	};

	struct spi_buf_set tx = {
		.buffers = &tx_buf,
		.count = 1,
	};

	struct spi_buf_set rx = {
		.buffers = &rx_buf,
		.count = 1,
	};

	flash_cs_assert();
	int ret = spi_transceive(spi_dev, cfg, &tx, &rx);
	flash_cs_deassert();

	if (ret == 0) {
		memcpy(data, &rx_raw[4], len);
	}

	return ret;
}

static int flash_read_sr1_cfg(const struct spi_config *cfg, uint8_t *sr1)
{
	return flash_cmd_read_cfg(cfg, 0x05, sr1, 1);
}

static bool flash_sr1_busy(uint8_t sr1)
{
	return (sr1 & BIT(0)) != 0U;
}

static bool flash_sr1_wel(uint8_t sr1)
{
	return (sr1 & BIT(1)) != 0U;
}

static int flash_wait_ready_cfg(const struct spi_config *cfg, uint32_t timeout_ms)
{
	uint8_t sr1 = 0;

	for (uint32_t elapsed = 0; elapsed < timeout_ms; elapsed++) {
		int ret = flash_read_sr1_cfg(cfg, &sr1);
		if (ret != 0) {
			return ret;
		}

		if (!flash_sr1_busy(sr1)) {
			return 0;
		}

		k_msleep(1);
	}

	return -ETIMEDOUT;
}

static int flash_write_enable_cfg(const struct spi_config *cfg)
{
	uint8_t sr1 = 0;
	int ret;

	ret = flash_cmd_write1_cfg(cfg, 0x06);
	if (ret != 0) {
		return ret;
	}

	ret = flash_read_sr1_cfg(cfg, &sr1);
	if (ret != 0) {
		return ret;
	}

	if (!flash_sr1_wel(sr1)) {
		return -EIO;
	}

	return 0;
}

static int flash_sector_erase_4k_cfg(const struct spi_config *cfg, uint32_t addr)
{
	int ret;

	ret = flash_write_enable_cfg(cfg);
	if (ret != 0) {
		return ret;
	}

	return flash_cmd_write_addr_n_cfg(cfg, 0x20, addr, NULL, 0);
}

static int flash_page_program_cfg(const struct spi_config *cfg,
				  uint32_t addr,
				  const uint8_t *data,
				  size_t len)
{
	int ret;

	if ((data == NULL) || (len == 0U) || (len > FLASH_PAGE_SIZE)) {
		return -EINVAL;
	}

	if ((addr & (FLASH_PAGE_SIZE - 1U)) + len > FLASH_PAGE_SIZE) {
		return -EINVAL;
	}

	ret = flash_write_enable_cfg(cfg);
	if (ret != 0) {
		return ret;
	}

	return flash_cmd_write_addr_n_cfg(cfg, 0x02, addr, data, len);
}

static int flash_read_region_cfg(const struct spi_config *cfg,
				 uint32_t addr,
				 uint8_t *data,
				 size_t len)
{
	size_t offset = 0U;

	while (offset < len) {
		size_t chunk = MIN(FLASH_PAGE_SIZE, len - offset);
		int ret = flash_cmd_read_addr_n_cfg(cfg, 0x03, addr + offset, &data[offset], chunk);
		if (ret != 0) {
			return ret;
		}
		offset += chunk;
	}

	return 0;
}

static bool buffer_is_all_ff(const uint8_t *data, size_t len)
{
	for (size_t i = 0; i < len; i++) {
		if (data[i] != 0xFFU) {
			return false;
		}
	}

	return true;
}

static uint8_t flash_test_pattern_byte(uint32_t abs_addr)
{
	uint8_t x = (uint8_t)(abs_addr & 0xFFU);
	uint8_t y = (uint8_t)((abs_addr >> 8) & 0xFFU);

	return (uint8_t)(0xA5U ^ x ^ (uint8_t)(y + 0x3CU));
}

static void flash_fill_test_pattern(uint8_t *data, uint32_t base_addr, size_t len)
{
	for (size_t i = 0; i < len; i++) {
		data[i] = flash_test_pattern_byte(base_addr + i);
	}
}

static bool flash_pattern_matches(uint32_t base_addr, const uint8_t *data, size_t len)
{
	for (size_t i = 0; i < len; i++) {
		if (data[i] != flash_test_pattern_byte(base_addr + i)) {
			return false;
		}
	}

	return true;
}

static int flash_restore_sector_cfg(const struct spi_config *cfg, uint32_t sector_addr)
{
	int ret;

	ret = flash_sector_erase_4k_cfg(cfg, sector_addr);
	if (ret != 0) {
		return ret;
	}

	ret = flash_wait_ready_cfg(cfg, FLASH_ERASE_TIMEOUT_MS);
	if (ret != 0) {
		return ret;
	}

	k_msleep(FLASH_POST_OP_DELAY_MS);

	for (size_t offset = 0; offset < FLASH_SECTOR_SIZE; offset += FLASH_PAGE_SIZE) {
		const uint8_t *page = &g_flash_backup[offset];

		if (buffer_is_all_ff(page, FLASH_PAGE_SIZE)) {
			continue;
		}

		ret = flash_page_program_cfg(cfg, sector_addr + offset, page, FLASH_PAGE_SIZE);
		if (ret != 0) {
			return ret;
		}

		ret = flash_wait_ready_cfg(cfg, FLASH_PROGRAM_TIMEOUT_MS);
		if (ret != 0) {
			return ret;
		}

		k_msleep(FLASH_POST_OP_DELAY_MS);
	}

	ret = flash_read_region_cfg(cfg, sector_addr, g_flash_readback, FLASH_SECTOR_SIZE);
	if (ret != 0) {
		return ret;
	}

	if (memcmp(g_flash_backup, g_flash_readback, FLASH_SECTOR_SIZE) != 0) {
		return -EIO;
	}

	return 0;
}

static bool flash_probe_cfg(const struct spi_config *cfg,
			    const char *tag,
			    uint8_t *jedec_out,
			    uint8_t *sr1_out)
{
	uint8_t jedec[3] = {0};
	uint8_t sr1 = 0;
	int ret_jedec;
	int ret_sr1;

	ret_jedec = flash_cmd_read_cfg(cfg, 0x9F, jedec, sizeof(jedec));
	ret_sr1 = flash_cmd_read_cfg(cfg, 0x05, &sr1, 1);

	printk("%s JEDEC ret=%d id=%02X %02X %02X | SR1 ret=%d val=%02X\n",
	       tag,
	       ret_jedec,
	       jedec[0], jedec[1], jedec[2],
	       ret_sr1,
	       sr1);

	if (jedec_out != NULL) {
		memcpy(jedec_out, jedec, sizeof(jedec));
	}

	if (sr1_out != NULL) {
		*sr1_out = sr1;
	}

	return (ret_jedec == 0) &&
	       (ret_sr1 == 0) &&
	       (jedec[0] == FLASH_EXPECTED_JEDEC_MFG) &&
	       (jedec[1] == FLASH_EXPECTED_JEDEC_TYPE) &&
	       (jedec[2] == FLASH_EXPECTED_JEDEC_CAP) &&
	       (sr1 == FLASH_EXPECTED_SR1);
}

static bool flash_functional_test_cfg(const struct spi_config *cfg, uint32_t sector_addr)
{
	uint8_t page_buf[FLASH_PAGE_SIZE];
	uint32_t page_addr = sector_addr + FLASH_TEST_PAGE_OFFSET;
	int ret;

	printk("[FLASH TEST] start sector=0x%08X page=0x%08X\n",
	       sector_addr, page_addr);

	ret = flash_read_region_cfg(cfg, sector_addr, g_flash_backup, FLASH_SECTOR_SIZE);
	if (ret != 0) {
		printk("[FLASH TEST] backup read failed ret=%d\n", ret);
		return false;
	}

	ret = flash_sector_erase_4k_cfg(cfg, sector_addr);
	if (ret != 0) {
		printk("[FLASH TEST] erase cmd failed ret=%d\n", ret);
		goto restore_original;
	}

	ret = flash_wait_ready_cfg(cfg, FLASH_ERASE_TIMEOUT_MS);
	if (ret != 0) {
		printk("[FLASH TEST] erase timeout ret=%d\n", ret);
		goto restore_original;
	}

	k_msleep(FLASH_POST_OP_DELAY_MS);

	ret = flash_read_region_cfg(cfg, sector_addr, g_flash_readback, FLASH_SECTOR_SIZE);
	if (ret != 0) {
		printk("[FLASH TEST] erase verify read failed ret=%d\n", ret);
		goto restore_original;
	}

	if (!buffer_is_all_ff(g_flash_readback, FLASH_SECTOR_SIZE)) {
		printk("[FLASH TEST] erase verify failed\n");
		goto restore_original;
	}

	flash_fill_test_pattern(page_buf, page_addr, FLASH_PAGE_SIZE);

	ret = flash_page_program_cfg(cfg, page_addr, page_buf, FLASH_PAGE_SIZE);
	if (ret != 0) {
		printk("[FLASH TEST] page program cmd failed ret=%d\n", ret);
		goto restore_original;
	}

	ret = flash_wait_ready_cfg(cfg, FLASH_PROGRAM_TIMEOUT_MS);
	if (ret != 0) {
		printk("[FLASH TEST] page program timeout ret=%d\n", ret);
		goto restore_original;
	}

	k_msleep(FLASH_POST_OP_DELAY_MS);

	ret = flash_read_region_cfg(cfg, page_addr, g_flash_readback, FLASH_PAGE_SIZE);
	if (ret != 0) {
		printk("[FLASH TEST] page readback failed ret=%d\n", ret);
		goto restore_original;
	}

	if (!flash_pattern_matches(page_addr, g_flash_readback, FLASH_PAGE_SIZE)) {
		printk("[FLASH TEST] page verify failed\n");
		goto restore_original;
	}

	ret = flash_restore_sector_cfg(cfg, sector_addr);
	if (ret != 0) {
		printk("[FLASH TEST] restore failed ret=%d\n", ret);
		return false;
	}

	printk("[FLASH TEST] PASS\n");
	return true;

restore_original:
	printk("[FLASH TEST] trying restore after failure\n");
	ret = flash_restore_sector_cfg(cfg, sector_addr);
	if (ret != 0) {
		printk("[FLASH TEST] restore after failure also failed ret=%d\n", ret);
	}

	return false;
}

static bool run_probe_and_test(const struct spi_config *cfg, const char *tag)
{
	manual_bus_idle();
	log_bus_state(tag);

	if (!flash_probe_cfg(cfg, tag, NULL, NULL)) {
		return false;
	}

	return flash_functional_test_cfg(cfg, FLASH_TEST_SECTOR_ADDR);
}

int main(void)
{
	bool post_reset_verify;
	struct spi_config cfg = flash_spi_cfg_make();

	post_reset_verify = post_reset_marker_consume();

	if (!leds_init()) {
		return 0;
	}

	leds_show_running();

	printk("\nSPI flash V9 style simple reset reproducer\n");
	log_reset_reason();

	if (!flash_interface_init()) {
		printk("Hardware init failed\n");
		leds_show_failure();
		hold_forever();
	}

	k_msleep(STARTUP_SETTLE_DELAY_MS);

	if (!post_reset_verify) {
		printk("=== FIRST BOOT ===\n");

		if (!run_probe_and_test(&cfg, "[FIRST BOOT]")) {
			printk("FIRST BOOT FAILED\n");
			leds_show_failure();
			hold_forever();
		}

		printk("FIRST BOOT PASS, rebooting now\n");
		log_bus_state("before_sys_reboot");
		post_reset_marker_arm();
		k_msleep(PRE_REBOOT_DELAY_MS);
		sys_reboot(SYS_REBOOT_COLD);

		printk("sys_reboot returned unexpectedly\n");
		leds_show_failure();
		hold_forever();
	}

	printk("=== SECOND BOOT AFTER sys_reboot() ===\n");

	if (!run_probe_and_test(&cfg, "[SECOND BOOT]")) {
		printk("SECOND BOOT FAILED AFTER SOFTWARE RESET\n");
		leds_show_failure();
		hold_forever();
	}

	printk("SECOND BOOT PASS AFTER SOFTWARE RESET\n");
	leds_show_success();
	hold_forever();
}

Thank you in advance for any help or guidance. 

Parents
  • I also have experienced this issue using an n5340 and different Winbond flash chip via qspi. The problem for me was that the qspi driver was attempting to read/write to the flash chip before tPUW had elapsed. I just had a look at the spi driver and it looks like it'll probably be the same.

    You should be able to confirm by adding a short delay (5 milliseconds) in the spi_nor_configure function, right before exit_dpd is called. That shouldn't be used as a proper fix, but it'll confirm if tPUW is the issue.

  • Thanks for your suggestion.

    I tried adding a short delay before exit_dpd, but the issue still persists.

    I am getting stuck in spi_nor_wait_until_ready(). More specifically, the following condition never evaluates as expected:

    ret = spi_nor_cmd_read(dev, SPI_NOR_CMD_RDSR, &reg, sizeof(reg));
    
    /* Exit on error or no longer WIP */
    if (ret || !(reg & SPI_NOR_WIP_BIT)) {
        break;
    }

    In my case:

    • ret = 0x0 so the SPI transaction itself seems fine
    • but reg always returns 0xFF

    So the loop never exits because the status register appears invalid.

    This matches what I observe more generally: the flash responds with 0xFF only, unless I perform a full power cycle.

    Given this behavior, I suspect the issue is not related to tPUW, but rather that the flash ends up in a bad state after MCU reset and does not properly respond to commands anymore.

    Would you still expect a tPUW violation to result in a constant 0xFF response? 

Reply
  • Thanks for your suggestion.

    I tried adding a short delay before exit_dpd, but the issue still persists.

    I am getting stuck in spi_nor_wait_until_ready(). More specifically, the following condition never evaluates as expected:

    ret = spi_nor_cmd_read(dev, SPI_NOR_CMD_RDSR, &reg, sizeof(reg));
    
    /* Exit on error or no longer WIP */
    if (ret || !(reg & SPI_NOR_WIP_BIT)) {
        break;
    }

    In my case:

    • ret = 0x0 so the SPI transaction itself seems fine
    • but reg always returns 0xFF

    So the loop never exits because the status register appears invalid.

    This matches what I observe more generally: the flash responds with 0xFF only, unless I perform a full power cycle.

    Given this behavior, I suspect the issue is not related to tPUW, but rather that the flash ends up in a bad state after MCU reset and does not properly respond to commands anymore.

    Would you still expect a tPUW violation to result in a constant 0xFF response? 

Children
  • Would you still expect a tPUW violation to result in a constant 0xFF response?

    I would, yes but for whatever reason, for me, it was only during a watchdog reset, reset pin or software reboot. Normal power off/on was fine.

    When it's working just fine, are you able to write to the flash and read back what was written to confirm it did in fact write?

    If you have `CONFIG_FLASH_LOG_LEVEL_DBG=y` set, can you show us the console output with all the debug messages during power up?

Related