blob: 0010a67d9882446268173b122b9055092b74f9cc [file]
/*
* 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