Skip to content

hardware_flash: preserve QSPI pad state over flash access calls. #2565

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 88 additions & 26 deletions src/rp2_common/hardware_flash/flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -71,7 +72,23 @@ static void __no_inline_not_in_flash_func(flash_enable_xip_via_boot2)(void) {

#endif

#if PICO_RP2350
//-----------------------------------------------------------------------------
// 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_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 {
Expand Down Expand Up @@ -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_RP2040
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_RP2040
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_RP2040
// 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_RP2040
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_RP2040
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);
Expand All @@ -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();
Expand All @@ -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) {
Expand All @@ -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();

Expand All @@ -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);
}

//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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

Expand Down
26 changes: 26 additions & 0 deletions src/rp2_common/hardware_flash/include/hardware/flash.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,32 @@
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 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);


/*! \brief Erase areas of flash
* \ingroup hardware_flash
*
Expand Down
Loading