====== RK3588 LPDDR5 overclock via ddrbin_tool.py ====== Recipe for pushing RK3588's LPDDR5 past the 2400 MHz shipped cap, without touching the training code. All the work happens in Rockchip's own parameter table, via Rockchip's own tool. Current target hardware: **CoolPi CM5 GenBook** ("ampere"), 32 GB LPDDR5. Ran to **3200 MHz (6400 MT/s, JEDEC LP5 max)** with 4 channels on 2026-04-23. ===== TL;DR ===== * Rockchip ships ''ddrbin_tool.py'' inside ''rkbin''. It edits the parameter table inside a DDR training blob in place: cold-boot frequency, DFS OPPs, UART config, pstore offsets, ECC/derate flags. * The v1.19 blob's PLL programmer computes dividers from the ''lp5_freq='' field. Set it to 2736 or 3200 and the blob trains there. * MegabitChip had noted "2736 dropped in v1.16". That's half-true: Rockchip deleted the 2736 **pre-built variants** from rkbin, but the **code path** is still live in v1.19. ddrbin_tool.py exposes it as a knob. * Community (SBCwiki, SkatterBencher #89) reaches 3200 MHz on Radxa Rock-5B+ / Rock-5T with SK Hynix modules using the same approach. ===== Results on ampere (2026-04-23) ===== |< 100% >| ^ lp5_freq ^ MT/s ^ peak BW ^ delta vs 2112 ^ status on ampere ^ | 2112 (stock conservative) | 4224 | 67.6 GB/s | baseline | known-good | | 2400 (stock aggressive) | 4800 | 76.8 GB/s | +13.6 % | stable post-CM5-reseat | | 2736 | 5472 | 87.5 GB/s | +29.5 % | cold-boot clean, memtester 11/17 patterns clean | | **3200 (JEDEC LP5 max)** | **6400** | **102.4 GB/s** | **+51.5 %** | cold-boot clean warm-ambient, memtester in progress | Idle thermals unchanged between rates (46–48 °C package). memtester at 2736 ran at 57–59 °C package — 3200 soak expected a few degrees warmer but fine. Thermal mod helping: 1-cent copper-plated coin laid on each LPDDR5 package with Arctic Silver 5, plus the Copperfield copper shim on the SoC package. Total invested: 2 cents plus a gram of paste. ===== Recipe ===== On the u-boot host (''boltzmann''): ==== 1. Verify baseline (optional) ==== cd ~/projects/AMPere/rkbin python3 tools/ddrbin_tool.py rk3588 -g /tmp/extract.txt \ bin/rk35/rk3588_ddr_lp4_2112MHz_lp5_2400MHz_v1.19.bin grep -E 'lp5_freq|lp5_f[0-9]' /tmp/extract.txt Reads out ''lp5_freq=2400'', ''lp5_f1_freq_mhz=534'', ''lp5_f2_freq_mhz=1320'', ''lp5_f3_freq_mhz=1968''. ==== 2. Build a param file ==== Start from the stock template, fill **only** the fields you want to change: cp ~/projects/AMPere/rkbin/tools/ddrbin_param.txt /tmp/ddr_param.txt sed -i 's/^lp5_freq=.*/lp5_freq=3200/' /tmp/ddr_param.txt Everything else blank = "keep the blob's default". ==== 3. Patch the blob ==== cp ~/projects/AMPere/rkbin/bin/rk35/rk3588_ddr_lp4_2112MHz_lp5_2400MHz_v1.19.bin \ ~/projects/AMPere/rkbin/bin/rk35/marfrit/rk3588_ddr_lp5_3200_v1.19_marfrit.bin cd ~/projects/AMPere/rkbin python3 tools/ddrbin_tool.py rk3588 /tmp/ddr_param.txt \ bin/rk35/marfrit/rk3588_ddr_lp5_3200_v1.19_marfrit.bin Same size (76704 B). Parameter table bytes + ''typ YY/MM/DD-HH:MM.SS,fwver'' stamp update. Code untouched. ==== 4. Verify with -g readback ==== python3 tools/ddrbin_tool.py rk3588 -g /tmp/verify.txt \ bin/rk35/marfrit/rk3588_ddr_lp5_3200_v1.19_marfrit.bin grep -E 'lp5_freq|lp5_f[0-9]' /tmp/verify.txt Confirm ''lp5_freq=3200''. Note the updated ''typ'' timestamp — useful signature later to confirm the right blob flashed. ==== 5. Build u-boot with the patched blob ==== cd ~/src/u-boot make O=build-tfa clean make O=build-tfa -j$(nproc) \ BL31=/home/mfritsche/projects/AMPere/trusted-firmware-a/build/rk3588/release/bl31/bl31.elf \ ROCKCHIP_TPL=/home/mfritsche/projects/AMPere/rkbin/bin/rk35/marfrit/rk3588_ddr_lp5_3200_v1.19_marfrit.bin ==== 6. Pad to 8 MB ==== binman produces ~1.66 MB. SPI chip is 8 MB; rest is 0xff: python3 -c " raw = open('build-tfa/u-boot-rockchip-spi.bin','rb').read() open('out-8mb.bin','wb').write(raw + b'\xff' * (8*1024*1024 - len(raw))) " ==== 7. Pre-flash sanity ==== * ''xxd -s 0x8000 -l 8'' → should read ''RKNS'' * ''xxd -s 0x60000 -l 8'' → should read ''d00d feed'' (FIT) * ''xxd -s 0x7ffff0 -l 16'' → should be all ''0xff'' * ''strings'' should show the updated DDR stamp and Collabora TF-A banner binman has silently truncated before (Bin campaign). Always verify structural markers before flashing. ==== 8. Flash from running ampere ==== # on ampere sudo flashcp --partition /tmp/out-8mb.bin /dev/mtd0 # post-flash verify sudo dd if=/dev/mtd0 bs=4096 2>/dev/null | sha256sum # should match sha256sum of your source image ''--partition'' writes only differing 4 KB blocks. Flash-wear-friendly and fast. ===== Rollback ===== Always dump the currently-working SPI as a flashcp target **before** pushing anything more aggressive: sudo dd if=/dev/mtd0 of=~/spi-backups/ampere-spi-$(date +%Y%m%d-%H%M).bin bs=4096 If a new image doesn't boot but leaves the shell reachable somehow, flashcp back. If it truly bricks, maskrom via meitner + rkdeveloptool remains the last line of defense. **Never push without a verified local backup.** On ampere the rollback chain currently is: * ''~/spi-backups/ampere-spi-rockhard-tfa-lp5-2736-VERIFIED-20260423-2229.bin'' — 2736-stable * ''~/spi-backups/ampere-spi-pre-rockhard-tfa-20260423-2138.bin'' — pre-TFA Bin-era ===== Performance implication ===== Peak BW scales linearly with DDR clock (4 × 32-bit channels × MT/s ÷ 8). Real-world gains: * **Streaming / bandwidth-bound** (GPU textures, NPU inference on big models, ffmpeg sw encode, kernel-level bulk copies): 20–40 %. The workloads where this actually matters. * **Compile / dev-loop** (kernel, Rust, Node, browsers): 10–15 %. Cache does most of the work; RAM gets hit on working-set spills. * **Latency-bound** (UI, small random access, IO-bound): ~0 %. LP5 CAS in nanoseconds is roughly flat across freqs. * **Allocator / fs cache refill** (under memory pressure, no-swap system): ~30 %. Costs: * DDR **power**: +25–35 % at full read/write load. Minutes off battery under heavy DRAM stress, not hours. * DDR **heat**: proportional to power. See thermal mod above. * **Bit-error margin**: silicon-lottery shaped. Rockchip capped at 2400 for yield reasons; individual silicon may or may not hold higher rates. Memtester + daily use is the only way to tell. ===== Why ddrbin_tool.py works ===== The DDR blob has a ''0x12345678'' magic header at ~''0x11B38'' in v1.19 marking the parameter table. Behind that is a versioned schema of field-name + offset + type, including ''lp5_freq'', ''lp4_freq'', ''lp4x_freq'', each DFS OPP (''lp5_f1_freq_mhz''..''lp5_f5_freq_mhz''), UART config, etc. The blob's training code reads these fields at runtime and derives DFI PLL dividers from them. Setting ''lp5_freq=3200'' doesn't need new code — the existing PLL programmer computes fbdiv + refdiv + postdiv to hit 3200 MHz. What Rockchip deleted at v1.16 was the **pre-built, field-validated variants** in ''bin/rk35/rk3588_ddr_lp4_*MHz_lp5_2736MHz_v1.03.bin'' — they stopped promising the rate, not computing it. ddrbin_tool.py parses the parameter table by chip + version, shows fields as named keys, lets you override, and writes back. ''-g'' extracts the current config; no arg = modify mode. ===== Known gotchas ===== * The ''-g'' option needs three positional args: ''rk3588 -g ''. Miss ''rk3588'' and it silently falls back to "others chip" and writes nothing useful. * The tool updates the ''typ'' timestamp on patch. If you're trying to keep byte-identical blobs for checksum reasons (you shouldn't), note the new timestamp. * binman **silently drops** the DDR blob out of the idbloader if the build's O= dir has a stale config. Always ''make O= clean'' before a re-patch-rebuild cycle. Verify with ''strings out-8mb.bin | grep "DDR .*fwver"'' — should show today's timestamp. * The community-facing ''hbiyik/rkddr'' tool is a TUI wrapper around the same tables. Pick whichever interface you prefer. ===== Coverage matrix ===== From ''rkbin/tools/ddrbin_tool_user_guide.txt'', RK3588 row: | uart info | ddr freq | ssmod | DDR 2T | sr pd | drv/odt/vref | dis train print | dis CBT | ddr2/3/4 lp2/3 vref | | V1.00 | V1.00 | X | V1.00 | V1.00 | V1.00 | X | X | X | "X" = not exposed. "V1.xx" = exposed from that blob version onward. Same tool handles RK3576, RK3566/3568, RK3528, RK3562. ===== See also ===== * [[rk3588_ddr|RK3588 DDR]] — MegabitChip (matching-decomp of v1.19 blob) * [[bin|Project Bin]] — u-boot + display + keyboard on GenBook * Sources: [[https://github.com/rockchip-linux/rkbin|rockchip-linux/rkbin]], [[https://github.com/hbiyik/rkddr|hbiyik/rkddr]], [[https://sbcwiki.com/news/articles/tune-your-rk3588/|SBCwiki Optimizing RK3588]], [[https://skatterbencher.com/2025/08/23/skatterbencher-89-orange-pi-5-max-overclocked-to-2650-mhz/|SkatterBencher #89]]