|
| 1 | +# PSRAM (APS6404L-3SQR-SN) |
| 2 | + |
| 3 | +The Harucom Board includes an AP Memory APS6404L-3SQR-SN (8 MB QSPI PSRAM). |
| 4 | +It is used as the mruby VM heap, expanding the available memory from 256 KB |
| 5 | +(on-chip SRAM) to 8 MB. |
| 6 | + |
| 7 | +## Hardware configuration |
| 8 | + |
| 9 | +| Item | Value | |
| 10 | +|------|-------| |
| 11 | +| IC | APS6404L-3SQR-SN | |
| 12 | +| Capacity | 64 Mbit (8 MB) | |
| 13 | +| Interface | SPI / QPI (SDR) | |
| 14 | +| Max clock | 109 MHz (VDD=3.3V, Wrapped Burst) | |
| 15 | +| CS pin | GPIO 0 (`PICO_RP2350_PSRAM_CS_PIN`) | |
| 16 | +| Bus | QSPI (shared with flash: SCLK, SD0–SD3) | |
| 17 | + |
| 18 | +The PSRAM CE# pin is connected to GPIO 0 with a 10 kΩ pull-up resistor to |
| 19 | +keep it deselected by default. The QSPI data lines (SD0–SD3) and clock (SCLK) |
| 20 | +are shared with the on-board flash (W25Q128JVS). |
| 21 | + |
| 22 | +## RP2350 XIP subsystem and PSRAM |
| 23 | + |
| 24 | +The RP2350 QMI (QSPI Memory Interface) supports two chip selects: |
| 25 | + |
| 26 | +- **CS0** — Flash (dedicated QSPI_SS pin), memory window M0 (0x10000000–0x10FFFFFF) |
| 27 | +- **CS1** — PSRAM (via GPIO), memory window M1 (0x11000000–0x117FFFFF) |
| 28 | + |
| 29 | +A 16 kB XIP cache transparently covers both windows. The cache is write-back: |
| 30 | +writes are held as dirty lines and flushed to PSRAM on eviction. |
| 31 | + |
| 32 | +### Address aliases |
| 33 | + |
| 34 | +The same physical address can be accessed through multiple aliases |
| 35 | +(RP2350 datasheet §4.4.1): |
| 36 | + |
| 37 | +| Base address | Description | |
| 38 | +|---|---| |
| 39 | +| `0x10000000` | Cached XIP access | |
| 40 | +| `0x14000000` | Uncached (cache bypass) | |
| 41 | +| `0x18000000` | Cache maintenance | |
| 42 | +| `0x1C000000` | Uncached + untranslated (bypass ATRANS) | |
| 43 | + |
| 44 | +The PSRAM cached address is `0x11000000`; its uncached alias is `0x15000000`. |
| 45 | + |
| 46 | +## Initialization sequence |
| 47 | + |
| 48 | +The implementation is in `src/psram.c`. Initialization proceeds in five steps. |
| 49 | + |
| 50 | +### Step 1: Detect PSRAM (`get_psram_size`) |
| 51 | + |
| 52 | +Uses QMI **direct mode** to send an SPI Read ID (0x9F) command, confirming the |
| 53 | +PSRAM is present and determining its size. Direct mode suspends memory-mapped |
| 54 | +(XIP) access and allows manual control of SPI transactions, so it works even |
| 55 | +before ATRANS and M1 registers are configured. |
| 56 | + |
| 57 | +To handle the case where the PSRAM is still in QPI mode after a warm reset, |
| 58 | +Exit Quad Mode (0xF5) is sent in quad width first. |
| 59 | + |
| 60 | +``` |
| 61 | +Exit QPI (0xF5, quad width) → Read ID (0x9F, SPI) → extract KGD/EID |
| 62 | +``` |
| 63 | + |
| 64 | +The function is marked `__no_inline_not_in_flash_func` so it runs from RAM, |
| 65 | +since flash XIP is paused while direct mode is active. |
| 66 | + |
| 67 | +### Step 2: Configure CS1 (bootrom flash_devinfo API) |
| 68 | + |
| 69 | +The bootrom is informed about the CS1 device via its runtime API: |
| 70 | + |
| 71 | +```c |
| 72 | +flash_devinfo_set_cs_gpio(1, PICO_RP2350_PSRAM_CS_PIN); // GPIO 0 |
| 73 | +flash_devinfo_set_cs_size(1, FLASH_DEVINFO_SIZE_8M); // 8 MB |
| 74 | +rom_connect_internal_flash(); |
| 75 | +rom_flash_exit_xip(); |
| 76 | +rom_flash_enter_cmd_xip(); |
| 77 | +``` |
| 78 | +
|
| 79 | +This API is the runtime equivalent of programming the OTP FLASH_DEVINFO |
| 80 | +register. It causes the bootrom to configure: |
| 81 | +
|
| 82 | +- **ATRANS registers** — address translation from XIP address space to CS1 |
| 83 | +- **GPIO pads** — output configuration for the CS1 pin |
| 84 | +
|
| 85 | +#### Why manual ATRANS writes alone do not work |
| 86 | +
|
| 87 | +Writing the QMI ATRANS registers directly is **not sufficient** for |
| 88 | +memory-mapped CS1 access. The bootrom must set up additional internal state |
| 89 | +(pad configuration, etc.) that is only applied through |
| 90 | +`rom_connect_internal_flash()`. |
| 91 | +
|
| 92 | +This behavior is discussed in the pico-sdk GitHub issue: |
| 93 | +https://github.com/raspberrypi/pico-sdk/issues/2205 |
| 94 | +
|
| 95 | +### Step 3: Enter QPI mode |
| 96 | +
|
| 97 | +The following SPI commands are sent to the PSRAM via QMI direct mode: |
| 98 | +
|
| 99 | +| Command | Code | Description | |
| 100 | +|---|---|---| |
| 101 | +| Reset Enable | 0x66 | Prepare for reset (must immediately precede 0x99) | |
| 102 | +| Reset | 0x99 | Software reset — returns device to SPI standby mode | |
| 103 | +| Enter Quad Mode | 0x35 | Switch to QPI mode (only valid in SPI mode) | |
| 104 | +
|
| 105 | +After reset the device is in SPI mode (datasheet §8.4). Enter Quad Mode |
| 106 | +switches it to QPI for higher throughput. |
| 107 | +
|
| 108 | +### Step 4: Configure QMI M1 registers |
| 109 | +
|
| 110 | +The QMI memory window 1 registers are programmed for QPI read/write access. |
| 111 | +
|
| 112 | +**Read — Fast Quad Read (0xEB):** |
| 113 | +
|
| 114 | +``` |
| 115 | +[CMD 0xEB: 2 clk] [24-bit ADDR: 6 clk] [6 WAIT: 6 clk] [DATA...] |
| 116 | + quad quad quad quad |
| 117 | +``` |
| 118 | +
|
| 119 | +- All phases (command, address, dummy, data) use 4 lines (quad) |
| 120 | +- 6 wait cycles = 24 dummy bits (`DUMMY_LEN_VALUE_24`) |
| 121 | +- Max 133 MHz |
| 122 | +
|
| 123 | +**Write — Quad Write (0x38):** |
| 124 | +
|
| 125 | +``` |
| 126 | +[CMD 0x38: 2 clk] [24-bit ADDR: 6 clk] [DATA...] |
| 127 | + quad quad quad |
| 128 | +``` |
| 129 | +
|
| 130 | +- No dummy cycles |
| 131 | +- Max 133 MHz |
| 132 | +
|
| 133 | +**Timing (`set_psram_timing`):** |
| 134 | +
|
| 135 | +Clock divider, MAX_SELECT, and MIN_DESELECT are computed dynamically from |
| 136 | +`clk_sys`. Call `set_psram_timing()` again after changing the system clock |
| 137 | +(e.g. overclocking). |
| 138 | +
|
| 139 | +| Parameter | Value | Description | |
| 140 | +|---|---|---| |
| 141 | +| CLKDIV | ceil(sys_clk / 109 MHz) | Limit SCK to ≤ 109 MHz | |
| 142 | +| MAX_SELECT | tCEM / (64 × sys_clk period) | Max CS# low time (8 µs) | |
| 143 | +| MIN_DESELECT | ceil(tCPH / sys_clk period) | Min CS# high time (50 ns) | |
| 144 | +| PAGEBREAK | 1024 bytes | Break bursts at 1 kB page boundary | |
| 145 | +| RXDELAY | 1 | Read data sample delay (½ sys_clk cycle) | |
| 146 | +
|
| 147 | +### Step 5: Enable XIP writes |
| 148 | +
|
| 149 | +```c |
| 150 | +xip_ctrl_hw->ctrl |= XIP_CTRL_WRITABLE_M1_BITS; |
| 151 | +``` |
| 152 | + |
| 153 | +XIP memory window 1 is **read-only by default** (RP2350 datasheet §4.4.5, |
| 154 | +XIP_CTRL.WRITABLE_M1). Without this bit set, writes to PSRAM are silently |
| 155 | +dropped and the XIP cache may return stale data. |
| 156 | + |
| 157 | +## Permanent configuration via OTP |
| 158 | + |
| 159 | +As an alternative to the runtime API, OTP can be programmed so the bootrom |
| 160 | +automatically configures CS1 on every boot. **OTP writes are irreversible.** |
| 161 | + |
| 162 | +| OTP field | Value | Description | |
| 163 | +|---|---|---| |
| 164 | +| `FLASH_DEVINFO.CS1_GPIO` | 0 | GPIO 0 | |
| 165 | +| `FLASH_DEVINFO.CS1_SIZE` | 0xB | 8 MB | |
| 166 | +| `FLASH_DEVINFO.CS0_SIZE` | 0xC | 16 MB (Flash) | |
| 167 | +| `FLASH_DEVINFO.D8H_ERASE_SUPPORTED` | 1 | 64 KB block erase supported | |
| 168 | +| `BOOT_FLAGS0.FLASH_DEVINFO_ENABLE` | 1 | Enable FLASH_DEVINFO | |
| 169 | + |
| 170 | +picotool commands: |
| 171 | + |
| 172 | +```sh |
| 173 | +picotool otp set FLASH_DEVINFO 0xbc80 |
| 174 | +picotool otp set BOOT_FLAGS0 0x20 |
| 175 | +``` |
| 176 | + |
| 177 | +## References |
| 178 | + |
| 179 | +- [APS6404L-3SQR datasheet (AP Memory)](https://www.apmemory.com/en/downloadFiles/032411212009597427) |
| 180 | +- [RP2350 datasheet §4.4 "External flash and PSRAM (XIP)"](https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf) |
| 181 | +- [pico-sdk issue #2205 — runtime CS1 configuration without OTP](https://github.com/raspberrypi/pico-sdk/issues/2205) |
| 182 | +- [SparkFun sparkfun-pico library (sfe_psram.c)](https://github.com/sparkfun/sparkfun-pico) |
0 commit comments