Skip to content

Commit ff06c35

Browse files
harukasanclaude
andcommitted
Add PSRAM driver documentation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0cf1ca9 commit ff06c35

2 files changed

Lines changed: 189 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,18 @@ rake clean # remove build/
1717
rake distclean # remove build/ and PicoRuby build
1818
```
1919

20+
## Documentation
21+
22+
Design documents and implementation notes are in `doc/`:
23+
24+
- [doc/psram.md](doc/psram.md) — PSRAM driver (APS6404L, QMI CS1 initialization, XIP mapping)
25+
2026
## Code style
2127

2228
- Follow `.editorconfig` for indentation and whitespace rules
2329
- Use C11, K&R brace style
2430
- Keep HAL functions prefixed with `hal_` or `mrb_hal_`
31+
- Write documents in `doc/` in English
2532

2633
## Commit messages
2734

doc/psram.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
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

Comments
 (0)