From b01ccd623604a1a56132af1f7d63ce4cc8436e59 Mon Sep 17 00:00:00 2001 From: ladyada Date: Tue, 25 Aug 2026 19:30:31 -0400 Subject: [PATCH 1/3] raspberrypi: derive QMI PSRAM timings from clk_sys Co-authored-by: Limor Fried --- ports/raspberrypi/supervisor/port.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ports/raspberrypi/supervisor/port.c b/ports/raspberrypi/supervisor/port.c index 8a8b9fb052c..8b858589c65 100644 --- a/ports/raspberrypi/supervisor/port.c +++ b/ports/raspberrypi/supervisor/port.c @@ -47,6 +47,7 @@ #include "supervisor/shared/stack.h" #include "supervisor/shared/tick.h" +#include "hardware/clocks.h" #include "hardware/structs/scb.h" #include "hardware/structs/watchdog.h" #include "hardware/gpio.h" @@ -152,6 +153,9 @@ static size_t _psram_size = 0; #include "hardware/structs/xip_ctrl.h" static void __no_inline_not_in_flash_func(setup_psram)(void) { + // Read the system clock before QMI goes into direct mode; clock_get_hz() is + // in flash and XIP is reconfigured below. + uint32_t sys_clk_mhz = clock_get_hz(clk_sys) / 1000000; gpio_set_function(CIRCUITPY_PSRAM_CHIP_SELECT->number, GPIO_FUNC_XIP_CS1); _psram_size = 0; common_hal_mcu_disable_interrupts(); @@ -237,13 +241,20 @@ static void __no_inline_not_in_flash_func(setup_psram)(void) { // Disable direct csr. qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS | QMI_DIRECT_CSR_EN_BITS); + // MAX_SELECT is in units of 64 system clock cycles; PSRAM allows 8us max CS + // assertion. Use 7.5us so there is margin at any clk_sys. + uint32_t max_select = (75 * sys_clk_mhz) / 10 / 64; + // MIN_DESELECT is in system clock cycles; PSRAM needs 50ns min CS + // deassertion. Round up so we are never under. + uint32_t min_deselect = (50 * sys_clk_mhz + 999) / 1000; + qmi_hw->m[1].timing = QMI_M0_TIMING_PAGEBREAK_VALUE_1024 << QMI_M0_TIMING_PAGEBREAK_LSB | // Break between pages. 3 << QMI_M0_TIMING_SELECT_HOLD_LSB | // Delay releasing CS for 3 extra system cycles. 1 << QMI_M0_TIMING_COOLDOWN_LSB | 1 << QMI_M0_TIMING_RXDELAY_LSB | - 16 << QMI_M0_TIMING_MAX_SELECT_LSB | // In units of 64 system clock cycles. PSRAM says 8us max. 8 / 0.00752 / 64 = 16.62 - 7 << QMI_M0_TIMING_MIN_DESELECT_LSB | // In units of system clock cycles. PSRAM says 50ns.50 / 7.52 = 6.64 + max_select << QMI_M0_TIMING_MAX_SELECT_LSB | + min_deselect << QMI_M0_TIMING_MIN_DESELECT_LSB | 2 << QMI_M0_TIMING_CLKDIV_LSB; 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 | From a8bb8c63b5deca2995f7df68874a18353daabbea Mon Sep 17 00:00:00 2001 From: ladyada Date: Wed, 26 Aug 2026 10:33:45 -0400 Subject: [PATCH 2/3] raspberrypi: update PSRAM timing after clock changes Co-authored-by: Limor Fried --- .../common-hal/microcontroller/Processor.c | 36 +++++++++++++++++++ .../common-hal/microcontroller/Processor.h | 4 +++ ports/raspberrypi/supervisor/port.c | 18 ++-------- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/ports/raspberrypi/common-hal/microcontroller/Processor.c b/ports/raspberrypi/common-hal/microcontroller/Processor.c index c093a964c39..a5f4691c075 100644 --- a/ports/raspberrypi/common-hal/microcontroller/Processor.c +++ b/ports/raspberrypi/common-hal/microcontroller/Processor.c @@ -10,6 +10,7 @@ #include "py/mphal.h" #include "py/runtime.h" #include "common-hal/microcontroller/Processor.h" +#include "shared-bindings/microcontroller/__init__.h" #include "shared-bindings/microcontroller/Processor.h" #include "shared-bindings/microcontroller/ResetReason.h" #include "shared-bindings/time/__init__.h" @@ -20,6 +21,11 @@ #include "hardware/vreg.h" #include "hardware/watchdog.h" +#ifdef CIRCUITPY_PSRAM_CHIP_SELECT +#include "hardware/regs/qmi.h" +#include "hardware/structs/qmi.h" +#endif + #if PICO_RP2040 #include "hardware/regs/vreg_and_chip_reset.h" #include "hardware/structs/vreg_and_chip_reset.h" @@ -50,6 +56,26 @@ uint32_t common_hal_mcu_processor_get_frequency(void) { return clock_get_hz(clk_sys); } +#ifdef CIRCUITPY_PSRAM_CHIP_SELECT +void __no_inline_not_in_flash_func(common_hal_mcu_processor_update_psram_timing)(uint32_t sys_clk_khz) { + // MAX_SELECT is in units of 64 system clock cycles; PSRAM allows 8 us max CS + // assertion. Use 7.5 us so there is margin at any clk_sys. + uint32_t max_select = (75 * sys_clk_khz) / 640000; + // MIN_DESELECT is in system clock cycles; PSRAM needs 50 ns min CS + // deassertion. Round up so we are never under. + uint32_t min_deselect = (sys_clk_khz + 19999) / 20000; + + qmi_hw->m[1].timing = + QMI_M0_TIMING_PAGEBREAK_VALUE_1024 << QMI_M0_TIMING_PAGEBREAK_LSB | // Break between pages. + 3 << QMI_M0_TIMING_SELECT_HOLD_LSB | // Delay releasing CS for 3 extra system cycles. + 1 << QMI_M0_TIMING_COOLDOWN_LSB | + 1 << QMI_M0_TIMING_RXDELAY_LSB | + max_select << QMI_M0_TIMING_MAX_SELECT_LSB | + min_deselect << QMI_M0_TIMING_MIN_DESELECT_LSB | + 2 << QMI_M0_TIMING_CLKDIV_LSB; +} +#endif + void common_hal_mcu_processor_set_frequency(mcu_processor_obj_t *self, uint32_t frequency) { uint vco, postdiv1, postdiv2; uint32_t freq_khz = frequency / 1000; @@ -68,7 +94,17 @@ void common_hal_mcu_processor_set_frequency(mcu_processor_obj_t *self, uint32_t vreg_set_voltage(voltage); // Wait for a stable voltage common_hal_time_delay_ms(10); + + #ifdef CIRCUITPY_PSRAM_CHIP_SELECT + // Prevent interrupt handlers from accessing PSRAM until its timing matches + // the new system clock. + common_hal_mcu_disable_interrupts(); + #endif set_sys_clock_khz(freq_khz, false); + #ifdef CIRCUITPY_PSRAM_CHIP_SELECT + common_hal_mcu_processor_update_psram_timing(freq_khz); + common_hal_mcu_enable_interrupts(); + #endif } void common_hal_mcu_processor_get_uid(uint8_t raw_id[]) { diff --git a/ports/raspberrypi/common-hal/microcontroller/Processor.h b/ports/raspberrypi/common-hal/microcontroller/Processor.h index df1e1cf2333..bf3293672b9 100644 --- a/ports/raspberrypi/common-hal/microcontroller/Processor.h +++ b/ports/raspberrypi/common-hal/microcontroller/Processor.h @@ -16,3 +16,7 @@ typedef struct { mp_obj_base_t base; // Stores no state currently. } mcu_processor_obj_t; + +#ifdef CIRCUITPY_PSRAM_CHIP_SELECT +void common_hal_mcu_processor_update_psram_timing(uint32_t sys_clk_khz); +#endif diff --git a/ports/raspberrypi/supervisor/port.c b/ports/raspberrypi/supervisor/port.c index 8b858589c65..23917061abd 100644 --- a/ports/raspberrypi/supervisor/port.c +++ b/ports/raspberrypi/supervisor/port.c @@ -155,7 +155,7 @@ static size_t _psram_size = 0; static void __no_inline_not_in_flash_func(setup_psram)(void) { // Read the system clock before QMI goes into direct mode; clock_get_hz() is // in flash and XIP is reconfigured below. - uint32_t sys_clk_mhz = clock_get_hz(clk_sys) / 1000000; + uint32_t sys_clk_khz = clock_get_hz(clk_sys) / 1000; gpio_set_function(CIRCUITPY_PSRAM_CHIP_SELECT->number, GPIO_FUNC_XIP_CS1); _psram_size = 0; common_hal_mcu_disable_interrupts(); @@ -241,21 +241,7 @@ static void __no_inline_not_in_flash_func(setup_psram)(void) { // Disable direct csr. qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS | QMI_DIRECT_CSR_EN_BITS); - // MAX_SELECT is in units of 64 system clock cycles; PSRAM allows 8us max CS - // assertion. Use 7.5us so there is margin at any clk_sys. - uint32_t max_select = (75 * sys_clk_mhz) / 10 / 64; - // MIN_DESELECT is in system clock cycles; PSRAM needs 50ns min CS - // deassertion. Round up so we are never under. - uint32_t min_deselect = (50 * sys_clk_mhz + 999) / 1000; - - qmi_hw->m[1].timing = - QMI_M0_TIMING_PAGEBREAK_VALUE_1024 << QMI_M0_TIMING_PAGEBREAK_LSB | // Break between pages. - 3 << QMI_M0_TIMING_SELECT_HOLD_LSB | // Delay releasing CS for 3 extra system cycles. - 1 << QMI_M0_TIMING_COOLDOWN_LSB | - 1 << QMI_M0_TIMING_RXDELAY_LSB | - max_select << QMI_M0_TIMING_MAX_SELECT_LSB | - min_deselect << QMI_M0_TIMING_MIN_DESELECT_LSB | - 2 << QMI_M0_TIMING_CLKDIV_LSB; + common_hal_mcu_processor_update_psram_timing(sys_clk_khz); 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 | From 7a5f6b1e63eb7ae6f849440b070ca63af777acac Mon Sep 17 00:00:00 2001 From: ladyada Date: Thu, 27 Aug 2026 22:21:57 -0400 Subject: [PATCH 3/3] raspberrypi: rename port-specific PSRAM timing helper Co-authored-by: Limor Fried --- ports/raspberrypi/common-hal/microcontroller/Processor.c | 4 ++-- ports/raspberrypi/common-hal/microcontroller/Processor.h | 2 +- ports/raspberrypi/supervisor/port.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/raspberrypi/common-hal/microcontroller/Processor.c b/ports/raspberrypi/common-hal/microcontroller/Processor.c index a5f4691c075..fbbef51acb4 100644 --- a/ports/raspberrypi/common-hal/microcontroller/Processor.c +++ b/ports/raspberrypi/common-hal/microcontroller/Processor.c @@ -57,7 +57,7 @@ uint32_t common_hal_mcu_processor_get_frequency(void) { } #ifdef CIRCUITPY_PSRAM_CHIP_SELECT -void __no_inline_not_in_flash_func(common_hal_mcu_processor_update_psram_timing)(uint32_t sys_clk_khz) { +void __no_inline_not_in_flash_func(mcu_processor_update_psram_timing)(uint32_t sys_clk_khz) { // MAX_SELECT is in units of 64 system clock cycles; PSRAM allows 8 us max CS // assertion. Use 7.5 us so there is margin at any clk_sys. uint32_t max_select = (75 * sys_clk_khz) / 640000; @@ -102,7 +102,7 @@ void common_hal_mcu_processor_set_frequency(mcu_processor_obj_t *self, uint32_t #endif set_sys_clock_khz(freq_khz, false); #ifdef CIRCUITPY_PSRAM_CHIP_SELECT - common_hal_mcu_processor_update_psram_timing(freq_khz); + mcu_processor_update_psram_timing(freq_khz); common_hal_mcu_enable_interrupts(); #endif } diff --git a/ports/raspberrypi/common-hal/microcontroller/Processor.h b/ports/raspberrypi/common-hal/microcontroller/Processor.h index bf3293672b9..9503eaf21f4 100644 --- a/ports/raspberrypi/common-hal/microcontroller/Processor.h +++ b/ports/raspberrypi/common-hal/microcontroller/Processor.h @@ -18,5 +18,5 @@ typedef struct { } mcu_processor_obj_t; #ifdef CIRCUITPY_PSRAM_CHIP_SELECT -void common_hal_mcu_processor_update_psram_timing(uint32_t sys_clk_khz); +void mcu_processor_update_psram_timing(uint32_t sys_clk_khz); #endif diff --git a/ports/raspberrypi/supervisor/port.c b/ports/raspberrypi/supervisor/port.c index 23917061abd..34e9fc159fd 100644 --- a/ports/raspberrypi/supervisor/port.c +++ b/ports/raspberrypi/supervisor/port.c @@ -241,7 +241,7 @@ static void __no_inline_not_in_flash_func(setup_psram)(void) { // Disable direct csr. qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS | QMI_DIRECT_CSR_EN_BITS); - common_hal_mcu_processor_update_psram_timing(sys_clk_khz); + mcu_processor_update_psram_timing(sys_clk_khz); 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 |