From b01ccd623604a1a56132af1f7d63ce4cc8436e59 Mon Sep 17 00:00:00 2001 From: ladyada Date: Tue, 25 Aug 2026 19:30:31 -0400 Subject: [PATCH 1/5] 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/5] 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 0cf0486bc08843976bf7a34dcb4b97c4fc3a8375 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Thu, 27 Aug 2026 15:06:41 -0700 Subject: [PATCH 3/5] Raise zephyr test timeouts and build nrf54lm20 bsim --- .github/workflows/run-tests.yml | 2 ++ ports/zephyr-cp/tests/bsim/test_bsim_ble_advertising.py | 1 + ports/zephyr-cp/tests/bsim/test_bsim_ble_peripheral.py | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5bb3aaee736..711b5f321b6 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -99,5 +99,7 @@ jobs: working-directory: ports/zephyr-cp/tools/bsim - name: Build native_nrf5340bsim run: make -C ports/zephyr-cp -j2 BOARD=native_nrf5340bsim + - name: Build native_nrf54lm20bsim + run: make -C ports/zephyr-cp -j2 BOARD=native_nrf54lm20bsim - name: Run Zephyr tests run: make -C ports/zephyr-cp test diff --git a/ports/zephyr-cp/tests/bsim/test_bsim_ble_advertising.py b/ports/zephyr-cp/tests/bsim/test_bsim_ble_advertising.py index 27fa78a5b7f..c8450e2af9f 100644 --- a/ports/zephyr-cp/tests/bsim/test_bsim_ble_advertising.py +++ b/ports/zephyr-cp/tests/bsim/test_bsim_ble_advertising.py @@ -128,6 +128,7 @@ @pytest.mark.zephyr_sample("tests/bsim/samples/observer") @pytest.mark.circuitpy_drive({"code.py": BSIM_ADV_CODE}) +@pytest.mark.duration(60) def test_bsim_advertise_and_scan(bsim_phy, circuitpython, zephyr_sample): """Advertise from CircuitPython and verify Zephyr observer sees traffic.""" observer = zephyr_sample diff --git a/ports/zephyr-cp/tests/bsim/test_bsim_ble_peripheral.py b/ports/zephyr-cp/tests/bsim/test_bsim_ble_peripheral.py index a8eb2612b40..ff99b789f0c 100644 --- a/ports/zephyr-cp/tests/bsim/test_bsim_ble_peripheral.py +++ b/ports/zephyr-cp/tests/bsim/test_bsim_ble_peripheral.py @@ -95,7 +95,7 @@ def test_bsim_peripheral_zephyr_central(bsim_phy, circuitpython, zephyr_sample): assert "Disconnected:" in central_output -@pytest.mark.duration(14) +@pytest.mark.duration(20) @pytest.mark.circuitpy_drive({"code.py": BSIM_PERIPHERAL_CODE}) @pytest.mark.circuitpy_drive({"code.py": BSIM_CENTRAL_CODE}) def test_bsim_peripheral_cp_central(bsim_phy, circuitpython1, circuitpython2): From 689d4deeff20ab4b7952c11602f9cdf33da8e668 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Thu, 27 Aug 2026 19:35:56 -0400 Subject: [PATCH 4/5] shrink pybadge: turn off epaperdisplay --- ports/atmel-samd/boards/pybadge/mpconfigboard.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/atmel-samd/boards/pybadge/mpconfigboard.mk b/ports/atmel-samd/boards/pybadge/mpconfigboard.mk index 70571b6c77d..86ce2d47e8c 100644 --- a/ports/atmel-samd/boards/pybadge/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pybadge/mpconfigboard.mk @@ -14,6 +14,7 @@ CIRCUITPY_AESIO = 0 CIRCUITPY_FLOPPYIO = 0 CIRCUITPY_FRAMEBUFFERIO = 0 CIRCUITPY_GIFIO = 0 +CIRCUITPY_EPAPERDISPLAY = 0 CIRCUITPY_I2CDISPLAYBUS = 0 CIRCUITPY_JPEGIO = 0 CIRCUITPY_KEYPAD = 1 From 7a5f6b1e63eb7ae6f849440b070ca63af777acac Mon Sep 17 00:00:00 2001 From: ladyada Date: Thu, 27 Aug 2026 22:21:57 -0400 Subject: [PATCH 5/5] 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 |