Skip to content

rp2: RP235: PSRAM support #15620

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

Closed
wants to merge 4 commits into from
Closed
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
1 change: 1 addition & 0 deletions ports/rp2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions ports/rp2/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include <stdio.h>

#include "rp2_flash.h"
#include "py/compile.h"
#include "py/cstack.h"
#include "py/runtime.h"
Expand All @@ -46,6 +47,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"
Expand Down Expand Up @@ -93,6 +95,13 @@ 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

#if MICROPY_HW_ENABLE_UART_REPL
bi_decl(bi_program_feature("UART REPL"))
setup_default_uart();
Expand Down Expand Up @@ -120,7 +129,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
Expand Down
16 changes: 16 additions & 0 deletions ports/rp2/modmachine.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
#include "mp_usbd.h"
#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"
Expand Down Expand Up @@ -94,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"));
}
Expand All @@ -111,10 +118,19 @@ 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();
#endif
#if MICROPY_HW_ENABLE_PSRAM
psram_init(MICROPY_HW_PSRAM_CS_PIN);
#endif
}

static void mp_machine_idle(void) {
Expand Down
12 changes: 12 additions & 0 deletions ports/rp2/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,20 @@
#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
#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)

Expand Down
133 changes: 128 additions & 5 deletions ports/rp2/rp2_flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,19 @@
#include "modrp2.h"
#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)

// 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");

Expand Down Expand Up @@ -90,16 +100,76 @@ 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) {
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) {
// 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
psram_init(MICROPY_HW_PSRAM_CS_PIN);
#endif
restore_interrupts(state);
if (use_multicore_lockout()) {
multicore_lockout_end_blocking();
Expand Down Expand Up @@ -192,10 +262,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;
}
Expand Down Expand Up @@ -259,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));
}
34 changes: 34 additions & 0 deletions ports/rp2/rp2_flash.h
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading