From a270498bb768764d651d1cb370fbcc46ff179df2 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 6 Aug 2024 19:49:40 +0100 Subject: [PATCH 1/4] ports/rp2: PSRAM support. Add PSRAM support with auto detection. Performs a best-effort attempt to detect attached PSRAM, configure it and *add* it to the MicroPython heap. If PSRAM is not present, should fall back to use internal RAM. Introduce two new port/board defines: * MICROPY_HW_ENABLE_PSRAM to enable PSRAM. * MICROPY_HW_PSRAM_CS_PIN to define the chip-select pin. Changes: ports/rp2/rp2_psram.c/h: Add new PSRAM module. ports/rp2/main.c: Add optional PSRAM support. ports/rp2/CMakeLists.txt: Include rp2_psram.c. ports/rp2/mpconfigport.h: Add MICROPY_HW_ENABLE_PSRAM. ports/rp2/modmachine.c: Reconfigure PSRAM on freq change. Co-authored-by: Kirk Benell Co-authored-by: Mike Bell Signed-off-by: Phil Howard --- ports/rp2/CMakeLists.txt | 1 + ports/rp2/main.c | 19 ++++ ports/rp2/modmachine.c | 4 + ports/rp2/mpconfigport.h | 4 + ports/rp2/rp2_psram.c | 201 +++++++++++++++++++++++++++++++++++++++ ports/rp2/rp2_psram.h | 44 +++++++++ 6 files changed, 273 insertions(+) create mode 100644 ports/rp2/rp2_psram.c create mode 100644 ports/rp2/rp2_psram.h diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index f89e2792c6479..1a5029c150417 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -165,6 +165,7 @@ set(MICROPY_SOURCE_PORT pendsv.c rp2_flash.c rp2_pio.c + rp2_psram.c rp2_dma.c uart.c usbd.c diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 58da63c06f12d..764af6b7466cf 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -46,6 +46,7 @@ #include "mpnetworkport.h" #include "genhdr/mpversion.h" #include "mp_usbd.h" +#include "rp2_psram.h" #include "pico/stdlib.h" #include "pico/binary_info.h" @@ -93,6 +94,10 @@ int main(int argc, char **argv) { // Hook for setting up anything that needs to be super early in the boot-up process. MICROPY_BOARD_STARTUP(); + #if MICROPY_HW_ENABLE_PSRAM + size_t psram_size = psram_init(MICROPY_HW_PSRAM_CS_PIN); + #endif + #if MICROPY_HW_ENABLE_UART_REPL bi_decl(bi_program_feature("UART REPL")) setup_default_uart(); @@ -120,7 +125,21 @@ int main(int argc, char **argv) { // Initialise stack extents and GC heap. mp_cstack_init_with_top(&__StackTop, &__StackTop - &__StackBottom); + + #if MICROPY_HW_ENABLE_PSRAM + if (psram_size) { + #if MICROPY_GC_SPLIT_HEAP + gc_init(&__GcHeapStart, &__GcHeapEnd); + gc_add((void *)PSRAM_BASE, (void *)(PSRAM_BASE + psram_size)); + #else + gc_init((void *)PSRAM_BASE, (void *)(PSRAM_BASE + psram_size)); + #endif + } else { + gc_init(&__GcHeapStart, &__GcHeapEnd); + } + #else gc_init(&__GcHeapStart, &__GcHeapEnd); + #endif #if MICROPY_PY_LWIP // lwIP doesn't allow to reinitialise itself by subsequent calls to this function diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 31665a7640d9c..bf4e43a613c90 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -31,6 +31,7 @@ #include "mp_usbd.h" #include "modmachine.h" #include "uart.h" +#include "rp2_psram.h" #include "clocks_extra.h" #include "hardware/pll.h" #include "hardware/structs/rosc.h" @@ -115,6 +116,9 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { setup_default_uart(); mp_uart_init(); #endif + #if MICROPY_HW_ENABLE_PSRAM + psram_init(MICROPY_HW_PSRAM_CS_PIN); + #endif } static void mp_machine_idle(void) { diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 38dcad1dae6e1..28022ba420942 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -81,6 +81,10 @@ #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) #endif +#ifndef MICROPY_HW_ENABLE_PSRAM +#define MICROPY_HW_ENABLE_PSRAM (0) +#endif + // Memory allocation policies #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t #define MICROPY_ALLOC_PATH_MAX (128) diff --git a/ports/rp2/rp2_psram.c b/ports/rp2/rp2_psram.c new file mode 100644 index 0000000000000..7f319fd753cc3 --- /dev/null +++ b/ports/rp2/rp2_psram.c @@ -0,0 +1,201 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Phil Howard + * Mike Bell + * Kirk D. Benell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/mphal.h" + +#if MICROPY_HW_ENABLE_PSRAM + +#include "hardware/structs/ioqspi.h" +#include "hardware/structs/qmi.h" +#include "hardware/structs/xip_ctrl.h" +#include "hardware/clocks.h" +#include "hardware/sync.h" +#include "rp2_psram.h" + +size_t __no_inline_not_in_flash_func(psram_detect)(void) { + int psram_size = 0; + + // Try and read the PSRAM ID via direct_csr. + qmi_hw->direct_csr = 30 << QMI_DIRECT_CSR_CLKDIV_LSB | QMI_DIRECT_CSR_EN_BITS; + + // Need to poll for the cooldown on the last XIP transfer to expire + // (via direct-mode BUSY flag) before it is safe to perform the first + // direct-mode operation + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + // Exit out of QMI in case we've inited already + qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; + + // Transmit as quad. + qmi_hw->direct_tx = QMI_DIRECT_TX_OE_BITS | QMI_DIRECT_TX_IWIDTH_VALUE_Q << QMI_DIRECT_TX_IWIDTH_LSB | 0xf5; + + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + (void)qmi_hw->direct_rx; + + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS); + + // Read the id + qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; + uint8_t kgd = 0; + uint8_t eid = 0; + + for (size_t i = 0; i < 7; i++) + { + if (i == 0) { + qmi_hw->direct_tx = 0x9f; + } else { + qmi_hw->direct_tx = 0xff; + } + + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_TXEMPTY_BITS) == 0) { + } + + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + if (i == 5) { + kgd = qmi_hw->direct_rx; + } else if (i == 6) { + eid = qmi_hw->direct_rx; + } else { + (void)qmi_hw->direct_rx; + } + } + + // Disable direct csr. + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS | QMI_DIRECT_CSR_EN_BITS); + + if (kgd == 0x5D) { + psram_size = 1024 * 1024; // 1 MiB + uint8_t size_id = eid >> 5; + if (eid == 0x26 || size_id == 2) { + psram_size *= 8; // 8 MiB + } else if (size_id == 0) { + psram_size *= 2; // 2 MiB + } else if (size_id == 1) { + psram_size *= 4; // 4 MiB + } + } + + return psram_size; +} + +size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { + gpio_set_function(cs_pin, GPIO_FUNC_XIP_CS1); + + uint32_t intr_stash = save_and_disable_interrupts(); + + size_t psram_size = psram_detect(); + + if (!psram_size) { + restore_interrupts(intr_stash); + return 0; + } + + // Enable direct mode, PSRAM CS, clkdiv of 10. + qmi_hw->direct_csr = 10 << QMI_DIRECT_CSR_CLKDIV_LSB | \ + QMI_DIRECT_CSR_EN_BITS | \ + QMI_DIRECT_CSR_AUTO_CS1N_BITS; + while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) { + ; + } + + // Enable QPI mode on the PSRAM + const uint CMD_QPI_EN = 0x35; + qmi_hw->direct_tx = QMI_DIRECT_TX_NOPUSH_BITS | CMD_QPI_EN; + + while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) { + ; + } + + // Set PSRAM timing for APS6404 + // + // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133MHz. + // So: don't allow running at divisor 1 above 100MHz (because delay of 2 would be too late), + // and add an extra 1 to the rxdelay if the divided clock is > 100MHz (i.e. sys clock > 200MHz). + const int max_psram_freq = 133000000; + const int clock_hz = clock_get_hz(clk_sys); + int divisor = (clock_hz + max_psram_freq - 1) / max_psram_freq; + if (divisor == 1 && clock_hz > 100000000) { + divisor = 2; + } + int rxdelay = divisor; + if (clock_hz / divisor > 100000000) { + rxdelay += 1; + } + + // - Max select must be <= 8us. The value is given in multiples of 64 system clocks. + // - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2). + const int clock_period_fs = 1000000000000000ll / clock_hz; + const int max_select = (125 * 1000000) / clock_period_fs; // 125 = 8000ns / 64 + const int min_deselect = (18 * 1000000 + (clock_period_fs - 1)) / clock_period_fs - (divisor + 1) / 2; + + qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | + QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | + max_select << QMI_M1_TIMING_MAX_SELECT_LSB | + min_deselect << QMI_M1_TIMING_MIN_DESELECT_LSB | + rxdelay << QMI_M1_TIMING_RXDELAY_LSB | + divisor << QMI_M1_TIMING_CLKDIV_LSB; + + // Set PSRAM commands and formats + qmi_hw->m[1].rfmt = + QMI_M0_RFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_PREFIX_WIDTH_LSB | \ + QMI_M0_RFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_RFMT_ADDR_WIDTH_LSB | \ + QMI_M0_RFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_SUFFIX_WIDTH_LSB | \ + QMI_M0_RFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_RFMT_DUMMY_WIDTH_LSB | \ + QMI_M0_RFMT_DATA_WIDTH_VALUE_Q << QMI_M0_RFMT_DATA_WIDTH_LSB | \ + QMI_M0_RFMT_PREFIX_LEN_VALUE_8 << QMI_M0_RFMT_PREFIX_LEN_LSB | \ + 6 << QMI_M0_RFMT_DUMMY_LEN_LSB; + + qmi_hw->m[1].rcmd = 0xEB; + + qmi_hw->m[1].wfmt = + QMI_M0_WFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_PREFIX_WIDTH_LSB | \ + QMI_M0_WFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_WFMT_ADDR_WIDTH_LSB | \ + QMI_M0_WFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_SUFFIX_WIDTH_LSB | \ + QMI_M0_WFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_WFMT_DUMMY_WIDTH_LSB | \ + QMI_M0_WFMT_DATA_WIDTH_VALUE_Q << QMI_M0_WFMT_DATA_WIDTH_LSB | \ + QMI_M0_WFMT_PREFIX_LEN_VALUE_8 << QMI_M0_WFMT_PREFIX_LEN_LSB; + + qmi_hw->m[1].wcmd = 0x38; + + // Disable direct mode + qmi_hw->direct_csr = 0; + + // Enable writes to PSRAM + hw_set_bits(&xip_ctrl_hw->ctrl, XIP_CTRL_WRITABLE_M1_BITS); + + restore_interrupts(intr_stash); + + return psram_size; +} + +#endif diff --git a/ports/rp2/rp2_psram.h b/ports/rp2/rp2_psram.h new file mode 100644 index 0000000000000..0eddf6341592b --- /dev/null +++ b/ports/rp2/rp2_psram.h @@ -0,0 +1,44 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Phil Howard + * Mike Bell + * Kirk D. Benell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "pico/stdlib.h" + +#ifndef MICROPY_INCLUDED_RP2_RP2_PSRAM_H +#define MICROPY_INCLUDED_RP2_RP2_PSRAM_H + +#if MICROPY_HW_ENABLE_PSRAM +#ifndef MICROPY_HW_PSRAM_CS_PIN +#error "MICROPY_HW_ENABLE_PSRAM requires MICROPY_HW_PSRAM_CS_PIN" +#endif + +#define PSRAM_BASE _u(0x11000000) + +extern size_t psram_init(uint cs_pin); +#endif + +#endif From c3f7e7256a9d91d686c7b46dab58d8bad8bb94a7 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 9 Aug 2024 14:15:33 +0100 Subject: [PATCH 2/4] ports/rp2: Configure heap for PSRAM. PSRAM will be used exclusively if MICROPY_GC_SPLIT_HEAP == 0, it will be added to RAM if MICROPY_GC_SPLIT_HEAP == 1, and the system will fall back to RAM only if it's not detected. Due to the size of PSRAM, GC stack was overflowing and causing the GC to scan through the entire memory pool. This caused noticable slowdowns during GC. Increase the stack from 256 to 4096 bytes to avoid overflow and increase the stack entry type size to accomodate 8MB+ PSRAM. Changes: ports/rp2/mpconfigport.h: Make split-heap optional and enable by default. Increase GC stack entry type to uint32_t. Raise GC stack size. Co-authored-by: Kirk Benell Signed-off-by: Phil Howard --- ports/rp2/mpconfigport.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/rp2/mpconfigport.h b/ports/rp2/mpconfigport.h index 28022ba420942..3d65737266b49 100644 --- a/ports/rp2/mpconfigport.h +++ b/ports/rp2/mpconfigport.h @@ -86,7 +86,15 @@ #endif // Memory allocation policies +#if MICROPY_HW_ENABLE_PSRAM +#define MICROPY_GC_STACK_ENTRY_TYPE uint32_t +#define MICROPY_ALLOC_GC_STACK_SIZE (1024) // Avoid slowdown when GC stack overflow causes a full sweep of PSRAM-backed heap +#else #define MICROPY_GC_STACK_ENTRY_TYPE uint16_t +#endif +#ifndef MICROPY_GC_SPLIT_HEAP +#define MICROPY_GC_SPLIT_HEAP MICROPY_HW_ENABLE_PSRAM // whether PSRAM is added to or replaces the heap +#endif #define MICROPY_ALLOC_PATH_MAX (128) #define MICROPY_QSTR_BYTES_IN_HASH (1) From 21620de67b489f698b9b5502b9e4c3b33313a6e1 Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Sun, 11 Aug 2024 21:39:05 +0100 Subject: [PATCH 3/4] rp2/rp2_flash: Support writes from PSRAM. Add a 256 byte (FLASH_PAGE_SIZE) SRAM copy buffer to allow copies from PSRAM to flash. This would otherwise hardfault since PSRAM is disabled to write flash. Changes: ports/rp2/rp2_flash.c: Add 256 byte (flash page size) SRAM copy buffer for PSRAM to flash copies. Invalidate the XIP cache to purge any PSRAM data before critical flash operations. Co-authored-by: Phil Howard Co-authored-by: Angus Gratton Signed-off-by: Phil Howard --- ports/rp2/rp2_flash.c | 64 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index f3f154a1402d9..8739f17a4a548 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -33,9 +33,13 @@ #include "modrp2.h" #include "hardware/flash.h" #include "pico/binary_info.h" +#include "rp2_psram.h" #define BLOCK_SIZE_BYTES (FLASH_SECTOR_SIZE) +// Size of buffer for flash writes from PSRAM, since they are mutually exclusive +#define COPY_BUFFER_SIZE_BYTES (FLASH_PAGE_SIZE) + static_assert(MICROPY_HW_ROMFS_BYTES % 4096 == 0, "ROMFS size must be a multiple of 4K"); static_assert(MICROPY_HW_FLASH_STORAGE_BYTES % 4096 == 0, "Flash storage size must be a multiple of 4K"); @@ -96,10 +100,27 @@ static uint32_t begin_critical_flash_section(void) { if (use_multicore_lockout()) { multicore_lockout_start_blocking(); } - return save_and_disable_interrupts(); + uint32_t state = save_and_disable_interrupts(); + + #if MICROPY_HW_ENABLE_PSRAM + // We're about to invalidate the XIP cache, clean it first to commit any dirty writes to PSRAM + // Use the upper 16k of the maintenance space (0x1bffc000 through 0x1bffffff) to workaround + // incorrect behaviour of the XIP clean operation, where it also alters the tag of the associated + // cache line: https://forums.raspberrypi.com/viewtopic.php?t=378249#p2263677 + volatile uint8_t *maintenance_ptr = (volatile uint8_t *)(XIP_SRAM_BASE + (XIP_MAINTENANCE_BASE - XIP_BASE)); + for (int i = 1; i < 16 * 1024; i += 8) { + maintenance_ptr[i] = 0; + } + #endif + + return state; } static void end_critical_flash_section(uint32_t state) { + #if MICROPY_HW_ENABLE_PSRAM + // The ROM function to program flash will reset PSRAM timings to defaults + psram_init(MICROPY_HW_PSRAM_CS_PIN); + #endif restore_interrupts(state); if (use_multicore_lockout()) { multicore_lockout_end_blocking(); @@ -192,10 +213,43 @@ static mp_obj_t rp2_flash_writeblocks(size_t n_args, const mp_obj_t *args) { } else { offset += mp_obj_get_int(args[3]); } - mp_uint_t atomic_state = begin_critical_flash_section(); - flash_range_program(self->flash_base + offset, bufinfo.buf, bufinfo.len); - end_critical_flash_section(atomic_state); - mp_event_handle_nowait(); + + // If copying from SRAM, can write direct to flash. + // If copying from PSRAM/flash, use an SRAM buffer and write in chunks. + #if MICROPY_HW_ENABLE_PSRAM + bool write_direct = (uintptr_t)bufinfo.buf >= SRAM_BASE; + #else + bool write_direct = true; + #endif + + if (write_direct) { + // If copying from SRAM, write direct + mp_uint_t atomic_state = begin_critical_flash_section(); + flash_range_program(self->flash_base + offset, bufinfo.buf, bufinfo.len); + end_critical_flash_section(atomic_state); + mp_event_handle_nowait(); + } + #if MICROPY_HW_ENABLE_PSRAM + else { + size_t bytes_left = bufinfo.len; + size_t bytes_offset = 0; + static uint8_t copy_buffer[COPY_BUFFER_SIZE_BYTES] = {0}; + + while (bytes_left) { + memcpy(copy_buffer, bufinfo.buf + bytes_offset, MIN(bytes_left, COPY_BUFFER_SIZE_BYTES)); + mp_uint_t atomic_state = begin_critical_flash_section(); + flash_range_program(self->flash_base + offset + bytes_offset, copy_buffer, MIN(bytes_left, COPY_BUFFER_SIZE_BYTES)); + end_critical_flash_section(atomic_state); + bytes_offset += COPY_BUFFER_SIZE_BYTES; + if (bytes_left <= COPY_BUFFER_SIZE_BYTES) { + break; + } + bytes_left -= COPY_BUFFER_SIZE_BYTES; + mp_event_handle_nowait(); + } + } + #endif + // TODO check return value return mp_const_none; } From 326fd97b27001338c8e02027cf2412a2a4f010f8 Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Tue, 25 Mar 2025 11:32:06 +0000 Subject: [PATCH 4/4] rp2/rp2_flash: Configure optimal flash timings. Configure flash timings dynamically to match the system clock. Reconfigure timings after flash writes. Changes: ports/rp2/main.c: Set default flash timings. ports/rp2/modmachine.c: Configure optimal flash timings on freq change. ports/rp2/rp2_flash.c: Reconfigure flash when leaving critical section. Signed-off-by: Phil Howard --- ports/rp2/main.c | 4 +++ ports/rp2/modmachine.c | 12 +++++++ ports/rp2/rp2_flash.c | 71 +++++++++++++++++++++++++++++++++++++++++- ports/rp2/rp2_flash.h | 34 ++++++++++++++++++++ 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 ports/rp2/rp2_flash.h diff --git a/ports/rp2/main.c b/ports/rp2/main.c index 764af6b7466cf..0f10f63c6d296 100644 --- a/ports/rp2/main.c +++ b/ports/rp2/main.c @@ -26,6 +26,7 @@ #include +#include "rp2_flash.h" #include "py/compile.h" #include "py/cstack.h" #include "py/runtime.h" @@ -94,6 +95,9 @@ int main(int argc, char **argv) { // Hook for setting up anything that needs to be super early in the boot-up process. MICROPY_BOARD_STARTUP(); + // Set the flash divisor to an appropriate value + rp2_flash_set_timing(); + #if MICROPY_HW_ENABLE_PSRAM size_t psram_size = psram_init(MICROPY_HW_PSRAM_CS_PIN); #endif diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index bf4e43a613c90..58a3a8ae4d87d 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -32,6 +32,7 @@ #include "modmachine.h" #include "uart.h" #include "rp2_psram.h" +#include "rp2_flash.h" #include "clocks_extra.h" #include "hardware/pll.h" #include "hardware/structs/rosc.h" @@ -95,6 +96,11 @@ static mp_obj_t mp_machine_get_freq(void) { static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { mp_int_t freq = mp_obj_get_int(args[0]); + + // If necessary, increase the flash divider before increasing the clock speed + const int old_freq = clock_get_hz(clk_sys); + rp2_flash_set_timing_for_freq(MAX(freq, old_freq)); + if (!set_sys_clock_khz(freq / 1000, false)) { mp_raise_ValueError(MP_ERROR_TEXT("cannot change frequency")); } @@ -112,6 +118,12 @@ static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) { } } } + + // If clock speed was reduced, maybe we can reduce the flash divider + if (freq < old_freq) { + rp2_flash_set_timing_for_freq(freq); + } + #if MICROPY_HW_ENABLE_UART_REPL setup_default_uart(); mp_uart_init(); diff --git a/ports/rp2/rp2_flash.c b/ports/rp2/rp2_flash.c index 8739f17a4a548..a9beabf051c2d 100644 --- a/ports/rp2/rp2_flash.c +++ b/ports/rp2/rp2_flash.c @@ -34,6 +34,12 @@ #include "hardware/flash.h" #include "pico/binary_info.h" #include "rp2_psram.h" +#ifdef PICO_RP2350 +#include "hardware/structs/ioqspi.h" +#include "hardware/structs/qmi.h" +#else +#include "hardware/structs/ssi.h" +#endif #define BLOCK_SIZE_BYTES (FLASH_SECTOR_SIZE) @@ -94,6 +100,48 @@ static bool use_multicore_lockout(void) { ; } +// Function to set the flash divisor to the correct divisor, assumes interrupts disabled +// and core1 locked out if relevant. +static void __no_inline_not_in_flash_func(rp2_flash_set_timing_internal)(int clock_hz) { + + // Use the minimum divisor assuming a 133MHz flash. + const int max_flash_freq = 133000000; + int divisor = (clock_hz + max_flash_freq - 1) / max_flash_freq; + + #if PICO_RP2350 + // Make sure flash is deselected - QMI doesn't appear to have a busy flag(!) + while ((ioqspi_hw->io[1].status & IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) != IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) { + ; + } + + // RX delay equal to the divisor means sampling at the same time as the next falling edge of SCK after the + // falling edge that generated the data. This is pretty tight at 133MHz but seems to work with the Winbond flash chips. + const int rxdelay = divisor; + qmi_hw->m[0].timing = (1 << QMI_M0_TIMING_COOLDOWN_LSB) | + rxdelay << QMI_M1_TIMING_RXDELAY_LSB | + divisor << QMI_M1_TIMING_CLKDIV_LSB; + + // Force a read through XIP to ensure the timing is applied + volatile uint32_t *ptr = (volatile uint32_t *)0x14000000; + (void)*ptr; + #else + // RP2040 SSI hardware only supports even divisors + if (divisor & 1) { + divisor += 1; + } + + // Wait for SSI not busy + while (ssi_hw->sr & SSI_SR_BUSY_BITS) { + ; + } + + // Disable, set the new divisor, and re-enable + hw_clear_bits(&ssi_hw->ssienr, SSI_SSIENR_SSI_EN_BITS); + ssi_hw->baudr = divisor; + hw_set_bits(&ssi_hw->ssienr, SSI_SSIENR_SSI_EN_BITS); + #endif +} + // Flash erase and write must run with interrupts disabled and the other core suspended, // because the XIP bit gets disabled. static uint32_t begin_critical_flash_section(void) { @@ -117,8 +165,9 @@ static uint32_t begin_critical_flash_section(void) { } static void end_critical_flash_section(uint32_t state) { + // The ROM function to program flash will have reset flash and PSRAM timings to defaults + rp2_flash_set_timing_internal(clock_get_hz(clk_sys)); #if MICROPY_HW_ENABLE_PSRAM - // The ROM function to program flash will reset PSRAM timings to defaults psram_init(MICROPY_HW_PSRAM_CS_PIN); #endif restore_interrupts(state); @@ -313,3 +362,23 @@ mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) { } } #endif + +// Modify the flash timing. Ensure flash access is suspended while +// the timings are altered. +void rp2_flash_set_timing_for_freq(int clock_hz) { + if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { + multicore_lockout_start_blocking(); + } + uint32_t state = save_and_disable_interrupts(); + + rp2_flash_set_timing_internal(clock_hz); + + restore_interrupts(state); + if (multicore_lockout_victim_is_initialized(1 - get_core_num())) { + multicore_lockout_end_blocking(); + } +} + +void rp2_flash_set_timing(void) { + rp2_flash_set_timing_for_freq(clock_get_hz(clk_sys)); +} diff --git a/ports/rp2/rp2_flash.h b/ports/rp2/rp2_flash.h new file mode 100644 index 0000000000000..3c1fbbff2f9cd --- /dev/null +++ b/ports/rp2/rp2_flash.h @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Mike Bell + * Phil Howard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_RP2_RP2_FLASH_H +#define MICROPY_INCLUDED_RP2_RP2_FLASH_H + +extern void rp2_flash_set_timing_for_freq(int clock_hz); +extern void rp2_flash_set_timing(void); + +#endif