Add PSRAM support (#2919)
Supports enabling PSRAM by injecting a function that runs when restoring QMI CS1 settings
This will always run runtime_init_setup_psram function when hardware_psram is linked
If PICO_PSRAM_SIZE_BYTES and PICO_AUTO_DETECT_PSRAM_SIZE are both 0 or undefined, it will only initialise when flash_devinfo is already setup (eg with OTP)
Also setup to bus fault with too small PSRAM, add kitchen sink test for those bus faults, and don't trigger those bus faults in runtime_init_setup_psram
* Add psram_check_address to check if psram variable can be used
Also fix no_flash binaries, and the tiny_psram test
* Add psram_or_malloc and psram_or_free
These will either statically allocate in psram, or malloc if the static allocation is not in available psram. Sections are sorted, so the group can be used to set priority.
* Tweak check_board_header.py to also validate the PSRAM settings (#2924)
* Fix low power compilation issue with clang
Clang was trying to place persistent_data in PSRAM at the relevant address (eg 0x200xxxxx), and then complaining that it overflowed
Fix is to add `> XXX` to the end, to explicitly put it in RAM/XIP_RAM
* Allow PICO_CONFIG type=list
Copied from extract_cmake_configs.py
* Fix tlsX_not_needed_marker into RAM
Without this, tlsX_not_needed_marker gets placed in PSRAM for some reason, which then doesn't fit if PSRAM_SIZE_BYTES is 0
---------
Co-authored-by: Andrew Scheller <andrew.scheller@raspberrypi.com>
Co-authored-by: graham sanderson <graham.sanderson@raspberrypi.com>
diff --git a/docs/index.h b/docs/index.h
index ebf1d0a..fe82799 100644
--- a/docs/index.h
+++ b/docs/index.h
@@ -31,6 +31,7 @@
* \cond hardware_pio \defgroup hardware_pio hardware_pio \endcond
* \cond hardware_pll \defgroup hardware_pll hardware_pll \endcond
* \cond hardware_powman \defgroup hardware_powman hardware_powman \endcond
+ * \cond hardware_psram \defgroup hardware_psram hardware_psram \endcond
* \cond hardware_pwm \defgroup hardware_pwm hardware_pwm \endcond
* \cond hardware_resets \defgroup hardware_resets hardware_resets \endcond
* \cond hardware_riscv \defgroup hardware_riscv hardware_riscv \endcond
diff --git a/src/boards/include/boards/adafruit_feather_rp2350.h b/src/boards/include/boards/adafruit_feather_rp2350.h
index 08227df..7ef3f49 100644
--- a/src/boards/include/boards/adafruit_feather_rp2350.h
+++ b/src/boards/include/boards/adafruit_feather_rp2350.h
@@ -83,6 +83,16 @@
#define PICO_FLASH_SIZE_BYTES (8 * 1024 * 1024)
#endif
+// --- PSRAM ---
+#ifndef PICO_PSRAM_CS_PIN
+#define PICO_PSRAM_CS_PIN 8
+#endif
+
+// Only some boards have PSRAM fitted, so auto-detect
+#ifndef PICO_AUTO_DETECT_PSRAM_SIZE
+#define PICO_AUTO_DETECT_PSRAM_SIZE 1
+#endif
+
pico_board_cmake_set_default(PICO_RP2350_A2_SUPPORTED, 1)
#ifndef PICO_RP2350_A2_SUPPORTED
#define PICO_RP2350_A2_SUPPORTED 1
diff --git a/src/boards/include/boards/adafruit_fruit_jam.h b/src/boards/include/boards/adafruit_fruit_jam.h
index d0f9086..0ff04a2 100644
--- a/src/boards/include/boards/adafruit_fruit_jam.h
+++ b/src/boards/include/boards/adafruit_fruit_jam.h
@@ -205,6 +205,16 @@
#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024)
#endif
+// --- PSRAM ---
+#ifndef PICO_PSRAM_CS_PIN
+#define PICO_PSRAM_CS_PIN 47
+#endif
+
+pico_board_cmake_set_default(PICO_PSRAM_SIZE_BYTES, (8 * 1024 * 1024))
+#ifndef PICO_PSRAM_SIZE_BYTES
+#define PICO_PSRAM_SIZE_BYTES (8 * 1024 * 1024)
+#endif
+
pico_board_cmake_set_default(PICO_RP2350_A2_SUPPORTED, 1)
#ifndef PICO_RP2350_A2_SUPPORTED
#define PICO_RP2350_A2_SUPPORTED 1
diff --git a/src/boards/include/boards/defcon32_badge.h b/src/boards/include/boards/defcon32_badge.h
index ce18d61..4d4f9d0 100644
--- a/src/boards/include/boards/defcon32_badge.h
+++ b/src/boards/include/boards/defcon32_badge.h
@@ -95,6 +95,16 @@
#define PICO_AUDIO_PWM_MONO_PIN PICO_AUDIO_PWM_L_PIN
#endif
+// --- PSRAM ---
+#ifndef PICO_PSRAM_CS_PIN
+#define PICO_PSRAM_CS_PIN DEFCON32_BADGE_SRAM_CS_PIN
+#endif
+
+// PSRAM not fitted by default, so auto-detect
+#ifndef PICO_AUTO_DETECT_PSRAM_SIZE
+#define PICO_AUTO_DETECT_PSRAM_SIZE 1
+#endif
+
// --- FLASH ---
#define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1
diff --git a/src/boards/include/boards/pimoroni_pga2350.h b/src/boards/include/boards/pimoroni_pga2350.h
index 7849444..fb13c24 100644
--- a/src/boards/include/boards/pimoroni_pga2350.h
+++ b/src/boards/include/boards/pimoroni_pga2350.h
@@ -84,6 +84,16 @@
#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024)
#endif
+// --- PSRAM ---
+#ifndef PICO_PSRAM_CS_PIN
+#define PICO_PSRAM_CS_PIN PIMORONI_PGA2350_PSRAM_CS_PIN
+#endif
+
+pico_board_cmake_set_default(PICO_PSRAM_SIZE_BYTES, (8 * 1024 * 1024))
+#ifndef PICO_PSRAM_SIZE_BYTES
+#define PICO_PSRAM_SIZE_BYTES (8 * 1024 * 1024)
+#endif
+
// no PICO_SMPS_MODE_PIN
// no PICO_VBUS_PIN
// no PICO_VSYS_PIN
diff --git a/src/boards/include/boards/pimoroni_pico_plus2_rp2350.h b/src/boards/include/boards/pimoroni_pico_plus2_rp2350.h
index 6cae74b..f3aacad 100644
--- a/src/boards/include/boards/pimoroni_pico_plus2_rp2350.h
+++ b/src/boards/include/boards/pimoroni_pico_plus2_rp2350.h
@@ -91,6 +91,16 @@
#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024)
#endif
+// --- PSRAM ---
+#ifndef PICO_PSRAM_CS_PIN
+#define PICO_PSRAM_CS_PIN PIMORONI_PICO_PLUS2_PSRAM_CS_PIN
+#endif
+
+pico_board_cmake_set_default(PICO_PSRAM_SIZE_BYTES, (8 * 1024 * 1024))
+#ifndef PICO_PSRAM_SIZE_BYTES
+#define PICO_PSRAM_SIZE_BYTES (8 * 1024 * 1024)
+#endif
+
// The GPIO Pin used to read VBUS to determine if the device is battery powered.
#ifndef PICO_VBUS_PIN
#define PICO_VBUS_PIN 24
diff --git a/src/boards/include/boards/pimoroni_pico_plus2_w_rp2350.h b/src/boards/include/boards/pimoroni_pico_plus2_w_rp2350.h
index 7f125aa..d8a8156 100644
--- a/src/boards/include/boards/pimoroni_pico_plus2_w_rp2350.h
+++ b/src/boards/include/boards/pimoroni_pico_plus2_w_rp2350.h
@@ -83,6 +83,16 @@
#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024)
#endif
+// --- PSRAM ---
+#ifndef PICO_PSRAM_CS_PIN
+#define PICO_PSRAM_CS_PIN PIMORONI_PICO_PLUS2_W_PSRAM_CS_PIN
+#endif
+
+pico_board_cmake_set_default(PICO_PSRAM_SIZE_BYTES, (8 * 1024 * 1024))
+#ifndef PICO_PSRAM_SIZE_BYTES
+#define PICO_PSRAM_SIZE_BYTES (8 * 1024 * 1024)
+#endif
+
// The GPIO Pin used to monitor VSYS. Typically you would use this with ADC.
// There is an example in adc/read_vsys in pico-examples.
#ifndef PICO_VSYS_PIN
diff --git a/src/boards/include/boards/sparkfun_iotnode_lorawan_rp2350.h b/src/boards/include/boards/sparkfun_iotnode_lorawan_rp2350.h
index e0e3297..f410f0d 100644
--- a/src/boards/include/boards/sparkfun_iotnode_lorawan_rp2350.h
+++ b/src/boards/include/boards/sparkfun_iotnode_lorawan_rp2350.h
@@ -80,6 +80,16 @@
#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024)
#endif
+// --- PSRAM ---
+#ifndef PICO_PSRAM_CS_PIN
+#define PICO_PSRAM_CS_PIN 0
+#endif
+
+pico_board_cmake_set_default(PICO_PSRAM_SIZE_BYTES, (8 * 1024 * 1024))
+#ifndef PICO_PSRAM_SIZE_BYTES
+#define PICO_PSRAM_SIZE_BYTES (8 * 1024 * 1024)
+#endif
+
pico_board_cmake_set_default(PICO_RP2350_A2_SUPPORTED, 1)
#ifndef PICO_RP2350_A2_SUPPORTED
#define PICO_RP2350_A2_SUPPORTED 1
diff --git a/src/boards/include/boards/sparkfun_iotredboard_rp2350.h b/src/boards/include/boards/sparkfun_iotredboard_rp2350.h
index 0c1d425..c9249bf 100644
--- a/src/boards/include/boards/sparkfun_iotredboard_rp2350.h
+++ b/src/boards/include/boards/sparkfun_iotredboard_rp2350.h
@@ -90,6 +90,16 @@
#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024)
#endif
+// --- PSRAM ---
+#ifndef PICO_PSRAM_CS_PIN
+#define PICO_PSRAM_CS_PIN SPARKFUN_IOTREDBOARD_RP2350_PSRAM_CS_PIN
+#endif
+
+pico_board_cmake_set_default(PICO_PSRAM_SIZE_BYTES, (8 * 1024 * 1024))
+#ifndef PICO_PSRAM_SIZE_BYTES
+#define PICO_PSRAM_SIZE_BYTES (8 * 1024 * 1024)
+#endif
+
// The IoT RedBoard has an SD Card.
#ifndef PICO_SD_CLK_PIN
#define PICO_SD_CLK_PIN 10
diff --git a/src/boards/include/boards/sparkfun_promicro_rp2350.h b/src/boards/include/boards/sparkfun_promicro_rp2350.h
index d247e59..9934045 100644
--- a/src/boards/include/boards/sparkfun_promicro_rp2350.h
+++ b/src/boards/include/boards/sparkfun_promicro_rp2350.h
@@ -78,6 +78,16 @@
#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024)
#endif
+// --- PSRAM ---
+#ifndef PICO_PSRAM_CS_PIN
+#define PICO_PSRAM_CS_PIN 19
+#endif
+
+pico_board_cmake_set_default(PICO_PSRAM_SIZE_BYTES, (8 * 1024 * 1024))
+#ifndef PICO_PSRAM_SIZE_BYTES
+#define PICO_PSRAM_SIZE_BYTES (8 * 1024 * 1024)
+#endif
+
// --- RP2350 VARIANT ---
#define PICO_RP2350A 1
diff --git a/src/boards/include/boards/sparkfun_thingplus_rp2350.h b/src/boards/include/boards/sparkfun_thingplus_rp2350.h
index 3f0e39a..6fa09e4 100644
--- a/src/boards/include/boards/sparkfun_thingplus_rp2350.h
+++ b/src/boards/include/boards/sparkfun_thingplus_rp2350.h
@@ -82,6 +82,16 @@
#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024)
#endif
+// --- PSRAM ---
+#ifndef PICO_PSRAM_CS_PIN
+#define PICO_PSRAM_CS_PIN 8
+#endif
+
+pico_board_cmake_set_default(PICO_PSRAM_SIZE_BYTES, (8 * 1024 * 1024))
+#ifndef PICO_PSRAM_SIZE_BYTES
+#define PICO_PSRAM_SIZE_BYTES (8 * 1024 * 1024)
+#endif
+
// The thing plus has a SD Card.
#ifndef PICO_SD_CLK_PIN
#define PICO_SD_CLK_PIN 2
diff --git a/src/boards/include/boards/sparkfun_xrp_controller.h b/src/boards/include/boards/sparkfun_xrp_controller.h
index 6b70995..1030642 100644
--- a/src/boards/include/boards/sparkfun_xrp_controller.h
+++ b/src/boards/include/boards/sparkfun_xrp_controller.h
@@ -82,6 +82,16 @@
#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024)
#endif
+// --- PSRAM ---
+#ifndef PICO_PSRAM_CS_PIN
+#define PICO_PSRAM_CS_PIN 47
+#endif
+
+pico_board_cmake_set_default(PICO_PSRAM_SIZE_BYTES, (8 * 1024 * 1024))
+#ifndef PICO_PSRAM_SIZE_BYTES
+#define PICO_PSRAM_SIZE_BYTES (8 * 1024 * 1024)
+#endif
+
#ifndef CYW43_WL_GPIO_COUNT
#define CYW43_WL_GPIO_COUNT 3
#endif
diff --git a/src/boards/include/boards/weact_studio_rp2350b_core.h b/src/boards/include/boards/weact_studio_rp2350b_core.h
index 5e05e24..73d492d 100644
--- a/src/boards/include/boards/weact_studio_rp2350b_core.h
+++ b/src/boards/include/boards/weact_studio_rp2350b_core.h
@@ -79,6 +79,16 @@
#define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024)
#endif
+// --- PSRAM ---
+#ifndef PICO_PSRAM_CS_PIN
+#define PICO_PSRAM_CS_PIN WEACT_STUDIO_RP2350B_PSRAM_CS_PIN
+#endif
+
+pico_board_cmake_set_default(PICO_PSRAM_SIZE_BYTES, (8 * 1024 * 1024))
+#ifndef PICO_PSRAM_SIZE_BYTES
+#define PICO_PSRAM_SIZE_BYTES (8 * 1024 * 1024)
+#endif
+
// --- RP2350 VARIANT ---
// This means RP2350B.
#define PICO_RP2350A 0
diff --git a/src/cmake/rp2_common.cmake b/src/cmake/rp2_common.cmake
index f466433..243def6 100644
--- a/src/cmake/rp2_common.cmake
+++ b/src/cmake/rp2_common.cmake
@@ -66,6 +66,7 @@
if (PICO_COMBINED_DOCS OR NOT PICO_RP2040)
pico_add_subdirectory(rp2_common/hardware_powman)
+ pico_add_subdirectory(rp2_common/hardware_psram)
# Note in spite of the name this is usable on Arm as well as RISC-V:
pico_add_subdirectory(rp2_common/hardware_riscv_platform_timer)
pico_add_subdirectory(rp2_common/hardware_sha256)
diff --git a/src/rp2040/pico_platform/memmap_blocked_ram.ld b/src/rp2040/pico_platform/memmap_blocked_ram.ld
index 049e0b0..8ad9dbd 100644
--- a/src/rp2040/pico_platform/memmap_blocked_ram.ld
+++ b/src/rp2040/pico_platform/memmap_blocked_ram.ld
@@ -12,6 +12,7 @@
* └── memmap_default.incl rp2_common/pico_standard_link
* ├── set_memory_locations.incl rp2_common/pico_standard_link
* ├── memory_flash.incl rp2_common/pico_standard_link
+ * ├── memory_psram.incl rp2040/pico_platform
* ├── memory_xip_ram.incl rp2_common/pico_standard_link
* ├── memory_ram.incl rp2_common/pico_standard_link
* ├── memory_scratch.incl rp2_common/pico_standard_link
@@ -43,6 +44,7 @@
* ├── section_generated_post_scratch.incl rp2_common/pico_standard_link
* ├── section_extra_post_scratch.incl rp2_common/pico_standard_link
* ├── sections_stack.incl rp2_common/pico_standard_link
+ * ├── sections_psram.incl rp2040/pico_platform
* ├── section_flash_end.incl rp2_common/pico_standard_link
* ├── section_end.incl rp2_common/pico_standard_link
* ├── section_generated_post_end.incl rp2_common/pico_standard_link
diff --git a/src/rp2040/pico_platform/memmap_copy_to_ram.ld b/src/rp2040/pico_platform/memmap_copy_to_ram.ld
index 330f170..dfc053a 100644
--- a/src/rp2040/pico_platform/memmap_copy_to_ram.ld
+++ b/src/rp2040/pico_platform/memmap_copy_to_ram.ld
@@ -9,6 +9,7 @@
* └── memmap_copy_to_ram.incl rp2_common/pico_standard_link
* ├── set_memory_locations.incl rp2_common/pico_standard_link
* ├── memory_flash.incl rp2_common/pico_standard_link
+ * ├── memory_psram.incl rp2040/pico_platform
* ├── memory_xip_ram.incl rp2_common/pico_standard_link
* ├── memory_ram.incl rp2_common/pico_standard_link
* ├── memory_scratch.incl rp2_common/pico_standard_link
@@ -39,6 +40,7 @@
* ├── section_generated_post_scratch.incl rp2_common/pico_standard_link
* ├── section_extra_post_scratch.incl rp2_common/pico_standard_link
* ├── sections_stack.incl rp2_common/pico_standard_link
+ * ├── sections_psram.incl rp2040/pico_platform
* ├── section_flash_end.incl rp2_common/pico_standard_link
* ├── section_end.incl rp2_common/pico_standard_link
* ├── section_generated_post_end.incl rp2_common/pico_standard_link
diff --git a/src/rp2040/pico_platform/memmap_default.ld b/src/rp2040/pico_platform/memmap_default.ld
index b02671e..3912267 100644
--- a/src/rp2040/pico_platform/memmap_default.ld
+++ b/src/rp2040/pico_platform/memmap_default.ld
@@ -9,6 +9,7 @@
* └── memmap_default.incl rp2_common/pico_standard_link
* ├── set_memory_locations.incl rp2_common/pico_standard_link
* ├── memory_flash.incl rp2_common/pico_standard_link
+ * ├── memory_psram.incl rp2040/pico_platform
* ├── memory_xip_ram.incl rp2_common/pico_standard_link
* ├── memory_ram.incl rp2_common/pico_standard_link
* ├── memory_scratch.incl rp2_common/pico_standard_link
@@ -40,6 +41,7 @@
* ├── section_generated_post_scratch.incl rp2_common/pico_standard_link
* ├── section_extra_post_scratch.incl rp2_common/pico_standard_link
* ├── sections_stack.incl rp2_common/pico_standard_link
+ * ├── sections_psram.incl rp2040/pico_platform
* ├── section_flash_end.incl rp2_common/pico_standard_link
* ├── section_end.incl rp2_common/pico_standard_link
* ├── section_generated_post_end.incl rp2_common/pico_standard_link
diff --git a/src/rp2040/pico_platform/memmap_no_flash.ld b/src/rp2040/pico_platform/memmap_no_flash.ld
index 1ae20f2..3e9109b 100644
--- a/src/rp2040/pico_platform/memmap_no_flash.ld
+++ b/src/rp2040/pico_platform/memmap_no_flash.ld
@@ -8,6 +8,7 @@
* memmap_no_flash.ld rp2040/pico_platform
* └── memmap_no_flash.incl rp2_common/pico_standard_link
* ├── set_memory_locations.incl rp2_common/pico_standard_link
+ * ├── memory_psram.incl rp2040/pico_platform
* ├── memory_xip_ram.incl rp2_common/pico_standard_link
* ├── memory_ram.incl rp2_common/pico_standard_link
* ├── memory_scratch.incl rp2_common/pico_standard_link
@@ -34,6 +35,7 @@
* ├── section_generated_post_scratch.incl rp2_common/pico_standard_link
* ├── section_extra_post_scratch.incl rp2_common/pico_standard_link
* ├── sections_stack.incl rp2_common/pico_standard_link
+ * ├── sections_psram.incl rp2040/pico_platform
* ├── section_end.incl rp2_common/pico_standard_link
* ├── section_generated_post_end.incl rp2_common/pico_standard_link
* ├── section_extra_post_end.incl rp2_common/pico_standard_link
diff --git a/src/rp2040/pico_platform/script_include/memory_psram.incl b/src/rp2040/pico_platform/script_include/memory_psram.incl
new file mode 100644
index 0000000..7613c5e
--- /dev/null
+++ b/src/rp2040/pico_platform/script_include/memory_psram.incl
@@ -0,0 +1 @@
+/* RP2040 has no PSRAM support */
diff --git a/src/rp2040/pico_platform/script_include/sections_psram.incl b/src/rp2040/pico_platform/script_include/sections_psram.incl
new file mode 100644
index 0000000..7613c5e
--- /dev/null
+++ b/src/rp2040/pico_platform/script_include/sections_psram.incl
@@ -0,0 +1 @@
+/* RP2040 has no PSRAM support */
diff --git a/src/rp2350/boot_stage2/boot_stage2.ld b/src/rp2350/boot_stage2/boot_stage2.ld
index ee1bce4..ad34a72 100644
--- a/src/rp2350/boot_stage2/boot_stage2.ld
+++ b/src/rp2350/boot_stage2/boot_stage2.ld
@@ -1,7 +1,7 @@
MEMORY {
/* We are loaded to the top 256 bytes of SRAM, which is above the bootrom
- stack. Note 4 bytes occupied by checksum. */
- SRAM(rx) : ORIGIN = 0x20081f00, LENGTH = 252
+ stack. */
+ SRAM(rx) : ORIGIN = 0x20081f00, LENGTH = 256
}
SECTIONS {
diff --git a/src/rp2350/pico_platform/BUILD.bazel b/src/rp2350/pico_platform/BUILD.bazel
index 4efa080..6705488 100644
--- a/src/rp2350/pico_platform/BUILD.bazel
+++ b/src/rp2350/pico_platform/BUILD.bazel
@@ -93,6 +93,7 @@
"//src/rp2350/pico_platform/script_include:rp2350_linker_scripts",
"//src/rp2_common/pico_standard_link/script_include:rp2_linker_scripts",
"//src/rp2_common/pico_standard_link:default_flash_region",
+ "//src/rp2_common/pico_standard_link:default_psram_region",
"memmap_default",
],
)
@@ -112,6 +113,7 @@
"//src/rp2350/pico_platform/script_include:rp2350_linker_scripts",
"//src/rp2_common/pico_standard_link/script_include:rp2_linker_scripts",
"//src/rp2_common/pico_standard_link:default_flash_region",
+ "//src/rp2_common/pico_standard_link:default_psram_region",
"memmap_copy_to_ram",
],
)
@@ -130,6 +132,7 @@
"//src/rp2_common/pico_crt0:no_warn_rwx_flag",
"//src/rp2350/pico_platform/script_include:rp2350_linker_scripts",
"//src/rp2_common/pico_standard_link/script_include:rp2_linker_scripts",
+ "//src/rp2_common/pico_standard_link:default_psram_region",
"memmap_no_flash",
],
)
diff --git a/src/rp2350/pico_platform/memmap_copy_to_ram.ld b/src/rp2350/pico_platform/memmap_copy_to_ram.ld
index b9437c8..b2f3255 100644
--- a/src/rp2350/pico_platform/memmap_copy_to_ram.ld
+++ b/src/rp2350/pico_platform/memmap_copy_to_ram.ld
@@ -9,6 +9,7 @@
* └── memmap_copy_to_ram.incl rp2_common/pico_standard_link
* ├── set_memory_locations.incl rp2_common/pico_standard_link
* ├── memory_flash.incl rp2_common/pico_standard_link
+ * ├── memory_psram.incl rp2_common/pico_standard_link
* ├── memory_xip_ram.incl rp2_common/pico_standard_link
* ├── memory_ram.incl rp2_common/pico_standard_link
* ├── memory_scratch.incl rp2_common/pico_standard_link
@@ -39,6 +40,7 @@
* ├── section_generated_post_scratch.incl rp2_common/pico_standard_link
* ├── section_extra_post_scratch.incl rp2_common/pico_standard_link
* ├── sections_stack.incl rp2_common/pico_standard_link
+ * ├── sections_psram.incl rp2_common/pico_standard_link
* ├── section_flash_end.incl rp2_common/pico_standard_link
* ├── section_end.incl rp2_common/pico_standard_link
* ├── section_generated_post_end.incl rp2_common/pico_standard_link
diff --git a/src/rp2350/pico_platform/memmap_default.ld b/src/rp2350/pico_platform/memmap_default.ld
index badeb46..b63a1ae 100644
--- a/src/rp2350/pico_platform/memmap_default.ld
+++ b/src/rp2350/pico_platform/memmap_default.ld
@@ -9,6 +9,7 @@
* └── memmap_default.incl rp2_common/pico_standard_link
* ├── set_memory_locations.incl rp2_common/pico_standard_link
* ├── memory_flash.incl rp2_common/pico_standard_link
+ * ├── memory_psram.incl rp2_common/pico_standard_link
* ├── memory_xip_ram.incl rp2_common/pico_standard_link
* ├── memory_ram.incl rp2_common/pico_standard_link
* ├── memory_scratch.incl rp2_common/pico_standard_link
@@ -40,6 +41,7 @@
* ├── section_generated_post_scratch.incl rp2_common/pico_standard_link
* ├── section_extra_post_scratch.incl rp2_common/pico_standard_link
* ├── sections_stack.incl rp2_common/pico_standard_link
+ * ├── sections_psram.incl rp2_common/pico_standard_link
* ├── section_flash_end.incl rp2_common/pico_standard_link
* ├── section_end.incl rp2_common/pico_standard_link
* ├── section_generated_post_end.incl rp2_common/pico_standard_link
diff --git a/src/rp2350/pico_platform/memmap_no_flash.ld b/src/rp2350/pico_platform/memmap_no_flash.ld
index 55e4d08..b251780 100644
--- a/src/rp2350/pico_platform/memmap_no_flash.ld
+++ b/src/rp2350/pico_platform/memmap_no_flash.ld
@@ -8,6 +8,7 @@
* memmap_no_flash.ld rp2350/pico_platform
* └── memmap_no_flash.incl rp2_common/pico_standard_link
* ├── set_memory_locations.incl rp2_common/pico_standard_link
+ * ├── memory_psram.incl rp2_common/pico_standard_link
* ├── memory_xip_ram.incl rp2_common/pico_standard_link
* ├── memory_ram.incl rp2_common/pico_standard_link
* ├── memory_scratch.incl rp2_common/pico_standard_link
@@ -34,6 +35,7 @@
* ├── section_generated_post_scratch.incl rp2_common/pico_standard_link
* ├── section_extra_post_scratch.incl rp2_common/pico_standard_link
* ├── sections_stack.incl rp2_common/pico_standard_link
+ * ├── sections_psram.incl rp2_common/pico_standard_link
* ├── section_end.incl rp2_common/pico_standard_link
* ├── section_generated_post_end.incl rp2_common/pico_standard_link
* ├── section_extra_post_end.incl rp2_common/pico_standard_link
diff --git a/src/rp2350/rp2350a_interface_pins.json b/src/rp2350/rp2350a_interface_pins.json
index 7869d2a..1a75f60 100644
--- a/src/rp2350/rp2350a_interface_pins.json
+++ b/src/rp2350/rp2350a_interface_pins.json
@@ -89,6 +89,13 @@
"B": [15]
}
}
+ },
+ "QMI": {
+ "instances": {
+ "0": {
+ "CS1N": [0, 8, 19]
+ }
+ }
}
},
"pins": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
diff --git a/src/rp2350/rp2350b_interface_pins.json b/src/rp2350/rp2350b_interface_pins.json
index ee90ad2..c8ed6bb 100644
--- a/src/rp2350/rp2350b_interface_pins.json
+++ b/src/rp2350/rp2350b_interface_pins.json
@@ -105,6 +105,13 @@
"B": [39, 47]
}
}
+ },
+ "QMI": {
+ "instances": {
+ "0": {
+ "CS1N": [0, 8, 19, 47]
+ }
+ }
}
},
"pins": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]
diff --git a/src/rp2_common/hardware_flash/flash.c b/src/rp2_common/hardware_flash/flash.c
index fbca8de..294e7be 100644
--- a/src/rp2_common/hardware_flash/flash.c
+++ b/src/rp2_common/hardware_flash/flash.c
@@ -89,6 +89,17 @@
// application.
#if !PICO_RP2040
+static qmi_setup_function_t qmi_cs1_setup_function;
+
+bool flash_set_qmi_cs1_setup_function(qmi_setup_function_t function) {
+ if ((void*)function > (void*)SRAM_BASE) {
+ qmi_cs1_setup_function = function;
+ return true;
+ } else {
+ return false;
+ }
+}
+
// 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 {
@@ -104,7 +115,9 @@
}
static void __no_inline_not_in_flash_func(flash_rp2350_restore_qmi_cs1)(const flash_rp2350_qmi_save_state_t *state) {
- if (flash_devinfo_get_cs_size(1) == FLASH_DEVINFO_SIZE_NONE) {
+ if (qmi_cs1_setup_function != NULL) {
+ qmi_cs1_setup_function();
+ } else if (flash_devinfo_get_cs_size(1) == FLASH_DEVINFO_SIZE_NONE) {
// Case 1: The RP2350 ROM sets QMI to a clean (03h read) configuration
// during flash_exit_xip(), even though when CS1 is not enabled via
// FLASH_DEVINFO it does not issue an XIP exit sequence to CS1. In
@@ -248,11 +261,11 @@
//-----------------------------------------------------------------------------
// Lower-level flash access functions
-#if !PICO_NO_FLASH
// Bitbanging the chip select using IO overrides, in case RAM-resident IRQs
// are still running, and the FIFO bottoms out. (the bootrom does the same)
-static void __no_inline_not_in_flash_func(flash_cs_force)(bool high) {
+static __force_inline void flash_cs_force(bool high, uint8_t cs) {
#if PICO_RP2040
+ (void)cs;
uint32_t field_val = high ?
IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH :
IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW;
@@ -261,15 +274,16 @@
IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS
);
#else
+ invalid_params_if(HARDWARE_FLASH, cs > 1);
if (high) {
- hw_clear_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_ASSERT_CS0N_BITS);
+ hw_clear_bits(&qmi_hw->direct_csr, cs == 0 ? QMI_DIRECT_CSR_ASSERT_CS0N_BITS : QMI_DIRECT_CSR_ASSERT_CS1N_BITS);
} else {
- hw_set_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_ASSERT_CS0N_BITS);
+ hw_set_bits(&qmi_hw->direct_csr, cs == 0 ? QMI_DIRECT_CSR_ASSERT_CS0N_BITS : QMI_DIRECT_CSR_ASSERT_CS1N_BITS);
}
#endif
}
-void __no_inline_not_in_flash_func(flash_do_cmd)(const uint8_t *txbuf, uint8_t *rxbuf, size_t count) {
+void __no_inline_not_in_flash_func(flash_do_cmd_cs)(const uint8_t *txbuf, uint8_t *rxbuf, size_t count, uint cs) {
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);
@@ -282,7 +296,7 @@
connect_internal_flash_func();
flash_exit_xip_func();
- flash_cs_force(0);
+ flash_cs_force(0, cs);
size_t tx_remaining = count;
size_t rx_remaining = count;
#if PICO_RP2040
@@ -320,13 +334,12 @@
}
hw_clear_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_EN_BITS);
#endif
- flash_cs_force(1);
+ flash_cs_force(1, cs);
flash_flush_cache_func();
flash_enable_xip_via_boot2();
flash_restore_hardware_state(&state);
}
-#endif
// Use standard RUID command to get a unique identifier for the flash (and
// hence the board)
diff --git a/src/rp2_common/hardware_flash/include/hardware/flash.h b/src/rp2_common/hardware_flash/include/hardware/flash.h
index c34feb4..0c5b27a 100644
--- a/src/rp2_common/hardware_flash/include/hardware/flash.h
+++ b/src/rp2_common/hardware_flash/include/hardware/flash.h
@@ -120,15 +120,14 @@
*/
void flash_get_unique_id(uint8_t *id_out);
-/*! \brief Execute bidirectional flash command
+/*! \brief Execute bidirectional QSPI command
* \ingroup hardware_flash
*
- * Low-level function to execute a serial command on a flash device attached
+ * Low-level function to execute a serial command on a device attached
* to the QSPI interface. Bytes are simultaneously transmitted and received
* from txbuf and to rxbuf. Therefore, both buffers must be the same length,
* count, which is the length of the overall transaction. This is useful for
- * reading metadata from the flash chip, such as device ID or SFDP
- * parameters.
+ * reading metadata from the chip, such as device ID or SFDP parameters.
*
* The XIP cache is flushed following each command, in case flash state
* has been modified. Like other hardware_flash functions, the flash is not
@@ -138,16 +137,44 @@
* it is recommended that this function only be used to extract flash metadata
* during startup, before the main application begins to run: see the
* implementation of pico_get_unique_id() for an example of this.
+ *
+ * \if rp2040_specific
+ * On RP2040 the chip select index is ignored, as there is only one chip select.
+ * \endif
+ *
+ * \param txbuf Pointer to a byte buffer which will be transmitted
+ * \param rxbuf Pointer to a byte buffer where received data will be written. txbuf and rxbuf may be the same buffer.
+ * \param count Length in bytes of txbuf and of rxbuf
+ * \param cs Chip select index
+ */
+void flash_do_cmd_cs(const uint8_t *txbuf, uint8_t *rxbuf, size_t count, uint cs);
+
+/*! \brief Execute bidirectional flash command on chip select 0
+ * \ingroup hardware_flash
+ *
+ * See \ref flash_do_cmd_cs for more details.
*
* \param txbuf Pointer to a byte buffer which will be transmitted to the flash
* \param rxbuf Pointer to a byte buffer where data received from the flash will be written. txbuf and rxbuf may be the same buffer.
* \param count Length in bytes of txbuf and of rxbuf
*/
-void flash_do_cmd(const uint8_t *txbuf, uint8_t *rxbuf, size_t count);
+static inline void flash_do_cmd(const uint8_t *txbuf, uint8_t *rxbuf, size_t count) {
+ flash_do_cmd_cs(txbuf, rxbuf, count, 0);
+}
void flash_flush_cache(void);
#if !PICO_RP2040
+typedef void (*qmi_setup_function_t)(void);
+
+/*! \brief Set the function to be called to setup the QMI CS1 configuration
+ * \ingroup hardware_flash
+ *
+ * \param function The function to be called to setup the QMI CS1 configuration
+ * \return true if the function was set, false if not (e.g. tried to set a function in flash)
+ */
+bool flash_set_qmi_cs1_setup_function(qmi_setup_function_t function);
+
typedef enum {
FLASH_DEVINFO_SIZE_NONE = 0x0,
FLASH_DEVINFO_SIZE_8K = 0x1,
diff --git a/src/rp2_common/hardware_powman/BUILD.bazel b/src/rp2_common/hardware_powman/BUILD.bazel
index 8c8971e..d08cc8a 100644
--- a/src/rp2_common/hardware_powman/BUILD.bazel
+++ b/src/rp2_common/hardware_powman/BUILD.bazel
@@ -8,8 +8,6 @@
hdrs = ["include/hardware/powman.h"],
implementation_deps = ["//src/rp2_common/hardware_gpio"],
includes = ["include"],
-
- # TODO: RP2350 only, but doesn't appear gated in CMake.
target_compatible_with = ["//bazel/constraint:rp2350"],
deps = [
"//src/rp2_common:hardware_structs",
diff --git a/src/rp2_common/hardware_psram/BUILD.bazel b/src/rp2_common/hardware_psram/BUILD.bazel
new file mode 100644
index 0000000..4b48306
--- /dev/null
+++ b/src/rp2_common/hardware_psram/BUILD.bazel
@@ -0,0 +1,18 @@
+load("@rules_cc//cc:cc_library.bzl", "cc_library")
+
+package(default_visibility = ["//visibility:public"])
+
+cc_library(
+ name = "hardware_psram",
+ srcs = ["psram.c"],
+ hdrs = ["include/hardware/psram.h"],
+ includes = ["include"],
+ target_compatible_with = ["//bazel/constraint:rp2350"],
+ deps = [
+ "//src/rp2_common:hardware_structs",
+ "//src/rp2_common:pico_platform",
+ "//src/rp2_common/hardware_clocks",
+ "//src/rp2_common/hardware_flash",
+ "//src/rp2_common/hardware_gpio",
+ ],
+)
diff --git a/src/rp2_common/hardware_psram/CMakeLists.txt b/src/rp2_common/hardware_psram/CMakeLists.txt
new file mode 100644
index 0000000..861f419
--- /dev/null
+++ b/src/rp2_common/hardware_psram/CMakeLists.txt
@@ -0,0 +1,4 @@
+pico_simple_hardware_target(psram)
+pico_mirrored_target_link_libraries(hardware_psram INTERFACE hardware_flash hardware_clocks)
+
+target_link_libraries(hardware_psram INTERFACE hardware_gpio pico_runtime_init_headers) # not mirrored as only implementation requires it
diff --git a/src/rp2_common/hardware_psram/include/hardware/psram.h b/src/rp2_common/hardware_psram/include/hardware/psram.h
new file mode 100644
index 0000000..38dd2c9
--- /dev/null
+++ b/src/rp2_common/hardware_psram/include/hardware/psram.h
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2026 Raspberry Pi (Trading) Ltd.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef _HARDWARE_PSRAM_H
+#define _HARDWARE_PSRAM_H
+
+#include "pico.h"
+#include "hardware/clocks.h"
+#include "hardware/flash.h"
+
+/** \file psram.h
+ * \defgroup hardware_psram hardware_psram
+ *
+ * \brief Low level PSRAM setup functions
+ *
+ * When using the runtime_init initialization, this can initialize PSRAM in 3 ways,
+ * listed from highest to lowest priority:
+ * 1. If flash_devinfo is setup (e.g. configured in OTP), it will initialize PSRAM with
+ * the flash_devinfo size and GPIO for CS1
+ * 2. If `PICO_AUTO_DETECT_PSRAM` is set it will attempt to detect PSRAM size and CS GPIO
+ * on CS1. This will attempt to use all available QMI CS1n GPIOs as chip selects, so they
+ * will be wiggled. By default, it will skip over some which are defined in the board
+ * header (see `PICO_AUTO_DETECT_PSRAM_CS_SKIP_DEFAULTS`).
+ * - If the CS GPIO is known and set in `PICO_PSRAM_CS_PIN`, you can just enable
+ * `PICO_AUTO_DETECT_PSRAM_SIZE` to only detect the size. Some board headers use
+ * this behavior if they have variants both with and without PSRAM fitted
+ * (e.g. adafruit_feather_rp2350)
+ * 3. If `PICO_PSRAM_SIZE_BYTES` and `PICO_PSRAM_CS_PIN` are set (e.g. configured in the
+ * board header, or with \ref pico_override_psram_size) it will initialize PSRAM with
+ * that size and CS GPIO
+ *
+ * Only the `PICO_AUTO_DETECT_PSRAM` methods (including `PICO_AUTO_DETECT_PSRAM_SIZE`) will
+ * verify that PSRAM is present before using it.
+ *
+ * Variables can be placed in PSRAM using __in_psram or __uninitialized_psram macros, and
+ * you can also read/write the memory addresses directly.
+ *
+ * If there are variables placed in PSRAM, XIP will be set up to cause bus faults on any
+ * access to PSRAM addresses greater than the size available. The \ref psram_check_address
+ * function should be used before accessing variables in PSRAM when auto-detection is on,
+ * to prevent these bus faults.
+ *
+ * Note some of these functions are *unsafe* if you are using both cores, and the other
+ * is executing from flash or psram concurrently with the operation. In this case, you
+ * must perform your own synchronization to make sure that no XIP accesses take
+ * place while running these functions. One option is to use the
+ * \ref flash_safe_execute functions in \ref pico_flash.
+ *
+ * Likewise they are *unsafe* if you have interrupt handlers or an interrupt
+ * vector table in flash or psram, so you must disable interrupts before calling in
+ * this case - \ref flash_safe_execute handles this case too.
+ *
+ * The unsafe functions are:
+ * - \ref psram_reinitialize
+ * - \ref psram_detect_cs_and_size
+ * - \ref psram_detect_size
+ */
+
+// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_HARDWARE_PSRAM, Enable/disable assertions in the hardware_psram module, type=bool, default=0, group=hardware_psram
+#ifndef PARAM_ASSERTIONS_ENABLED_HARDWARE_PSRAM
+#define PARAM_ASSERTIONS_ENABLED_HARDWARE_PSRAM 0
+#endif
+
+// PICO_CONFIG: PICO_PSRAM_SIZE_BYTES, size of psram in bytes, type=int, default=Usually provided via board header if psram is present, group=hardware_psram
+// PICO_CONFIG: PICO_PSRAM_CS_PIN, chip select pin for psram, type=int, default=Usually provided via board header if psram is present, group=hardware_psram
+
+// PICO_CONFIG: PICO_AUTO_DETECT_PSRAM, automatically detect if psram is present, type=bool, default=0, group=hardware_psram
+#ifndef PICO_AUTO_DETECT_PSRAM
+#define PICO_AUTO_DETECT_PSRAM 0
+#endif
+
+// PICO_CONFIG: PICO_AUTO_DETECT_PSRAM_SIZE, automatically detect psram size, type=bool, default=PICO_AUTO_DETECT_PSRAM, group=hardware_psram
+#ifndef PICO_AUTO_DETECT_PSRAM_SIZE
+#define PICO_AUTO_DETECT_PSRAM_SIZE PICO_AUTO_DETECT_PSRAM
+#endif
+
+// PICO_CONFIG: PICO_AUTO_DETECT_PSRAM_CS, automatically detect psram chip select pin, type=bool, default=PICO_AUTO_DETECT_PSRAM, group=hardware_psram
+#ifndef PICO_AUTO_DETECT_PSRAM_CS
+#define PICO_AUTO_DETECT_PSRAM_CS PICO_AUTO_DETECT_PSRAM
+#elif PICO_AUTO_DETECT_PSRAM_CS && !PICO_AUTO_DETECT_PSRAM_SIZE
+#error "PICO_AUTO_DETECT_PSRAM_SIZE must be set to use PICO_AUTO_DETECT_PSRAM_CS"
+#endif
+
+// PICO_CONFIG: PICO_AUTO_DETECT_PSRAM_CS_SKIP_DEFAULTS, skip any _DEFAULT_ GPIOs defined in the board header when auto-detecting psram chip select pin, type=bool, default=PICO_AUTO_DETECT_PSRAM_CS, group=hardware_psram
+// PICO_CONFIG: PICO_AUTO_DETECT_PSRAM_CS_SKIP_PINS, comma separated list of extra pins to not check when auto-detecting psram chip select pin, type=list, group=hardware_psram
+#ifndef PICO_AUTO_DETECT_PSRAM_CS_SKIP_DEFAULTS
+#define PICO_AUTO_DETECT_PSRAM_CS_SKIP_DEFAULTS PICO_AUTO_DETECT_PSRAM_CS
+#endif
+
+// PICO_CONFIG: PICO_DEFAULT_PSRAM_ID, Default ID of psram used for auto-detection, type=int, default=0x5D, group=hardware_psram
+#ifndef PICO_DEFAULT_PSRAM_ID
+#define PICO_DEFAULT_PSRAM_ID 0x5D
+#endif
+
+// PICO_CONFIG: PICO_DEFAULT_PSRAM_MAX_FREQ, Default max frequency of psram, type=int, default=133 * MHZ, group=hardware_psram
+#ifndef PICO_DEFAULT_PSRAM_MAX_FREQ
+#define PICO_DEFAULT_PSRAM_MAX_FREQ (133 * MHZ)
+#endif
+
+// PICO_CONFIG: PICO_DEFAULT_PSRAM_MAX_SELECT, Default max select time of psram in ns, type=int, default=8000, group=hardware_psram
+#ifndef PICO_DEFAULT_PSRAM_MAX_SELECT
+#define PICO_DEFAULT_PSRAM_MAX_SELECT 8000
+#endif
+
+// PICO_CONFIG: PICO_DEFAULT_PSRAM_MIN_DESELECT, Default min deselect time of psram in ns, type=int, default=18, group=hardware_psram
+#ifndef PICO_DEFAULT_PSRAM_MIN_DESELECT
+#define PICO_DEFAULT_PSRAM_MIN_DESELECT 18
+#endif
+
+
+// PICO_CONFIG: PICO_RUNTIME_SKIP_INIT_PSRAM, Skip calling of `runtime_init_setup_psram` function during runtime init, type=bool, default=0, group=pico_runtime_init
+// PICO_CONFIG: PICO_RUNTIME_NO_INIT_PSRAM, Do not include SDK implementation of `runtime_init_setup_psram` function, type=bool, default=0, group=pico_runtime_init
+
+#ifndef PICO_RUNTIME_INIT_PSRAM
+#define PICO_RUNTIME_INIT_PSRAM "11080"
+#endif
+
+#ifndef PICO_RUNTIME_SKIP_INIT_PSRAM
+#define PICO_RUNTIME_SKIP_INIT_PSRAM 0
+#endif
+
+#ifndef PICO_RUNTIME_NO_INIT_PSRAM
+#define PICO_RUNTIME_NO_INIT_PSRAM 0
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*! \brief Check if PSRAM is available and initialized
+ * \ingroup hardware_psram
+ *
+ * \return true if PSRAM is available and initialized, false otherwise
+ */
+bool psram_is_available(void);
+
+/*! \brief Get the size of the PSRAM
+ * \ingroup hardware_psram
+ *
+ * Retrieve the size of the PSRAM, either from PICO_PSRAM_SIZE_BYTES,
+ * flash_devinfo, or auto-detection.
+ *
+ * \return size of PSRAM in bytes, or 0 if none
+ */
+size_t psram_get_size(void);
+
+/*! \brief Check if an address is in available PSRAM
+ * \ingroup hardware_psram
+ *
+ * \return true if the address is in available PSRAM, false otherwise
+ */
+bool psram_check_address(void* addr);
+
+/*! \brief Detect PSRAM size
+ * \ingroup hardware_psram
+ *
+ * This will read the ID of the PSRAM chip and return the size based on the ID.
+ *
+ * You must configure the GPIO function for the CS pin before calling this function,
+ * and should also configure the CS GPIO in flash_devinfo to prevent toggling of the
+ * previously configured GPIO (usually 0, so prints invalid characters to default UART).
+ *
+ * \return size of PSRAM, or 0 if none found
+ */
+size_t psram_detect_size(void);
+
+/*! \brief Detect PSRAM chip select pin and size
+ * \ingroup hardware_psram
+ *
+ * This runs \ref psram_detect_size() for each CS GPIO in the array in turn,
+ * and returns the size as soon as a PSRAM chip is detected.
+ *
+ * This will setup the CS GPIO using flash_devinfo if PSRAM is found.
+ *
+ * \param cs_gpios Array of CS GPIOs to try
+ * \param num Number of CS GPIOs in the array
+ * \return size of PSRAM, or 0 if none found
+ */
+size_t psram_detect_cs_and_size(uint8_t *cs_gpios, size_t num);
+
+/*! \brief Configure PSRAM timing parameters
+ * \ingroup hardware_psram
+ *
+ * This will calculate and set the PSRAM timing parameters based on the given values.
+ *
+ * Note: This will also implement the workaround for RP2350-E14 if PICO_RP2350_A2_SUPPORTED is set.
+ *
+ * \param max_psram_freq Maximum frequency of PSRAM
+ * \param max_select_ns Maximum select time in ns
+ * \param min_deselect_ns Minimum deselect time in ns
+ * \return PICO_OK on success, PICO_ERROR_INVALID_ARG if unable to calculate valid parameters
+ */
+int psram_configure_params(uint32_t max_psram_freq, uint32_t max_select_ns, uint32_t min_deselect_ns);
+
+/*! \brief Explicitly set PSRAM timing parameters
+ * \ingroup hardware_psram
+ *
+ * This will explicitly set the PSRAM timing parameters to the given values.
+ *
+ * This may be necessary if the parameters calculated by \ref psram_configure_params are not suitable.
+ *
+ * \param divisor Divisor for PSRAM clock
+ * \param rxdelay RX delay for PSRAM clock
+ * \param max_select Maximum select time in multiples of 64 system clocks
+ * \param min_deselect Minimum deselect time in system clock cycles - ceil(divisor / 2)
+ * \return PICO_OK on success, PICO_ERROR_INVALID_ARG if any of the parameters are invalid
+ */
+int psram_set_params(uint32_t divisor, uint32_t rxdelay, uint32_t max_select, uint32_t min_deselect);
+
+/*! \brief Re-initialize PSRAM
+ * \ingroup hardware_psram
+ *
+ * This will re-initialize the PSRAM with the parameters set by \ref psram_configure_params.
+ *
+ * This calls \ref flash_start_xip internally, so will reset any QSPI pads changes you have made.
+ *
+ * \return PICO_OK on success, PICO_ERROR_PRECONDITION_NOT_MET if the PSRAM size is not set in
+ * flash_devinfo or the PSRAM parameters are not set by \ref psram_configure_params or \ref psram_set_params
+ */
+int psram_reinitialize(void);
+
+/*! \brief Convert PSRAM EID to size
+ * \ingroup hardware_psram
+ *
+ * This will convert the PSRAM EID to the size in bytes.
+ *
+ * This is not intended to be called by the user, but is provided as a weak function so it can
+ * be overridden if other PSRAM chips are used that have different EID to size mapping.
+ *
+ * This is used by \ref psram_detect_size to check the KGD and convert the EID to the size.
+ *
+ * \param kgd Known Good Die
+ * \param eid EID
+ * \return size of PSRAM in bytes, or 0 if the KGD/EID is not recognised
+ */
+size_t psram_eid_to_size(uint8_t kgd, uint8_t eid);
+
+/*! \brief Provide a static PSRAM allocation, or malloc if PSRAM is not available
+ * \ingroup hardware_psram
+ *
+ * This will allocate a static buffer in PSRAM and if available use that, otherwise it will use the heap.
+ *
+ * This will fail to compile if PICO_PSRAM_SIZE_BYTES is not set
+ */
+#define psram_or_malloc(group, type, var, size) \
+ static type __uninitialized_psram(group) var##_psram[size]; static type* var; \
+ if (!var) { \
+ if (psram_check_address(var##_psram + (size))) { \
+ var = (type*)var##_psram; \
+ } else { \
+ var = (type*)malloc((size) * sizeof(type)); \
+ } \
+ }
+
+/*! \brief Free a buffer if it is not in PSRAM
+ * \ingroup hardware_psram
+ *
+ * This will free the buffer from \ref psram_or_malloc if it was created by malloc
+ */
+#define psram_or_free(var) if (!psram_check_address(var##_psram)) { free(var); }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _HARDWARE_PSRAM_H
diff --git a/src/rp2_common/hardware_psram/psram.c b/src/rp2_common/hardware_psram/psram.c
new file mode 100644
index 0000000..0010a67
--- /dev/null
+++ b/src/rp2_common/hardware_psram/psram.c
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 2026 Raspberry Pi (Trading) Ltd.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include "hardware/psram.h"
+#include <string.h>
+#include "hardware/address_mapped.h"
+#include "hardware/clocks.h"
+#include "hardware/gpio.h"
+#include "hardware/flash.h"
+#include "hardware/structs/qmi.h"
+#include "hardware/structs/xip_ctrl.h"
+#include "pico/runtime_init.h"
+
+// PSRAM SPI command codes
+#define PSRAM_READ_ID_CMD 0x9F
+#define PSRAM_QUAD_ENABLE_CMD 0x35
+#define PSRAM_QUAD_READ_CMD 0xEB
+#define PSRAM_QUAD_WRITE_CMD 0x38
+#define PSRAM_NOOP_CMD 0xFF
+
+
+size_t __weak psram_eid_to_size(uint8_t kgd, uint8_t eid) {
+ // Weak function to allow overriding if other PSRAM chips
+ // have different EID to size mapping
+ // Currently supports APS6404 and ISSI PSRAM
+ size_t psram_size = 0;
+
+ if (kgd == PICO_DEFAULT_PSRAM_ID) { // Known Good Die - expects 0x5D
+ psram_size = 1024 * 1024; // 1 MiB
+ uint8_t size_id = eid >> 5;
+ if (size_id == 4) { // == 4 is for ISSI PSRAM
+ psram_size *= 16; // 16 MiB
+ } else if (eid == 0x26 || size_id == 2 || size_id == 3) { // == 3 is for ISSI PSRAM
+ psram_size *= 8; // 8 MiB
+ } else if (size_id == 1) {
+ psram_size *= 4; // 4 MiB
+ } else if (size_id == 0) {
+ psram_size *= 2; // 2 MiB
+ }
+ }
+
+ return psram_size;
+}
+
+
+size_t psram_detect_size(void) {
+ // Save size to restore later
+ flash_devinfo_size_t prev_size = flash_devinfo_get_cs_size(1);
+ // Setup with non-zero size, so the bootrom will issue the XIP exit sequence to CS1
+ // The GPIO doesn't matter, as the caller must set the correct function for the CS pin
+ flash_devinfo_set_cs_size(1, FLASH_DEVINFO_SIZE_8K);
+
+ // Read ID command, followed by 7 NOOP commands to get the response
+ uint8_t txbuffer[8] = { PSRAM_READ_ID_CMD, PSRAM_NOOP_CMD, PSRAM_NOOP_CMD, PSRAM_NOOP_CMD, PSRAM_NOOP_CMD, PSRAM_NOOP_CMD, PSRAM_NOOP_CMD, PSRAM_NOOP_CMD };
+ uint8_t rxbuffer[8] = { 0 };
+ flash_do_cmd_cs(txbuffer, rxbuffer, sizeof(txbuffer), 1);
+
+ // Restore previous size
+ flash_devinfo_set_cs_size(1, prev_size);
+
+ uint8_t kgd = rxbuffer[5];
+ uint8_t eid = rxbuffer[6];
+
+ return psram_eid_to_size(kgd, eid);
+}
+
+size_t psram_detect_cs_and_size(uint8_t *cs_gpios, size_t num) {
+ gpio_function_t prev_funcs[num];
+ size_t psram_size = 0;
+ for (size_t i=0; i < num; i++) {
+ // Save and clear all CS GPIO functions
+ uint8_t gpio = cs_gpios[i];
+ prev_funcs[i] = gpio_get_function(gpio);
+ gpio_set_function(gpio, GPIO_FUNC_NULL);
+ }
+ for (size_t i=0; i < num; i++) {
+ uint8_t gpio = cs_gpios[i];
+ flash_devinfo_set_cs_gpio(1, gpio);
+ gpio_set_function(gpio, GPIO_FUNC_XIP_CS1);
+ psram_size = psram_detect_size();
+ if (psram_size > 0) {
+ // CS GPIO found, so will be left configured in flash_devinfo
+ break;
+ }
+ gpio_set_function(gpio, GPIO_FUNC_NULL);
+ }
+ for (size_t i=0; i < num; i++) {
+ // Restore previous function to all CS GPIOs
+ uint8_t gpio = cs_gpios[i];
+ gpio_set_function(gpio, prev_funcs[i]);
+ }
+
+ return psram_size;
+}
+
+#if PICO_AUTO_DETECT_PSRAM_CS_SKIP_DEFAULTS
+// Preprocessor hackery to make this simpler and have fewer checks
+#if PICO_RP2350
+#if PICO_RP2350A
+#define is_cs_gpio(gpio) (gpio == 0) || (gpio == 8) || (gpio == 19)
+#else
+#define is_cs_gpio(gpio) (gpio == 0) || (gpio == 8) || (gpio == 19) || (gpio == 47)
+#endif
+#else
+// Default to assume all GPIOs are CS GPIOs
+#define is_cs_gpio(gpio) true
+#endif
+
+static const uint8_t default_pin_cs_gpios[] = {
+#if defined(PICO_DEFAULT_UART_TX_PIN) && is_cs_gpio(PICO_DEFAULT_UART_TX_PIN)
+ PICO_DEFAULT_UART_TX_PIN,
+#endif
+#if defined(PICO_DEFAULT_UART_RX_PIN) && is_cs_gpio(PICO_DEFAULT_UART_RX_PIN)
+ PICO_DEFAULT_UART_RX_PIN,
+#endif
+#if defined(PICO_DEFAULT_I2C_SDA_PIN) && is_cs_gpio(PICO_DEFAULT_I2C_SDA_PIN)
+ PICO_DEFAULT_I2C_SDA_PIN,
+#endif
+#if defined(PICO_DEFAULT_I2C_SCL_PIN) && is_cs_gpio(PICO_DEFAULT_I2C_SCL_PIN)
+ PICO_DEFAULT_I2C_SCL_PIN,
+#endif
+#if defined(PICO_DEFAULT_SPI_SCK_PIN) && is_cs_gpio(PICO_DEFAULT_SPI_SCK_PIN)
+ PICO_DEFAULT_SPI_SCK_PIN,
+#endif
+#if defined(PICO_DEFAULT_SPI_TX_PIN) && is_cs_gpio(PICO_DEFAULT_SPI_TX_PIN)
+ PICO_DEFAULT_SPI_TX_PIN,
+#endif
+#if defined(PICO_DEFAULT_SPI_RX_PIN) && is_cs_gpio(PICO_DEFAULT_SPI_RX_PIN)
+ PICO_DEFAULT_SPI_RX_PIN,
+#endif
+#if defined(PICO_DEFAULT_SPI_CSN_PIN) && is_cs_gpio(PICO_DEFAULT_SPI_CSN_PIN)
+ PICO_DEFAULT_SPI_CSN_PIN,
+#endif
+#if defined(PICO_DEFAULT_LED_PIN) && is_cs_gpio(PICO_DEFAULT_LED_PIN)
+ PICO_DEFAULT_LED_PIN,
+#endif
+#if defined(PICO_DEFAULT_WS2812_PIN) && is_cs_gpio(PICO_DEFAULT_WS2812_PIN)
+ PICO_DEFAULT_WS2812_PIN,
+#endif
+#if defined(PICO_DEFAULT_WS2812_POWER_PIN) && is_cs_gpio(PICO_DEFAULT_WS2812_POWER_PIN)
+ PICO_DEFAULT_WS2812_POWER_PIN,
+#endif
+#if defined(CYW43_DEFAULT_PIN_WL_REG_ON) && is_cs_gpio(CYW43_DEFAULT_PIN_WL_REG_ON)
+ CYW43_DEFAULT_PIN_WL_REG_ON,
+#endif
+#if defined(CYW43_DEFAULT_PIN_WL_DATA_OUT) && is_cs_gpio(CYW43_DEFAULT_PIN_WL_DATA_OUT)
+ CYW43_DEFAULT_PIN_WL_DATA_OUT,
+#endif
+#if defined(CYW43_DEFAULT_PIN_WL_DATA_IN) && is_cs_gpio(CYW43_DEFAULT_PIN_WL_DATA_IN)
+ CYW43_DEFAULT_PIN_WL_DATA_IN,
+#endif
+#if defined(CYW43_DEFAULT_PIN_WL_HOST_WAKE) && is_cs_gpio(CYW43_DEFAULT_PIN_WL_HOST_WAKE)
+ CYW43_DEFAULT_PIN_WL_HOST_WAKE,
+#endif
+#if defined(CYW43_DEFAULT_PIN_WL_CLOCK) && is_cs_gpio(CYW43_DEFAULT_PIN_WL_CLOCK)
+ CYW43_DEFAULT_PIN_WL_CLOCK,
+#endif
+#if defined(CYW43_DEFAULT_PIN_WL_CS) && is_cs_gpio(CYW43_DEFAULT_PIN_WL_CS)
+ CYW43_DEFAULT_PIN_WL_CS,
+#endif
+#ifdef PICO_AUTO_DETECT_PSRAM_CS_SKIP_PINS
+ PICO_AUTO_DETECT_PSRAM_CS_SKIP_PINS
+#endif
+};
+
+static size_t remove_defaults_from_cs_gpios(uint8_t *cs_gpios, size_t num) {
+ // To prevent trying to use a pin that is already defined for something
+ // else by the board header
+ size_t new_num = num;
+ for (size_t i=0; i < new_num; i++) {
+ for (size_t j=0; j < sizeof(default_pin_cs_gpios); j++) {
+ if (cs_gpios[i] == default_pin_cs_gpios[j]) {
+ // Replace with last valid GPIO in the array
+ cs_gpios[i] = cs_gpios[new_num-1];
+ new_num--;
+ // Check the same index again, as it has a new value
+ i--;
+ // Exit the inner loop
+ break;
+ }
+ }
+ }
+ return new_num;
+}
+#endif
+
+static uint32_t psram_divisor = 0;
+static uint32_t psram_rxdelay = 0;
+static uint32_t psram_max_select = 0;
+static uint32_t psram_min_deselect = 0;
+
+int psram_configure_params(uint32_t max_psram_freq, uint32_t max_select_ns, uint32_t min_deselect_ns) {
+ // 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).
+ uint32_t clock_hz = clock_get_hz(clk_sys);
+ uint32_t divisor = (clock_hz + max_psram_freq - 1) / max_psram_freq;
+ if (divisor == 1 && clock_hz > 100000000) {
+ divisor = 2;
+ }
+ uint32_t rxdelay = divisor;
+ if (clock_hz / divisor > 100000000) {
+ rxdelay += 1;
+ }
+
+ // - Max select is given in multiples of 64 system clocks.
+ // - Min deselect is given in system clock cycles - ceil(divisor / 2).
+ // Requires 64-bit maths as we're working with femtoseconds
+ uint32_t clock_period_fs = 1000000000000000ull / clock_hz; // 1s = 1000000000000000fs
+ uint32_t max_select = ((uint64_t)max_select_ns * 1000000ull) / (64ull * (uint64_t)clock_period_fs); // 1ns = 1000000fs
+ uint32_t min_deselect = (min_deselect_ns * 1000000 + (clock_period_fs - 1)) / clock_period_fs - (divisor + 1) / 2;
+
+ return psram_set_params(divisor, rxdelay, max_select, min_deselect);
+}
+
+int psram_set_params(uint32_t divisor, uint32_t rxdelay, uint32_t max_select, uint32_t min_deselect)
+{
+ invalid_params_if_and_return(HARDWARE_PSRAM, divisor & ~(QMI_M1_TIMING_CLKDIV_BITS >> QMI_M1_TIMING_CLKDIV_LSB), PICO_ERROR_INVALID_ARG);
+ invalid_params_if_and_return(HARDWARE_PSRAM, rxdelay & ~(QMI_M1_TIMING_RXDELAY_BITS >> QMI_M1_TIMING_RXDELAY_LSB), PICO_ERROR_INVALID_ARG);
+ invalid_params_if_and_return(HARDWARE_PSRAM, max_select & ~(QMI_M1_TIMING_MAX_SELECT_BITS >> QMI_M1_TIMING_MAX_SELECT_LSB), PICO_ERROR_INVALID_ARG);
+ invalid_params_if_and_return(HARDWARE_PSRAM, min_deselect & ~(QMI_M1_TIMING_MIN_DESELECT_BITS >> QMI_M1_TIMING_MIN_DESELECT_LSB), PICO_ERROR_INVALID_ARG);
+ // Provide method for explicitly setting the PSRAM parameters
+ psram_divisor = divisor;
+ psram_rxdelay = rxdelay;
+ psram_max_select = max_select;
+ psram_min_deselect = min_deselect;
+
+ return PICO_OK;
+}
+
+static void __no_inline_not_in_flash_func(psram_initialize_internal)(void) {
+ // Send QUAD_ENABLE command manually, as using flash_do_cmd_cs would call this function again,
+ // if it has been registered using flash_set_qmi_cs1_setup_function
+ // Enable direct mode, auto CS1, 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) {
+ tight_loop_contents();
+ }
+ // Send QUAD_ENABLE command
+ qmi_hw->direct_tx = PSRAM_QUAD_ENABLE_CMD;
+ while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) {
+ tight_loop_contents();
+ }
+ // Disable direct mode
+ qmi_hw->direct_csr = 0;
+
+ qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB |
+ QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | // Crossing page boundary would need lower clock speed
+ psram_max_select << QMI_M1_TIMING_MAX_SELECT_LSB |
+ psram_min_deselect << QMI_M1_TIMING_MIN_DESELECT_LSB |
+ psram_rxdelay << QMI_M1_TIMING_RXDELAY_LSB |
+ psram_divisor << QMI_M1_TIMING_CLKDIV_LSB;
+
+ // Read is all quad, with prefix of PSRAM_CMD_QUAD_READ, 6 wait cycles (so 6*4=24 dummy bits), no suffix
+ qmi_hw->m[1].rfmt = (QMI_M1_RFMT_PREFIX_WIDTH_VALUE_Q << QMI_M1_RFMT_PREFIX_WIDTH_LSB |
+ QMI_M1_RFMT_ADDR_WIDTH_VALUE_Q << QMI_M1_RFMT_ADDR_WIDTH_LSB |
+ QMI_M1_RFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M1_RFMT_SUFFIX_WIDTH_LSB |
+ QMI_M1_RFMT_DUMMY_WIDTH_VALUE_Q << QMI_M1_RFMT_DUMMY_WIDTH_LSB |
+ QMI_M1_RFMT_DATA_WIDTH_VALUE_Q << QMI_M1_RFMT_DATA_WIDTH_LSB |
+ QMI_M1_RFMT_PREFIX_LEN_VALUE_8 << QMI_M1_RFMT_PREFIX_LEN_LSB |
+ QMI_M1_RFMT_DUMMY_LEN_VALUE_24 << QMI_M1_RFMT_DUMMY_LEN_LSB |
+ QMI_M1_RFMT_SUFFIX_LEN_VALUE_NONE << QMI_M1_RFMT_SUFFIX_LEN_LSB);
+ qmi_hw->m[1].rcmd = PSRAM_QUAD_READ_CMD << QMI_M1_RCMD_PREFIX_LSB;
+
+ // Write is all quad, with prefix of PSRAM_CMD_QUAD_WRITE, no dummy, no suffix
+ qmi_hw->m[1].wfmt = (QMI_M1_WFMT_PREFIX_WIDTH_VALUE_Q << QMI_M1_WFMT_PREFIX_WIDTH_LSB |
+ QMI_M1_WFMT_ADDR_WIDTH_VALUE_Q << QMI_M1_WFMT_ADDR_WIDTH_LSB |
+ QMI_M1_WFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M1_WFMT_SUFFIX_WIDTH_LSB |
+ QMI_M1_WFMT_DUMMY_WIDTH_VALUE_Q << QMI_M1_WFMT_DUMMY_WIDTH_LSB |
+ QMI_M1_WFMT_DATA_WIDTH_VALUE_Q << QMI_M1_WFMT_DATA_WIDTH_LSB |
+ QMI_M1_WFMT_PREFIX_LEN_VALUE_8 << QMI_M1_WFMT_PREFIX_LEN_LSB |
+ QMI_M1_WFMT_DUMMY_LEN_VALUE_NONE << QMI_M1_WFMT_DUMMY_LEN_LSB |
+ QMI_M1_WFMT_SUFFIX_LEN_VALUE_NONE << QMI_M1_WFMT_SUFFIX_LEN_LSB);
+ qmi_hw->m[1].wcmd = PSRAM_QUAD_WRITE_CMD << QMI_M1_WCMD_PREFIX_LSB;
+
+ // Enable writes to PSRAM
+ hw_set_bits(&xip_ctrl_hw->ctrl, XIP_CTRL_WRITABLE_M1_BITS);
+}
+
+static bool psram_initialized = false;
+
+int psram_reinitialize(void) {
+ // flash_devinfo must be configured correctly to use this function
+ invalid_params_if_and_return(HARDWARE_PSRAM, flash_devinfo_get_cs_size(1) == FLASH_DEVINFO_SIZE_NONE, PICO_ERROR_PRECONDITION_NOT_MET);
+ // psram_configure_params must have been called to set the parameters
+ invalid_params_if_and_return(HARDWARE_PSRAM, psram_divisor == 0, PICO_ERROR_PRECONDITION_NOT_MET);
+
+#if PICO_RP2350_A2_SUPPORTED
+ // Workaround for RP2350-E14, where the bootrom only does this for GPIO 0 instead of the correct CS pin
+ hw_clear_bits(&pads_bank0_hw->io[flash_devinfo_get_cs_gpio(1)], PADS_BANK0_GPIO0_ISO_BITS);
+#endif
+
+ // Register the function to initialize the QMI CS1 configuration
+ flash_set_qmi_cs1_setup_function(psram_initialize_internal);
+
+ // Call flash_start_xip, which calls psram_initialize_internal
+ flash_start_xip();
+
+ psram_initialized = true;
+
+ return PICO_OK;
+}
+
+bool psram_is_available(void) {
+ return psram_initialized;
+}
+
+size_t psram_get_size(void) {
+ if (!psram_initialized) {
+ return 0;
+ }
+ return flash_devinfo_size_to_bytes(flash_devinfo_get_cs_size(1));
+}
+
+bool psram_check_address(void* addr) {
+ uint32_t offset = (uint32_t)addr - (XIP_BASE + flash_devinfo_size_to_bytes(FLASH_DEVINFO_SIZE_MAX));
+ if (offset < psram_get_size()) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+#if !PICO_RUNTIME_NO_INIT_PSRAM
+#if PICO_AUTO_DETECT_PSRAM_CS
+#if PICO_RP2350
+#if PICO_RP2350A
+#define PICO_AVAILABLE_CS1_GPIOS {0, 8, 19}
+#else
+#define PICO_AVAILABLE_CS1_GPIOS {0, 8, 19, 47}
+#endif
+#else
+#error "PICO_AVAILABLE_CS1_GPIOS must be defined for this platform to use PICO_AUTO_DETECT_PSRAM_CS"
+#endif
+#endif
+
+void runtime_init_setup_psram(void) {
+ // Check if flash_devinfo is already configured by OTP (or an earlier runtime_init function, e.g. in kitchen_sink_psram_tiny)
+ bool __unused flash_devinfo_not_configured = flash_devinfo_get_cs_size(1) == FLASH_DEVINFO_SIZE_NONE;
+
+ // Setup flash_devinfo from compile definitions
+ #if defined(PICO_PSRAM_CS_PIN) && !PICO_AUTO_DETECT_PSRAM_CS
+ if (flash_devinfo_not_configured) {
+ flash_devinfo_set_cs_gpio(1, PICO_PSRAM_CS_PIN);
+ }
+ #endif
+ #if defined(PICO_PSRAM_SIZE_BYTES) && !PICO_AUTO_DETECT_PSRAM_SIZE
+ if (flash_devinfo_not_configured) {
+ flash_devinfo_set_cs_size(1, flash_devinfo_bytes_to_size(PICO_PSRAM_SIZE_BYTES));
+ }
+ #endif
+
+ // Setup flash_devinfo with auto-detected size
+ #if PICO_AUTO_DETECT_PSRAM_SIZE
+ #if !defined(PICO_PSRAM_CS_PIN) && !PICO_AUTO_DETECT_PSRAM_CS
+ #error PICO_AUTO_DETECT_PSRAM_SIZE requires a specified PICO_PSRAM_CS_PIN or PICO_AUTO_DETECT_PSRAM_CS
+ #elif defined(PICO_PSRAM_CS_PIN)
+ gpio_set_function(PICO_PSRAM_CS_PIN, GPIO_FUNC_XIP_CS1);
+ #endif
+ if (flash_devinfo_not_configured) {
+ // Attempt to auto-detect the PSRAM size
+ #if PICO_AUTO_DETECT_PSRAM_CS
+ uint8_t cs_gpios[] = PICO_AVAILABLE_CS1_GPIOS;
+ size_t num_cs_gpios = count_of(cs_gpios);
+ #if PICO_AUTO_DETECT_PSRAM_CS_SKIP_DEFAULTS
+ num_cs_gpios = remove_defaults_from_cs_gpios(cs_gpios, num_cs_gpios);
+ #endif
+ size_t psram_size = psram_detect_cs_and_size(cs_gpios, num_cs_gpios);
+ #else
+ size_t psram_size = psram_detect_size();
+ #endif
+ // Set flash_devinfo size
+ if (psram_size > 0) flash_devinfo_set_cs_size(1, flash_devinfo_bytes_to_size(psram_size));
+ }
+ #endif
+
+ // The size has now been configured, so can cache the value
+ flash_devinfo_size_t psram_flash_devinfo_size = flash_devinfo_get_cs_size(1);
+ size_t psram_word_size = flash_devinfo_size_to_bytes(psram_flash_devinfo_size) >> 2; // >>2 is /4, for words
+ psram_initialized = psram_flash_devinfo_size != FLASH_DEVINFO_SIZE_NONE;
+
+ static_assert(FLASH_DEVINFO_SIZE_MAX == FLASH_DEVINFO_SIZE_16M, "expected max region size of 16M");
+ extern uint32_t __psram_start__;
+ extern uint32_t __psram_end__;
+ uint32_t psram_words = (uint32_t)(&__psram_end__ - &__psram_start__);
+ if (psram_words > psram_word_size) {
+ // Setup to bus fault for variables that don't fit in available PSRAM
+ int clear_regions = 0; // Clear no regions by default
+ if (psram_flash_devinfo_size == FLASH_DEVINFO_SIZE_NONE) {
+ clear_regions = 4; // Clear all PSRAM regions
+ } else if (psram_flash_devinfo_size < FLASH_DEVINFO_SIZE_4M) {
+ clear_regions = 3; // Clear last 3x 4M regions
+ // And reduce size of first PSRAM region
+ qmi_hw->atrans[4] = (1u << psram_flash_devinfo_size) << QMI_ATRANS4_SIZE_LSB; // units are 4 kiB
+ } else if (psram_flash_devinfo_size == FLASH_DEVINFO_SIZE_4M) {
+ clear_regions = 3; // Clear last 3x 4M regions
+ } else if (psram_flash_devinfo_size == FLASH_DEVINFO_SIZE_8M) {
+ clear_regions = 2; // Clear last 2x 4M regions
+ }
+
+ for (int i = 8 - clear_regions; i < 8; i++) {
+ qmi_hw->atrans[i] = 0;
+ }
+ }
+
+ if (!psram_initialized) {
+ return;
+ }
+
+ // Configure the PSRAM parameters with the default values
+ int ret = psram_configure_params(PICO_DEFAULT_PSRAM_MAX_FREQ, PICO_DEFAULT_PSRAM_MAX_SELECT, PICO_DEFAULT_PSRAM_MIN_DESELECT);
+ if (ret != PICO_OK) {
+ psram_initialized = false;
+ return;
+ }
+
+ // Initialize the PSRAM
+ ret = psram_reinitialize();
+ if (ret != PICO_OK) {
+ psram_initialized = false;
+ return;
+ }
+
+ // And load any initialized PSRAM data
+ extern uint32_t __psram_load_source__;
+ extern uint32_t __psram_load_start__;
+ extern uint32_t __psram_load_end__;
+ uint32_t stored_words = (uint32_t)(&__psram_load_end__ - &__psram_load_start__);
+ if (stored_words > 0) {
+ if (stored_words > psram_word_size) {
+ // Only copy into available PSRAM, to avoid triggering bus faults here,
+ // they will be triggered later when the variable is accessed
+ stored_words = psram_word_size;
+ }
+ memcpy(&__psram_load_start__, &__psram_load_source__, stored_words * sizeof(uint32_t));
+ }
+}
+
+#if defined(PICO_RUNTIME_INIT_PSRAM) && !PICO_RUNTIME_SKIP_INIT_PSRAM
+PICO_RUNTIME_INIT_FUNC_RUNTIME(runtime_init_setup_psram, PICO_RUNTIME_INIT_PSRAM);
+#endif
+#endif
diff --git a/src/rp2_common/hardware_sha256/BUILD.bazel b/src/rp2_common/hardware_sha256/BUILD.bazel
index 129b827..f56a708 100644
--- a/src/rp2_common/hardware_sha256/BUILD.bazel
+++ b/src/rp2_common/hardware_sha256/BUILD.bazel
@@ -7,7 +7,6 @@
srcs = ["sha256.c"],
hdrs = ["include/hardware/sha256.h"],
includes = ["include"],
- # TODO: RP2350 only, but doesn't appear gated in CMake.
target_compatible_with = ["//bazel/constraint:rp2350"],
deps = [
"//src/rp2_common:hardware_structs",
diff --git a/src/rp2_common/pico_platform_sections/include/pico/platform/sections.h b/src/rp2_common/pico_platform_sections/include/pico/platform/sections.h
index 591e9e3..fdafb66 100644
--- a/src/rp2_common/pico_platform_sections/include/pico/platform/sections.h
+++ b/src/rp2_common/pico_platform_sections/include/pico/platform/sections.h
@@ -79,6 +79,32 @@
#define __scratch_y(group) __attribute__((section(".scratch_y." group)))
#endif
+/*! \brief Section attribute macro for placement in PSRAM
+ * \ingroup pico_platform
+ *
+ * PSRAM is commonly used for extra data sections. You can place data in initialised or
+ * uninitialised PSRAM, depending on how the data is loaded into the PSRAM.
+ *
+ * For example a `uint32_t` variable placed in PSRAM
+ *
+ * uint32_t __in_psram("my_group_name") foo = 23;
+ *
+ * Or placed in uninitialised PSRAM
+ *
+ * uint32_t __uninitialized_psram("my_group_name") foo;
+ *
+ * The section attribute is `.psram_initialised.<group>` or `.psram_uninitialised.<group>`
+ *
+ * \param group a string suffix to use in the section name to distinguish groups that can be linker
+ * garbage-collected independently
+ */
+#ifndef __in_psram
+#define __in_psram(group) __attribute__((section(".psram_initialised." group)))
+#endif
+#ifndef __uninitialized_psram
+#define __uninitialized_psram(group) __attribute__((section(".psram_uninitialised." group)))
+#endif
+
/*! \brief Section attribute macro for data that is to be left uninitialized
* \ingroup pico_platform
*
diff --git a/src/rp2_common/pico_standard_link/BUILD.bazel b/src/rp2_common/pico_standard_link/BUILD.bazel
index 4ebfdd4..ae0c26d 100644
--- a/src/rp2_common/pico_standard_link/BUILD.bazel
+++ b/src/rp2_common/pico_standard_link/BUILD.bazel
@@ -1,6 +1,7 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("//bazel:defs.bzl", "compatible_with_rp2")
load("pico_flash_region.bzl", "generated_pico_flash_region")
+load("pico_psram_region.bzl", "generated_pico_psram_region")
package(default_visibility = ["//visibility:public"])
@@ -18,6 +19,11 @@
}),
)
+generated_pico_psram_region(
+ name = "default_psram_region",
+ psram_region_size = 0,
+)
+
# It's possible to set linker scripts globally or on a per-binary basis.
#
# Setting globally to a custom linker script:
diff --git a/src/rp2_common/pico_standard_link/CMakeLists.txt b/src/rp2_common/pico_standard_link/CMakeLists.txt
index f4cf41b..5c53505 100644
--- a/src/rp2_common/pico_standard_link/CMakeLists.txt
+++ b/src/rp2_common/pico_standard_link/CMakeLists.txt
@@ -101,6 +101,30 @@
set_property(TARGET ${TARGET} APPEND PROPERTY PICO_TARGET_LINKER_SCRIPT_OVERRIDE_PATHS "${PATH}")
endfunction()
+ # pico_override_flash_size(TARGET FLASH_SIZE)
+ # \brief\ Override the FLASH size for a given target
+ #
+ # \param\ TARGET The target to override the FLASH size for
+ # \param\ FLASH_SIZE The FLASH size to set for the target
+ function(pico_override_flash_size TARGET FLASH_SIZE)
+ set(PICO_FLASH_SIZE_BYTES_STRING "${FLASH_SIZE}")
+ configure_file(${PICO_SDK_PATH}/src/rp2_common/pico_standard_link/pico_flash_region.template.ld ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}/pico_flash_region.ld)
+ pico_add_linker_script_override_path(${TARGET} ${CMAKE_CURRENT_BINARY_DIR}/${TARGET})
+ target_compile_definitions(${TARGET} PRIVATE "PICO_FLASH_SIZE_BYTES=${FLASH_SIZE}")
+ endfunction()
+
+ # pico_override_psram_size(TARGET PSRAM_SIZE)
+ # \brief\ Override the PSRAM size for a given target
+ #
+ # \param\ TARGET The target to override the PSRAM size for
+ # \param\ PSRAM_SIZE The PSRAM size to set for the target
+ function(pico_override_psram_size TARGET PSRAM_SIZE)
+ set(PICO_PSRAM_SIZE_BYTES_STRING "${PSRAM_SIZE}")
+ configure_file(${PICO_SDK_PATH}/src/rp2_common/pico_standard_link/pico_psram_region.template.ld ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}/pico_psram_region.ld)
+ pico_add_linker_script_override_path(${TARGET} ${CMAKE_CURRENT_BINARY_DIR}/${TARGET})
+ target_compile_definitions(${TARGET} PRIVATE "PICO_PSRAM_SIZE_BYTES=${PSRAM_SIZE}")
+ endfunction()
+
# pico_set_binary_type(TARGET TYPE)
# \brief\ Set the binary type for the target
#
@@ -155,11 +179,19 @@
# since linker script can handle expressions; may as well leave it as one
#math(EXPR PICO_FLASH_SIZE_BYTES_STRING "${PICO_FLASH_SIZE_BYTES}" OUTPUT_FORMAT HEXADECIMAL)
set(PICO_FLASH_SIZE_BYTES_STRING "${PICO_FLASH_SIZE_BYTES}")
- configure_file(${CMAKE_CURRENT_LIST_DIR}/pico_flash_region.template.ld ${CMAKE_BINARY_DIR}/pico_flash_region.ld)
+ configure_file(${CMAKE_CURRENT_LIST_DIR}/pico_flash_region.template.ld ${CMAKE_CURRENT_BINARY_DIR}/pico_flash_region.ld)
# add include path for linker scripts to find it
- list(APPEND PICO_LINKER_SCRIPT_INCLUDE_DIRS ${CMAKE_BINARY_DIR})
+ list(APPEND PICO_LINKER_SCRIPT_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR})
pico_promote_common_scope_vars()
+ # add PSRAM region if it is enabled
+ if (PICO_PSRAM_SIZE_BYTES)
+ set(PICO_PSRAM_SIZE_BYTES_STRING "${PICO_PSRAM_SIZE_BYTES}")
+ else()
+ set(PICO_PSRAM_SIZE_BYTES_STRING "0")
+ endif()
+ configure_file(${CMAKE_CURRENT_LIST_DIR}/pico_psram_region.template.ld ${CMAKE_CURRENT_BINARY_DIR}/pico_psram_region.ld)
+
# add override paths from PICO_TARGET_LINKER_SCRIPT_OVERRIDE_PATHS
target_link_options(pico_standard_link INTERFACE "LINKER:$<$<BOOL:$<TARGET_PROPERTY:PICO_TARGET_LINKER_SCRIPT_OVERRIDE_PATHS>>:-L$<JOIN:$<TARGET_PROPERTY:PICO_TARGET_LINKER_SCRIPT_OVERRIDE_PATHS>,,-L>>")
diff --git a/src/rp2_common/pico_standard_link/pico_psram_region.bzl b/src/rp2_common/pico_standard_link/pico_psram_region.bzl
new file mode 100644
index 0000000..8fde1d2
--- /dev/null
+++ b/src/rp2_common/pico_standard_link/pico_psram_region.bzl
@@ -0,0 +1,36 @@
+"""Utilities for creating psram regions."""
+
+load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "use_cpp_toolchain")
+load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
+load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
+
+def _generated_pico_psram_region_impl(ctx):
+ psram_region_linker_fragment = ctx.actions.declare_file(ctx.label.name + "/ldinclude/pico_psram_region.ld")
+ link_include_dir = psram_region_linker_fragment.dirname
+
+ file_contents = "\n".join((
+ "PSRAM(rwx) : ORIGIN = 0x11000000, LENGTH = " + str(ctx.attr.psram_region_size),
+ ))
+ ctx.actions.write(psram_region_linker_fragment, file_contents)
+ linking_inputs = cc_common.create_linker_input(
+ owner = ctx.label,
+ user_link_flags = depset(
+ direct = ["-L" + str(link_include_dir)],
+ ),
+ additional_inputs = depset(
+ direct = [psram_region_linker_fragment],
+ ),
+ )
+ return [
+ DefaultInfo(files = depset([psram_region_linker_fragment])),
+ CcInfo(linking_context = cc_common.create_linking_context(linker_inputs = depset(direct = [linking_inputs]))),
+ ]
+
+generated_pico_psram_region = rule(
+ implementation = _generated_pico_psram_region_impl,
+ attrs = {
+ "psram_region_size": attr.int(mandatory = True),
+ },
+ toolchains = use_cpp_toolchain(),
+ fragments = ["cpp"],
+)
diff --git a/src/rp2_common/pico_standard_link/pico_psram_region.template.ld b/src/rp2_common/pico_standard_link/pico_psram_region.template.ld
new file mode 100644
index 0000000..45697f7
--- /dev/null
+++ b/src/rp2_common/pico_standard_link/pico_psram_region.template.ld
@@ -0,0 +1 @@
+PSRAM(rwx) : ORIGIN = 0x11000000, LENGTH = ${PICO_PSRAM_SIZE_BYTES_STRING}
\ No newline at end of file
diff --git a/src/rp2_common/pico_standard_link/script_include/memmap_copy_to_ram.incl b/src/rp2_common/pico_standard_link/script_include/memmap_copy_to_ram.incl
index 5d05531..1bf95c8 100644
--- a/src/rp2_common/pico_standard_link/script_include/memmap_copy_to_ram.incl
+++ b/src/rp2_common/pico_standard_link/script_include/memmap_copy_to_ram.incl
@@ -3,6 +3,7 @@
/* Include memory regions used */
INCLUDE "memory_flash.incl"
+INCLUDE "memory_psram.incl"
INCLUDE "memory_xip_ram.incl"
INCLUDE "memory_ram.incl"
INCLUDE "memory_scratch.incl"
diff --git a/src/rp2_common/pico_standard_link/script_include/memmap_default.incl b/src/rp2_common/pico_standard_link/script_include/memmap_default.incl
index 27d50de..d8e856b 100644
--- a/src/rp2_common/pico_standard_link/script_include/memmap_default.incl
+++ b/src/rp2_common/pico_standard_link/script_include/memmap_default.incl
@@ -3,6 +3,7 @@
/* Include memory regions used */
INCLUDE "memory_flash.incl"
+INCLUDE "memory_psram.incl"
INCLUDE "memory_xip_ram.incl"
INCLUDE "memory_ram.incl"
INCLUDE "memory_scratch.incl"
diff --git a/src/rp2_common/pico_standard_link/script_include/memmap_no_flash.incl b/src/rp2_common/pico_standard_link/script_include/memmap_no_flash.incl
index 8c9cf4b..264bf4e 100644
--- a/src/rp2_common/pico_standard_link/script_include/memmap_no_flash.incl
+++ b/src/rp2_common/pico_standard_link/script_include/memmap_no_flash.incl
@@ -2,6 +2,7 @@
INCLUDE "set_memory_locations.incl"
/* Include memory regions used */
+INCLUDE "memory_psram.incl"
INCLUDE "memory_xip_ram.incl"
INCLUDE "memory_ram.incl"
INCLUDE "memory_scratch.incl"
diff --git a/src/rp2_common/pico_standard_link/script_include/memory_aliases_flash.incl b/src/rp2_common/pico_standard_link/script_include/memory_aliases_flash.incl
index 200d878..06492d2 100644
--- a/src/rp2_common/pico_standard_link/script_include/memory_aliases_flash.incl
+++ b/src/rp2_common/pico_standard_link/script_include/memory_aliases_flash.incl
@@ -2,6 +2,7 @@
REGION_ALIAS("TEXT_STORE", FLASH);
REGION_ALIAS("RAM_STORE", FLASH);
+REGION_ALIAS("PSRAM_STORE", FLASH);
REGION_ALIAS("XIP_RAM_STORE", FLASH);
REGION_ALIAS("SCRATCH_X_STORE", FLASH);
REGION_ALIAS("SCRATCH_Y_STORE", FLASH);
diff --git a/src/rp2_common/pico_standard_link/script_include/memory_aliases_no_flash.incl b/src/rp2_common/pico_standard_link/script_include/memory_aliases_no_flash.incl
index 2d48e87..d264f23 100644
--- a/src/rp2_common/pico_standard_link/script_include/memory_aliases_no_flash.incl
+++ b/src/rp2_common/pico_standard_link/script_include/memory_aliases_no_flash.incl
@@ -2,6 +2,7 @@
REGION_ALIAS("TEXT_STORE", RAM);
REGION_ALIAS("RAM_STORE", RAM);
+REGION_ALIAS("PSRAM_STORE", RAM); /* Can't store in PSRAM */
REGION_ALIAS("XIP_RAM_STORE", XIP_RAM);
REGION_ALIAS("SCRATCH_X_STORE", SCRATCH_X);
REGION_ALIAS("SCRATCH_Y_STORE", SCRATCH_Y);
diff --git a/src/rp2_common/pico_standard_link/script_include/memory_psram.incl b/src/rp2_common/pico_standard_link/script_include/memory_psram.incl
new file mode 100644
index 0000000..d177e89
--- /dev/null
+++ b/src/rp2_common/pico_standard_link/script_include/memory_psram.incl
@@ -0,0 +1,4 @@
+MEMORY
+{
+ INCLUDE "pico_psram_region.ld"
+}
diff --git a/src/rp2_common/pico_standard_link/script_include/section_tls.incl b/src/rp2_common/pico_standard_link/script_include/section_tls.incl
index 5f15fae..be358cc 100644
--- a/src/rp2_common/pico_standard_link/script_include/section_tls.incl
+++ b/src/rp2_common/pico_standard_link/script_include/section_tls.incl
@@ -18,12 +18,12 @@
PROVIDE( __tbss_size = SIZEOF(.tbss) );
PROVIDE( __tbss_offset = ADDR(.tbss) - __tls_start );
- /* this marker is present if the code knows we don't need __tls0 and __tls1... we decide
+ /* this marker is present if the code knows we don't need __tls0 and __tls1... we decide
* to waste 4 bytes in that case rather than possibly large amounts for __tls0 and __tls1
* which will never be used */
.tlsX_not_needed_marker (NOLOAD) : {
KEEP(*(.tlsX_not_needed_marker))
- }
+ } > RAM
PROVIDE( __tlsX_align = SIZEOF(.tlsX_not_needed_marker) > 0 ? 4 : __tls_align);
PROVIDE( __tlsX_size_align = SIZEOF(.tlsX_not_needed_marker) > 0 ? 0 : __tls_size_align);
diff --git a/src/rp2_common/pico_standard_link/script_include/sections_copy_to_ram.incl b/src/rp2_common/pico_standard_link/script_include/sections_copy_to_ram.incl
index 0c3c7d4..c6e586b 100644
--- a/src/rp2_common/pico_standard_link/script_include/sections_copy_to_ram.incl
+++ b/src/rp2_common/pico_standard_link/script_include/sections_copy_to_ram.incl
@@ -11,6 +11,7 @@
INCLUDE "section_generated_post_scratch.incl"
INCLUDE "section_extra_post_scratch.incl"
INCLUDE "sections_stack.incl"
+INCLUDE "sections_psram.incl"
INCLUDE "section_flash_end.incl"
INCLUDE "section_end.incl"
INCLUDE "section_generated_post_end.incl"
diff --git a/src/rp2_common/pico_standard_link/script_include/sections_default.incl b/src/rp2_common/pico_standard_link/script_include/sections_default.incl
index 4e07b39..8f15074 100644
--- a/src/rp2_common/pico_standard_link/script_include/sections_default.incl
+++ b/src/rp2_common/pico_standard_link/script_include/sections_default.incl
@@ -11,6 +11,7 @@
INCLUDE "section_generated_post_scratch.incl"
INCLUDE "section_extra_post_scratch.incl"
INCLUDE "sections_stack.incl"
+INCLUDE "sections_psram.incl"
INCLUDE "section_flash_end.incl"
INCLUDE "section_end.incl"
INCLUDE "section_generated_post_end.incl"
diff --git a/src/rp2_common/pico_standard_link/script_include/sections_no_flash.incl b/src/rp2_common/pico_standard_link/script_include/sections_no_flash.incl
index e9ba353..a7a467e 100644
--- a/src/rp2_common/pico_standard_link/script_include/sections_no_flash.incl
+++ b/src/rp2_common/pico_standard_link/script_include/sections_no_flash.incl
@@ -11,6 +11,7 @@
INCLUDE "section_generated_post_scratch.incl"
INCLUDE "section_extra_post_scratch.incl"
INCLUDE "sections_stack.incl"
+INCLUDE "sections_psram.incl"
INCLUDE "section_end.incl"
INCLUDE "section_generated_post_end.incl"
INCLUDE "section_extra_post_end.incl"
diff --git a/src/rp2_common/pico_standard_link/script_include/sections_psram.incl b/src/rp2_common/pico_standard_link/script_include/sections_psram.incl
new file mode 100644
index 0000000..7f986a3
--- /dev/null
+++ b/src/rp2_common/pico_standard_link/script_include/sections_psram.incl
@@ -0,0 +1,17 @@
+SECTIONS
+{
+ .psram_load : {
+ __psram_start__ = .;
+ __psram_load_start__ = .;
+ *(SORT(.psram_initialised.*))
+ . = ALIGN(4);
+ __psram_load_end__ = .;
+ } > PSRAM AT> PSRAM_STORE
+ __psram_load_source__ = LOADADDR(.psram_load);
+
+ .psram_noload (NOLOAD) : {
+ *(SORT(.psram_uninitialised.*))
+ . = ALIGN(4);
+ __psram_end__ = .;
+ } > PSRAM
+}
diff --git a/test/kitchen_sink/BUILD.bazel b/test/kitchen_sink/BUILD.bazel
index de46657..189eef3 100644
--- a/test/kitchen_sink/BUILD.bazel
+++ b/test/kitchen_sink/BUILD.bazel
@@ -2,6 +2,7 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("//bazel:defs.bzl", "compatible_with_rp2", "incompatible_with_config", "pico_generate_pio_header")
load("//bazel/util:transition.bzl", "kitchen_sink_test_binary", "pico_set_binary_type", "pico_set_linker_script")
+load("//src/rp2_common/pico_standard_link:pico_psram_region.bzl", "generated_pico_psram_region")
package(default_visibility = ["//visibility:public"])
@@ -86,6 +87,7 @@
"//test/pico_test",
] + select({
"//bazel/constraint:rp2350": [
+ "//src/rp2_common/hardware_psram",
"//src/rp2_common/hardware_sha256",
],
"//conditions:default": [
@@ -266,3 +268,39 @@
mbedtls_config = ":mbedtls_config",
target_compatible_with = compatible_with_rp2(),
)
+
+cc_binary(
+ name = "kitchen_sink_psram_actual",
+ testonly = True,
+ srcs = ["kitchen_sink.c"],
+ defines = [
+ "PICO_AUTO_DETECT_PSRAM=1",
+ 'PICO_PSRAM_SIZE_BYTES="(8 * 1024 * 1024)"',
+ ],
+ tags = ["manual"], # Built via kitchen_sink_psram
+ deps = [":kitchen_sink_common"],
+)
+
+generated_pico_psram_region(
+ name = "8M_psram_region",
+ psram_region_size = (8 * 1024 * 1024),
+)
+
+cc_library(
+ name = "kitchen_sink_psram_linker_script",
+ # The order of the linker scripts below is important.
+ # Prevent buildifier from reordering them.
+ # buildifier: leave-alone
+ deps = [
+ "//test/kitchen_sink:8M_psram_region",
+ "//src/rp2_common/pico_standard_link:default_linker_script",
+ ],
+)
+
+pico_set_linker_script(
+ name = "kitchen_sink_psram",
+ testonly = True,
+ src = ":kitchen_sink_psram_actual",
+ linker_script = "//test/kitchen_sink:kitchen_sink_psram_linker_script",
+ target_compatible_with = ["//bazel/constraint:rp2350"],
+)
diff --git a/test/kitchen_sink/CMakeLists.txt b/test/kitchen_sink/CMakeLists.txt
index 999d8dd..a01304b 100644
--- a/test/kitchen_sink/CMakeLists.txt
+++ b/test/kitchen_sink/CMakeLists.txt
@@ -18,6 +18,7 @@
hardware_pio
hardware_pll
hardware_powman
+ hardware_psram
hardware_pwm
hardware_rcp
hardware_resets
@@ -169,6 +170,70 @@
pico_add_extra_outputs(kitchen_sink)
target_compile_definitions(kitchen_sink PRIVATE KITCHEN_SINK_ID="regular binary")
+if (NOT PICO_RP2040)
+ add_executable(kitchen_sink_psram_fixed ${CMAKE_CURRENT_LIST_DIR}/kitchen_sink.c)
+ target_link_libraries(kitchen_sink_psram_fixed kitchen_sink_libs kitchen_sink_options)
+ target_compile_definitions(kitchen_sink_psram_fixed PRIVATE FIXED_PSRAM_SIZE=1)
+ if (NOT DEFINED PICO_PSRAM_SIZE_BYTES)
+ # Override the PSRAM size for boards that usually don't have PSRAM to allow
+ # variables in PSRAM, and auto-detect at runtime if PSRAM is present.
+ target_compile_definitions(kitchen_sink_psram_fixed PRIVATE PICO_AUTO_DETECT_PSRAM=1)
+ pico_override_psram_size(kitchen_sink_psram_fixed "(1 * 1024 * 1024)")
+ endif()
+ pico_add_extra_outputs(kitchen_sink_psram_fixed)
+ target_compile_definitions(kitchen_sink_psram_fixed PRIVATE KITCHEN_SINK_ID="psram fixed size binary")
+ set_target_properties(kitchen_sink_psram_fixed PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:")
+
+ add_executable(kitchen_sink_psram_tiny ${CMAKE_CURRENT_LIST_DIR}/kitchen_sink.c)
+ target_link_libraries(kitchen_sink_psram_tiny kitchen_sink_libs kitchen_sink_options)
+ target_compile_definitions(kitchen_sink_psram_tiny PRIVATE FIXED_PSRAM_SIZE=1 TINY_PSRAM=1)
+ # Override the PSRAM size to allow variables in PSRAM
+ target_compile_definitions(kitchen_sink_psram_tiny PRIVATE PICO_AUTO_DETECT_PSRAM=1)
+ pico_override_psram_size(kitchen_sink_psram_tiny "(1 * 1024 * 1024)")
+ pico_add_extra_outputs(kitchen_sink_psram_tiny)
+ target_compile_definitions(kitchen_sink_psram_tiny PRIVATE KITCHEN_SINK_ID="psram tiny size binary")
+ # Expected to bus fault, so printing PASSED is a failure
+ set_target_properties(kitchen_sink_psram_tiny PROPERTIES PICO_TEST_SUCCESS_STRING "psram tiny size binary")
+ set_target_properties(kitchen_sink_psram_tiny PROPERTIES PICO_TEST_FAILURE_STRING "PASSED")
+
+ add_executable(kitchen_sink_psram_small ${CMAKE_CURRENT_LIST_DIR}/kitchen_sink.c)
+ target_link_libraries(kitchen_sink_psram_small kitchen_sink_libs kitchen_sink_options)
+ target_compile_definitions(kitchen_sink_psram_small PRIVATE FIXED_PSRAM_SIZE=1 SMALL_PSRAM=1)
+ # Override the PSRAM size to allow variables in PSRAM
+ target_compile_definitions(kitchen_sink_psram_small PRIVATE PICO_AUTO_DETECT_PSRAM=1)
+ pico_override_psram_size(kitchen_sink_psram_small "(1 * 1024 * 1024)")
+ pico_add_extra_outputs(kitchen_sink_psram_small)
+ target_compile_definitions(kitchen_sink_psram_small PRIVATE KITCHEN_SINK_ID="psram small size binary")
+ set_target_properties(kitchen_sink_psram_small PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:")
+
+ add_executable(kitchen_sink_psram_detect ${CMAKE_CURRENT_LIST_DIR}/kitchen_sink.c)
+ target_link_libraries(kitchen_sink_psram_detect kitchen_sink_libs kitchen_sink_options)
+ target_compile_definitions(kitchen_sink_psram_detect PRIVATE PICO_AUTO_DETECT_PSRAM=1)
+ pico_add_extra_outputs(kitchen_sink_psram_detect)
+ target_compile_definitions(kitchen_sink_psram_detect PRIVATE KITCHEN_SINK_ID="psram detect size binary")
+ set_target_properties(kitchen_sink_psram_detect PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:")
+
+ add_executable(kitchen_sink_no_flash_psram_detect ${CMAKE_CURRENT_LIST_DIR}/kitchen_sink.c)
+ target_link_libraries(kitchen_sink_no_flash_psram_detect kitchen_sink_libs kitchen_sink_options)
+ target_compile_definitions(kitchen_sink_no_flash_psram_detect PRIVATE PICO_AUTO_DETECT_PSRAM=1)
+ pico_set_binary_type(kitchen_sink_no_flash_psram_detect no_flash)
+ pico_add_extra_outputs(kitchen_sink_no_flash_psram_detect)
+ target_compile_definitions(kitchen_sink_no_flash_psram_detect PRIVATE KITCHEN_SINK_ID="psram detect size no_flash binary")
+ set_target_properties(kitchen_sink_no_flash_psram_detect PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:")
+
+ add_executable(kitchen_sink_psram_flash_fixed ${CMAKE_CURRENT_LIST_DIR}/kitchen_sink.c)
+ target_link_libraries(kitchen_sink_psram_flash_fixed kitchen_sink_libs kitchen_sink_options)
+ target_compile_definitions(kitchen_sink_psram_flash_fixed PRIVATE FIXED_PSRAM_SIZE=1)
+ # Override the FLASH and PSRAM size to check overriding works
+ # variables in PSRAM, and auto-detect at runtime if PSRAM is present.
+ target_compile_definitions(kitchen_sink_psram_flash_fixed PRIVATE PICO_AUTO_DETECT_PSRAM=1)
+ pico_override_flash_size(kitchen_sink_psram_flash_fixed "(1 * 1024 * 1024)")
+ pico_override_psram_size(kitchen_sink_psram_flash_fixed "(1 * 1024 * 1024)")
+ pico_add_extra_outputs(kitchen_sink_psram_flash_fixed)
+ target_compile_definitions(kitchen_sink_psram_flash_fixed PRIVATE KITCHEN_SINK_ID="psram and flash fixed size binary")
+ set_target_properties(kitchen_sink_psram_flash_fixed PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:")
+endif()
+
if (TARGET hardware_pio)
pico_generate_pio_header(kitchen_sink ${CMAKE_CURRENT_LIST_DIR}/trivial.pio)
endif()
diff --git a/test/kitchen_sink/kitchen_sink.c b/test/kitchen_sink/kitchen_sink.c
index 6cd96d0..a151a0b 100644
--- a/test/kitchen_sink/kitchen_sink.c
+++ b/test/kitchen_sink/kitchen_sink.c
@@ -21,6 +21,11 @@
#if LIB_PICO_AON_TIMER
#include "pico/aon_timer.h"
#endif
+#if !PICO_RP2040
+#include "hardware/flash.h"
+#include "hardware/psram.h"
+#include "hardware/xip_cache.h"
+#endif
#else
#include KITCHEN_SINK_INCLUDE_HEADER
#endif
@@ -47,6 +52,35 @@
uint32_t dma_to = 0;
uint32_t dma_from = 0xaaaa5555;
+#ifdef FIXED_PSRAM_SIZE
+int __in_psram("foo") foo_psram = 23;
+char __uninitialized_psram("bar") bar_psram[0x8000];
+#if defined(TINY_PSRAM) || defined(SMALL_PSRAM)
+void make_tiny_psram(void) {
+#if defined(TINY_PSRAM)
+ // Override flash_devinfo cs size to be tiny, so bar_psram doesn't fit in it
+ flash_devinfo_set_cs_size(1, FLASH_DEVINFO_SIZE_8K);
+#elif defined(SMALL_PSRAM)
+ // Override flash_devinfo cs size to be small, so bar_psram fits but int_buffer doesn't
+ flash_devinfo_set_cs_size(1, FLASH_DEVINFO_SIZE_128K);
+#endif
+ // Still auto-detect CS pin, as we don't know that
+ #if PICO_RP2350
+ #if PICO_RP2350A
+ uint8_t cs_gpios[] = {0, 8, 19};
+ #else
+ uint8_t cs_gpios[] = {0, 8, 19, 47};
+ #endif
+ #else
+ // Unknown platform, just try 0
+ uint8_t cs_gpios[] = {0}
+ #endif
+ psram_detect_cs_and_size(cs_gpios, count_of(cs_gpios));
+}
+PICO_RUNTIME_INIT_FUNC_RUNTIME(make_tiny_psram, "11000");
+#endif
+#endif
+
void __noinline spiggle(void) {
dma_channel_config c = dma_channel_get_default_config(1);
channel_config_set_bswap(&c, true);
@@ -195,6 +229,91 @@
busy_wait_ms(500);
}
#endif
+
+#if PICO_PSRAM_SIZE_BYTES
+ psram_or_malloc("z0000", char, char_buffer, 0x8000);
+ memset(char_buffer, 0x55, 0x8000);
+ printf("char_buffer in %s at %p\n", char_buffer < (char*)SRAM_BASE ? "PSRAM" : "Normal SRAM", char_buffer);
+ psram_or_free(char_buffer);
+ psram_or_malloc("z0001", int, int_buffer, 0x8000);
+ memset(int_buffer, 0x55, 0x8000 * sizeof(int));
+ printf("int_buffer in %s at %p\n", int_buffer < (int*)SRAM_BASE ? "PSRAM" : "Normal SRAM", int_buffer);
+ psram_or_free(int_buffer);
+#endif
+
+#ifdef FIXED_PSRAM_SIZE
+ if (psram_is_available()) {
+ printf("PSRAM is available\n");
+ memset(bar_psram, 0x55, sizeof(bar_psram));
+ printf("foo_psram = %d, bar_psram = %02x\n", foo_psram, bar_psram[0]);
+ if (foo_psram != 23 || bar_psram[0] != 0x55) {
+ printf("ERROR: foo_psram = %d, bar_psram = %02x\n", foo_psram, bar_psram[0]);
+ }
+ // Make sure the write actually went to PSRAM
+ xip_cache_clean_all();
+ xip_cache_invalidate_all();
+ if (foo_psram != 23 || bar_psram[0] != 0x55) {
+ printf("ERROR: after flush foo_psram = %d, bar_psram = %02x\n", foo_psram, bar_psram[0]);
+ }
+ // Check PSRAM still works after flash functions
+ flash_range_erase(PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE);
+ if (foo_psram != 23 || bar_psram[0] != 0x55) {
+ printf("ERROR: after erase foo_psram = %d, bar_psram = %02x\n", foo_psram, bar_psram[0]);
+ }
+ foo_psram = 27;
+ memset(bar_psram, 0xab, sizeof(bar_psram));
+ // Make sure the write actually went to PSRAM
+ xip_cache_clean_all();
+ xip_cache_invalidate_all();
+ printf("foo_psram = %d, bar_psram = %02x\n", foo_psram, bar_psram[0]);
+ if (foo_psram != 27 || bar_psram[0] != 0xab) {
+ printf("ERROR: after program foo_psram = %d, bar_psram = %02x\n", foo_psram, bar_psram[0]);
+ }
+ } else {
+ printf("PSRAM not available\n");
+ }
+#elif !PICO_RP2040
+ if (psram_is_available()) {
+ printf("PSRAM is available, size = 0x%x\n", psram_get_size());
+ size_t psram_size = psram_get_size();
+ // Fill each half with different data, to check wrapping isn't ocurring
+ char *foo_psram = (char*)(XIP_BASE + 0x01000000);
+ size_t foo_psram_size = psram_size / 2;
+ char *bar_psram = foo_psram + foo_psram_size;
+ size_t bar_psram_size = foo_psram_size;
+ memset(foo_psram, 0x55, foo_psram_size);
+ memset(bar_psram, 0xab, bar_psram_size);
+ printf("foo_psram = %02x, bar_psram = %02x\n", foo_psram[0], bar_psram[0]);
+ if (foo_psram[0] != 0x55 || bar_psram[0] != 0xab) {
+ printf("ERROR: foo_psram = %02x, bar_psram = %02x\n", foo_psram[0], bar_psram[0]);
+ }
+ // Make sure the write actually went to PSRAM
+ xip_cache_clean_all();
+ xip_cache_invalidate_all();
+ if (foo_psram[0] != 0x55 || bar_psram[0] != 0xab) {
+ printf("ERROR: after flush foo_psram = %02x, bar_psram = %02x\n", foo_psram[0], bar_psram[0]);
+ }
+ // Check PSRAM still works after flash functions
+ flash_range_erase(PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE);
+ if (foo_psram[0] != 0x55 || bar_psram[0] != 0xab) {
+ printf("ERROR: after erase foo_psram = %02x, bar_psram = %02x\n", foo_psram[0], bar_psram[0]);
+ }
+ memset(foo_psram, 0xac, foo_psram_size);
+ memset(bar_psram, 0x56, bar_psram_size);
+ // Make sure the write actually went to PSRAM
+ xip_cache_clean_all();
+ xip_cache_invalidate_all();
+ printf("foo_psram = %02x, bar_psram = %02x\n", foo_psram[0], bar_psram[0]);
+ if (foo_psram[0] != 0xac || bar_psram[0] != 0x56) {
+ printf("ERROR: after program bar_psram = %02x\n", bar_psram[0]);
+ }
+ } else {
+ #if PICO_AUTO_DETECT_PSRAM // Only printout when trying to autodetect
+ printf("PSRAM not available\n");
+ #endif
+ }
+#endif
+
#ifndef __riscv
exception_set_exclusive_handler(SVCALL_EXCEPTION, svc_call);
// this should compile as we are Cortex-M
diff --git a/tools/bazel_build.py b/tools/bazel_build.py
index 45ffa41..cfe8137 100755
--- a/tools/bazel_build.py
+++ b/tools/bazel_build.py
@@ -44,6 +44,7 @@
"//test/kitchen_sink:kitchen_sink_lwip_background",
"//test/kitchen_sink:kitchen_sink_ram_section",
"//test/kitchen_sink:kitchen_sink_simple_overlay",
+ "//test/kitchen_sink:kitchen_sink_psram",
"//test/pico_divider_test:pico_divider_test",
"//test/pico_divider_test:pico_divider_nesting_test",
"//test/pico_float_test:pico_double_test",
@@ -81,8 +82,10 @@
"//test/pico_float_test:hazard3_test_gen",
# No RISC-V on RP2040.
"//test/pico_float_test:pico_float_test_hazard3",
- # hardware_sha256 doesn't appear to work on RP2040.
+ # No sha256 on RP2040.
"//test/pico_sha256_test:pico_sha256_test",
+ # No PSRAM on RP2040
+ "//test/kitchen_sink:kitchen_sink_psram",
)
),
},
@@ -118,10 +121,12 @@
"//test/pico_float_test:hazard3_test_gen",
# No RISC-V on RP2040.
"//test/pico_float_test:pico_float_test_hazard3",
- # hardware_sha256 doesn't appear to work on RP2040.
+ # No sha256 on RP2040.
"//test/pico_sha256_test:pico_sha256_test",
# not supported by clang
"//test/kitchen_sink:kitchen_sink_simple_overlay",
+ # No PSRAM on RP2040
+ "//test/kitchen_sink:kitchen_sink_psram",
)
),
},
@@ -160,8 +165,10 @@
"//test/pico_float_test:hazard3_test_gen",
# No RISC-V on RP2040.
"//test/pico_float_test:pico_float_test_hazard3",
- # hardware_sha256 doesn't appear to work on RP2040.
+ # No sha256 on RP2040.
"//test/pico_sha256_test:pico_sha256_test",
+ # No PSRAM on RP2040
+ "//test/kitchen_sink:kitchen_sink_psram",
)
),
},
diff --git a/tools/check_board_header.py b/tools/check_board_header.py
index 817f3e2..165d6de 100755
--- a/tools/check_board_header.py
+++ b/tools/check_board_header.py
@@ -37,10 +37,24 @@
'RP2350B': "src/rp2350/rp2350b_interface_pins.json",
}
-compulsory_cmake_settings = set(['PICO_PLATFORM'])
-compulsory_cmake_default_settings = set(['PICO_FLASH_SIZE_BYTES'])
-matching_cmake_default_settings = set(['PICO_FLASH_SIZE_BYTES', 'PICO_RP2350_A2_SUPPORTED'])
-compulsory_defines = set(['PICO_FLASH_SIZE_BYTES'])
+# names for which we MUST have a pico_board_cmake_set
+compulsory_cmake_settings = frozenset(['PICO_PLATFORM'])
+
+# names for which we MUST have a pico_board_cmake_set_default
+compulsory_cmake_default_settings = frozenset(['PICO_FLASH_SIZE_BYTES'])
+
+# names for which pico_board_cmake_set_default (if present) needs a matching #define (and vice-versa)
+matching_cmake_default_settings = frozenset(['PICO_FLASH_SIZE_BYTES', 'PICO_RP2350_A2_SUPPORTED', 'PICO_PSRAM_SIZE_BYTES'])
+
+# names for which we MUST have a #define
+compulsory_defines = frozenset(['PICO_FLASH_SIZE_BYTES'])
+
+# names where the presence of one #define requires other #defines to be present too
+# (this is one-way and not bidirectional, so you'll need multiple entries for mutually-dependent #defines)
+linked_defines = {
+ 'PICO_PSRAM_SIZE_BYTES': frozenset(['PICO_PSRAM_CS_PIN']),
+ 'PICO_AUTO_DETECT_PSRAM_SIZE': frozenset(['PICO_PSRAM_CS_PIN']),
+}
DefineType = namedtuple("DefineType", ["name", "value", "resolved_value", "lineno", "has_ifndef"])
@@ -461,6 +475,11 @@
for setting in compulsory_defines:
if setting not in defines:
errors.append(Exception("{} is missing a #define {}".format(board_header, setting)))
+ for setting in linked_defines:
+ if setting in defines:
+ for linked_define in linked_defines[setting]:
+ if linked_define not in defines:
+ errors.append(Exception("{} defines {} but is missing a corresponding #define {}".format(board_header, setting, linked_define)))
if chip is None:
errors.append(Exception("Couldn't determine chip for {}".format(board_header)))
@@ -476,7 +495,7 @@
with open(interfaces_json) as interfaces_fh:
interface_pins = json.load(interfaces_fh)
allowed_interfaces = interface_pins["interfaces"]
- allowed_pins = set(interface_pins["pins"])
+ allowed_pins = frozenset(interface_pins["pins"])
# convert instance-keys to integers (allowed by Python but not by JSON)
for interface in allowed_interfaces:
instances = allowed_interfaces[interface]["instances"]
@@ -558,6 +577,24 @@
if (name in cmake_default_settings or name.startswith("PICO_DEFAULT_")) and not define.has_ifndef:
errors.append(Exception("{}:{} {} isn't enclosed in an #ifndef {} guard".format(board_header, define.lineno, name, name)))
+ # check for invalid PSRAM CS pin
+ if name == "PICO_PSRAM_CS_PIN":
+ interface = "QMI"
+ instance_num = 0
+ function = "CS1N"
+ if interface not in allowed_interfaces:
+ errors.append(Exception("{}:{} {} is defined but {} isn't in {}".format(board_header, define.lineno, name, interface, interfaces_json)))
+ continue
+ if instance_num not in allowed_interfaces[interface]["instances"]:
+ errors.append(Exception("{}:{} {} is set to an invalid instance {}".format(board_header, instance_define.lineno, instance_define, instance_num)))
+ continue
+ interface_instance = allowed_interfaces[interface]["instances"][instance_num]
+ if function not in interface_instance:
+ errors.append(Exception("{}:{} {} is defined but {} isn't a valid function for {}".format(board_header, define.lineno, name, function, instance_define)))
+ continue
+ if define.resolved_value not in interface_instance[function]:
+ errors.append(Exception("{}:{} {} is set to {} which isn't a valid pin for {} on {} {}".format(board_header, define.lineno, name, define.resolved_value, function, interface, instance_num)))
+
if not has_include_guard:
errors.append(Exception("{} has no include-guard (expected {})".format(board_header, expected_include_guard)))
if not has_board_detection and expected_board_detection != "NONE":
diff --git a/tools/extract_configs.py b/tools/extract_configs.py
index b226d11..c123c64 100755
--- a/tools/extract_configs.py
+++ b/tools/extract_configs.py
@@ -60,6 +60,7 @@
PROPERTY_TYPE_INT = 'int'
PROPERTY_TYPE_BOOL = 'bool'
PROPERTY_TYPE_ENUM = 'enum'
+PROPERTY_TYPE_LIST = 'list'
CHIP_NAMES = ["rp2040", "rp2350"]
@@ -168,6 +169,14 @@
if str_values[attr_name] not in _enumvalues:
errors.append(Exception('{} at {}:{} has {} value {} which isn\'t in list of {} {}'.format(config_name, file_path, linenum, attr_name, str_values[attr_name], PROPERTY_ENUMVALUES, str_values[PROPERTY_ENUMVALUES])))
+ elif type_str == PROPERTY_TYPE_LIST:
+ assert PROPERTY_MIN not in config_attrs
+ assert PROPERTY_MAX not in config_attrs
+ assert PROPERTY_ENUMVALUES not in config_attrs
+
+ attr_name = PROPERTY_DEFAULT
+ str_values[attr_name] = config_attrs.get(attr_name, None)
+
else:
errors.append(Exception("Found unknown {} type {} at {}:{}".format(BASE_CONFIG_NAME, type_str, file_path, linenum)))
diff --git a/tools/memmap_include_tree.py b/tools/memmap_include_tree.py
index 807d45c..6f090c2 100755
--- a/tools/memmap_include_tree.py
+++ b/tools/memmap_include_tree.py
@@ -45,7 +45,7 @@
)
# CMake generates this in the binary dir; skip it so the tree is useful without a build.
-IGNORED_INCLUDE_NAMES = frozenset({"pico_flash_region.ld"})
+IGNORED_INCLUDE_NAMES = frozenset({"pico_flash_region.ld", "pico_psram_region.ld"})
# Shared CLI helpers (argparse help strings, colors, doc folding) for memmap_annotate.py
CLI_HELP_SDK_ROOT = (