From ba2a2059084c05ef9512281553fb77814ebc3335 Mon Sep 17 00:00:00 2001 From: Luke Wren Date: Wed, 16 Jul 2025 15:58:13 +0100 Subject: [PATCH 1/2] hardware_flash: preserve QSPI pad state over flash access calls. Add new function, flash_start_xip(), which explicitly performs a first-time XIP setup (including initialising pads). This is mostly useful for PICO_NO_FLASH=1 binaries where there is actually an attached external flash. Fixes #2333 --- src/rp2_common/hardware_flash/flash.c | 112 ++++++++++++++---- .../hardware_flash/include/hardware/flash.h | 18 +++ 2 files changed, 105 insertions(+), 25 deletions(-) diff --git a/src/rp2_common/hardware_flash/flash.c b/src/rp2_common/hardware_flash/flash.c index dab5d6b46..5800884e7 100644 --- a/src/rp2_common/hardware_flash/flash.c +++ b/src/rp2_common/hardware_flash/flash.c @@ -14,6 +14,7 @@ #include "hardware/structs/qmi.h" #include "hardware/regs/otp_data.h" #endif +#include "hardware/structs/pads_qspi.h" #include "hardware/xip_cache.h" #define FLASH_BLOCK_ERASE_CMD 0xd8 @@ -71,6 +72,22 @@ static void __no_inline_not_in_flash_func(flash_enable_xip_via_boot2)(void) { #endif +//----------------------------------------------------------------------------- +// State save/restore + +// Most functions save and restore the QSPI pad state over the call. (The main +// exception is flash_start_xip() which is explicitly intended to initialise +// them). The expectation is that by the time you do any flash operations, +// you have either gone through a normal flash boot process or (in the case +// of PICO_NO_FLASH=1) you have called flash_start_xip(). Any further +// modifications to the pad state are therefore deliberate changes that we +// should preserve. +// +// Additionally, on RP2350, we save and restore the window 1 QMI configuration +// if the user has not opted into bootrom CS1 support via FLASH_DEVINFO OTP +// flags. This avoids clobbering CS1 setup (e.g. PSRAM) performed by the +// application. + #if PICO_RP2350 // This is specifically for saving/restoring the registers modified by RP2350 // flash_exit_xip() ROM func, not the entirety of the QMI window state. @@ -108,9 +125,69 @@ static void __no_inline_not_in_flash_func(flash_rp2350_restore_qmi_cs1)(const fl } #endif + +typedef struct flash_hardware_save_state { +#if PICO_RP2350 + flash_rp2350_qmi_save_state_t qmi_save; +#endif + uint32_t qspi_pads[count_of(pads_qspi_hw->io)]; +} flash_hardware_save_state_t; + +static void __no_inline_not_in_flash_func(flash_save_hardware_state)(flash_hardware_save_state_t *state) { + // Commit any pending writes to external RAM, to avoid losing them in a subsequent flush: + xip_cache_clean_all(); + for (size_t i = 0; i < count_of(pads_qspi_hw->io); ++i) { + state->qspi_pads[i] = pads_qspi_hw->io[i]; + } +#if PICO_RP2350 + flash_rp2350_save_qmi_cs1(&state->qmi_save); +#endif +} + +static void __no_inline_not_in_flash_func(flash_restore_hardware_state)(flash_hardware_save_state_t *state) { + for (size_t i = 0; i < count_of(pads_qspi_hw->io); ++i) { + pads_qspi_hw->io[i] = state->qspi_pads[i]; + } +#if PICO_RP2350 + // Tail call! + flash_rp2350_restore_qmi_cs1(&state->qmi_save); +#endif +} + //----------------------------------------------------------------------------- // Actual flash programming shims (work whether or not PICO_NO_FLASH==1) +void __no_inline_not_in_flash_func(flash_start_xip)(void) { + rom_connect_internal_flash_fn connect_internal_flash_func = (rom_connect_internal_flash_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + rom_flash_exit_xip_fn flash_exit_xip_func = (rom_flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + rom_flash_flush_cache_fn flash_flush_cache_func = (rom_flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + rom_flash_enter_cmd_xip_fn flash_enter_cmd_xip_func = (rom_flash_enter_cmd_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_ENTER_CMD_XIP); + assert(connect_internal_flash_func && flash_exit_xip_func && flash_flush_cache_func && flash_enter_cmd_xip_func); + // Commit any pending writes to external RAM, to avoid losing them in the subsequent flush: + xip_cache_clean_all(); +#if PICO_RP2350 + flash_rp2350_qmi_save_state_t qmi_save; + flash_rp2350_save_qmi_cs1(&qmi_save); +#endif + + // Use ROM calls to get from ~any state to a state where low-speed flash access works: + connect_internal_flash_func(); + flash_exit_xip_func(); + flash_flush_cache_func(); + flash_enter_cmd_xip_func(); + + // If a boot2 is available then call it now. Slight limitation here is that if this is a + // NO_FLASH binary which was loaded via bootrom LOAD_MAP, we should actually have a better + // flash setup than this available via xip setup func stub left in boot RAM, but we can't + // easily detect this case to take advantage of this. + flash_init_boot2_copyout(); + flash_enable_xip_via_boot2(); + +#if PICO_RP2350 + flash_rp2350_restore_qmi_cs1(&qmi_save); +#endif +} + void __no_inline_not_in_flash_func(flash_range_erase)(uint32_t flash_offs, size_t count) { #ifdef PICO_FLASH_SIZE_BYTES hard_assert(flash_offs + count <= PICO_FLASH_SIZE_BYTES); @@ -123,12 +200,8 @@ void __no_inline_not_in_flash_func(flash_range_erase)(uint32_t flash_offs, size_ rom_flash_flush_cache_fn flash_flush_cache_func = (rom_flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); assert(connect_internal_flash_func && flash_exit_xip_func && flash_range_erase_func && flash_flush_cache_func); flash_init_boot2_copyout(); - // Commit any pending writes to external RAM, to avoid losing them in the subsequent flush: - xip_cache_clean_all(); -#if PICO_RP2350 - flash_rp2350_qmi_save_state_t qmi_save; - flash_rp2350_save_qmi_cs1(&qmi_save); -#endif + flash_hardware_save_state_t state; + flash_save_hardware_state(&state); // No flash accesses after this point __compiler_memory_barrier(); @@ -138,9 +211,7 @@ void __no_inline_not_in_flash_func(flash_range_erase)(uint32_t flash_offs, size_ flash_range_erase_func(flash_offs, count, FLASH_BLOCK_SIZE, FLASH_BLOCK_ERASE_CMD); flash_flush_cache_func(); // Note this is needed to remove CSn IO force as well as cache flushing flash_enable_xip_via_boot2(); -#if PICO_RP2350 - flash_rp2350_restore_qmi_cs1(&qmi_save); -#endif + flash_restore_hardware_state(&state); } void __no_inline_not_in_flash_func(flash_flush_cache)(void) { @@ -160,11 +231,8 @@ void __no_inline_not_in_flash_func(flash_range_program)(uint32_t flash_offs, con rom_flash_flush_cache_fn flash_flush_cache_func = (rom_flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); assert(connect_internal_flash_func && flash_exit_xip_func && flash_range_program_func && flash_flush_cache_func); flash_init_boot2_copyout(); - xip_cache_clean_all(); -#if PICO_RP2350 - flash_rp2350_qmi_save_state_t qmi_save; - flash_rp2350_save_qmi_cs1(&qmi_save); -#endif + flash_hardware_save_state_t state; + flash_save_hardware_state(&state); __compiler_memory_barrier(); @@ -173,9 +241,8 @@ void __no_inline_not_in_flash_func(flash_range_program)(uint32_t flash_offs, con flash_range_program_func(flash_offs, data, count); flash_flush_cache_func(); // Note this is needed to remove CSn IO force as well as cache flushing flash_enable_xip_via_boot2(); -#if PICO_RP2350 - flash_rp2350_restore_qmi_cs1(&qmi_save); -#endif + + flash_restore_hardware_state(&state); } //----------------------------------------------------------------------------- @@ -208,11 +275,8 @@ void __no_inline_not_in_flash_func(flash_do_cmd)(const uint8_t *txbuf, uint8_t * rom_flash_flush_cache_fn flash_flush_cache_func = (rom_flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); assert(connect_internal_flash_func && flash_exit_xip_func && flash_flush_cache_func); flash_init_boot2_copyout(); - xip_cache_clean_all(); -#if PICO_RP2350 - flash_rp2350_qmi_save_state_t qmi_save; - flash_rp2350_save_qmi_cs1(&qmi_save); -#endif + flash_hardware_save_state_t state; + flash_save_hardware_state(&state); __compiler_memory_barrier(); connect_internal_flash_func(); @@ -260,9 +324,7 @@ void __no_inline_not_in_flash_func(flash_do_cmd)(const uint8_t *txbuf, uint8_t * flash_flush_cache_func(); flash_enable_xip_via_boot2(); -#if PICO_RP2350 - flash_rp2350_restore_qmi_cs1(&qmi_save); -#endif + flash_restore_hardware_state(&state); } #endif diff --git a/src/rp2_common/hardware_flash/include/hardware/flash.h b/src/rp2_common/hardware_flash/include/hardware/flash.h index af6343274..3924fa6e6 100644 --- a/src/rp2_common/hardware_flash/include/hardware/flash.h +++ b/src/rp2_common/hardware_flash/include/hardware/flash.h @@ -56,6 +56,24 @@ extern "C" { #endif +/*! \brief Initialise QSPI interface and external QSPI devices for execute-in-place + * \ingroup hardware_flash + * + * This function performs the same first-time flash setup that would normally occur during startup + * of a flash binary. + * + * This is mostly useful for initialising flash on a PICO_NO_FLASH=1 binary. In spite of the name, + * this binary type really means "preloaded to RAM" and there may still be a flash device. + * + * This function does not preserve the QSPI interface state or pad state. However, on RP2350 it does + * preserve the QMI window 1 configuration if you have not opted into bootrom CS1 support via + * FLASH_DEVINFO. This is in contrast to most other functions in this library, which preserve at + * least the QSPI pad state. + * + */ +void flash_start_xip(void); + + /*! \brief Erase areas of flash * \ingroup hardware_flash * From 1881fd399ac689b59b5fa7387ec5aa73a963a06b Mon Sep 17 00:00:00 2001 From: Luke Wren Date: Fri, 18 Jul 2025 14:44:46 +0100 Subject: [PATCH 2/2] Address review comments --- src/rp2_common/hardware_flash/flash.c | 12 ++++---- .../hardware_flash/include/hardware/flash.h | 30 ++++++++++++------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/rp2_common/hardware_flash/flash.c b/src/rp2_common/hardware_flash/flash.c index 5800884e7..fbca8de2c 100644 --- a/src/rp2_common/hardware_flash/flash.c +++ b/src/rp2_common/hardware_flash/flash.c @@ -88,7 +88,7 @@ static void __no_inline_not_in_flash_func(flash_enable_xip_via_boot2)(void) { // flags. This avoids clobbering CS1 setup (e.g. PSRAM) performed by the // application. -#if PICO_RP2350 +#if !PICO_RP2040 // This is specifically for saving/restoring the registers modified by RP2350 // flash_exit_xip() ROM func, not the entirety of the QMI window state. typedef struct flash_rp2350_qmi_save_state { @@ -127,7 +127,7 @@ static void __no_inline_not_in_flash_func(flash_rp2350_restore_qmi_cs1)(const fl typedef struct flash_hardware_save_state { -#if PICO_RP2350 +#if !PICO_RP2040 flash_rp2350_qmi_save_state_t qmi_save; #endif uint32_t qspi_pads[count_of(pads_qspi_hw->io)]; @@ -139,7 +139,7 @@ static void __no_inline_not_in_flash_func(flash_save_hardware_state)(flash_hardw for (size_t i = 0; i < count_of(pads_qspi_hw->io); ++i) { state->qspi_pads[i] = pads_qspi_hw->io[i]; } -#if PICO_RP2350 +#if !PICO_RP2040 flash_rp2350_save_qmi_cs1(&state->qmi_save); #endif } @@ -148,7 +148,7 @@ static void __no_inline_not_in_flash_func(flash_restore_hardware_state)(flash_ha for (size_t i = 0; i < count_of(pads_qspi_hw->io); ++i) { pads_qspi_hw->io[i] = state->qspi_pads[i]; } -#if PICO_RP2350 +#if !PICO_RP2040 // Tail call! flash_rp2350_restore_qmi_cs1(&state->qmi_save); #endif @@ -165,7 +165,7 @@ void __no_inline_not_in_flash_func(flash_start_xip)(void) { assert(connect_internal_flash_func && flash_exit_xip_func && flash_flush_cache_func && flash_enter_cmd_xip_func); // Commit any pending writes to external RAM, to avoid losing them in the subsequent flush: xip_cache_clean_all(); -#if PICO_RP2350 +#if !PICO_RP2040 flash_rp2350_qmi_save_state_t qmi_save; flash_rp2350_save_qmi_cs1(&qmi_save); #endif @@ -183,7 +183,7 @@ void __no_inline_not_in_flash_func(flash_start_xip)(void) { flash_init_boot2_copyout(); flash_enable_xip_via_boot2(); -#if PICO_RP2350 +#if !PICO_RP2040 flash_rp2350_restore_qmi_cs1(&qmi_save); #endif } diff --git a/src/rp2_common/hardware_flash/include/hardware/flash.h b/src/rp2_common/hardware_flash/include/hardware/flash.h index 3924fa6e6..2bb5d5ae3 100644 --- a/src/rp2_common/hardware_flash/include/hardware/flash.h +++ b/src/rp2_common/hardware_flash/include/hardware/flash.h @@ -59,17 +59,25 @@ extern "C" { /*! \brief Initialise QSPI interface and external QSPI devices for execute-in-place * \ingroup hardware_flash * - * This function performs the same first-time flash setup that would normally occur during startup - * of a flash binary. - * - * This is mostly useful for initialising flash on a PICO_NO_FLASH=1 binary. In spite of the name, - * this binary type really means "preloaded to RAM" and there may still be a flash device. - * - * This function does not preserve the QSPI interface state or pad state. However, on RP2350 it does - * preserve the QMI window 1 configuration if you have not opted into bootrom CS1 support via - * FLASH_DEVINFO. This is in contrast to most other functions in this library, which preserve at - * least the QSPI pad state. - * + * This function performs the same first-time flash setup that would normally occur over the course + * of the bootrom locating a flash binary and booting it, and that flash binary executing the SDK + * crt0. Specifically: + * + * * Initialise QSPI pads to their default states, and (non-RP2040) disable pad isolation latches + * * Issue a hardcoded sequence to attached QSPI devices to return them to a serial command state + * * Flush the XIP cache + * * Configure the QSPI interface for low-speed 03h reads + * * If this is not a PICO_NO_FLASH=1 binary: + * * (RP2040) load a boot2 stage from the first 256 bytes of RAM and execute it + * * (non-RP2040) execute an XIP setup function stored in boot RAM by either the bootrom or by crt0 + * + * This is mostly useful for initialising flash on a PICO_NO_FLASH=1 binary. (In spite of the name, + * this binary type really means "preloaded to RAM" and there may still be a flash device.) + * + * This function does not preserve the QSPI interface state or pad state. This is in contrast to + * most other functions in this library, which preserve at least the QSPI pad state. However, on + * RP2350 it does preserve the QMI window 1 configuration if you have not opted into bootrom CS1 + * support via FLASH_DEVINFO. */ void flash_start_xip(void);