Previous commit committed sibling claims without verifying against
the TRM bit tables. Verification fails for d328:
Sibling: +0x110 = CAL_RD_VWML0 (from TRM §2.4.3).
Blob: writes 0xF000F000 to that offset.
TRM: CAL_RD_VWML0 is READ-ONLY, bits[9:0]=rd_vwml0 code, [25:16]=rd_vwml1.
Writing is a no-op.
Root cause of sibling's error: conflated 'DDRPHY_OPB + offset' with
d328's 'DDRPHY_OPB + 0x8000 + offset'. The +0x8000 sub-block is NOT
documented in the TRM; offsets 0x110/0x118/0x120/0x154/0x160/0x184
WITHIN that sub-block mean something different from CAL_RD_VWML0 etc.
Kept the TRM-verified names I DID check:
- DDRCTL_DFISTAT @ +0x10514 (site 3)
- DDRCTL_STAT @ +0x10014 (sites 2,4,5,7)
- DDRPHY_SCHD_TRAIN_CON0 @ +0xa24 — bit layout verified directly
Retracted names for d328's +0x8XXX accesses; restoring the PHY_CTL_110
etc. RE-guess labels as the safe fallback. True names remain unknown
until we get hardware-trace data or the Synopsys DWC PUB databook.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9.6 KiB
Poll-site → register map (RK3588 DDR v1.19)
Each of the 16 timeout-less poll sites in the v1.19 stock conservative
blob, decoded against the RK3588 TRM Part 2 (Ch. 2, DMC) where
possible. Sites without TRM coverage are Synopsys DWC PUB registers —
not republished by Rockchip; names ending in (RE) are our educated
guesses from the code context.
Site index comes from patch_timeouts_v3.py (ascending-offset order
after find_poll_loops()).
Early cluster (sites 0–7): 0x07b78..0x07f08
| # | branch @ | body | load | addr (symbolic) | register | src |
|---|---|---|---|---|---|---|
| 0 | 0x07b78 | 2 | ldr w1, [x0+0x114] on x0=PHY+0x20000 |
PHY + 0x20114 | PHY_TRAIN_INTERLOCK_114 (RE) |
— |
| 1 | 0x07ba4 | 2 | ldr w1, [x26+0xb88] where x26=PHY+0x10000 |
PHY + 0x10b88 | PHY_SHADOW_BB8 (RE) |
— |
| 2 | 0x07c8c | 3 | ldr w0, [x1+0x14] where x1=DDRCTL+0x10000 |
DDRCTL + 0x10014 | DDRCTL_PWRCTL? (TBD — 0x14 offset in uMCTL2 is typically PWRCTL or STAT) |
TRM (partial) |
| 3 | 0x07ca8 | 2 | ldr w1, [x0+0x514] where x0=DDRCTL+0x10000 |
DDRCTL + 0x10514 | DDRCTL_DFISTAT dfi_init_complete |
TRM Part 2 Ch.2 |
| 4 | 0x07cd4 | 3 | ldr w0, [x1+0x14] same pattern as site 2 |
DDRCTL + 0x10014 | same as #2 | TRM (partial) |
| 5 | 0x07ce8 | 3 | same +0x14 load, different mask | DDRCTL + 0x10014 | same as #2 | TRM (partial) |
| 6 | 0x07d0c | 3 | ldr w0, [x26+0xb88] where x26=PHY+0x10000 |
PHY + 0x10b88 | PHY_SHADOW_BB8 (RE) — same reg as site 1, different mask |
— |
| 7 | 0x07f08 | 3 | ldr w1, [x0+0x14] same DDRCTL family |
DDRCTL + 0x10014 | same as #2 | TRM (partial) |
Mid cluster (sites 8–10): 0x09124..0x0aaf8
| # | branch @ | body | register | src |
|---|---|---|---|---|
| 8 | 0x09124 | 3 | DDRCTL + (via x27) — needs further context trace | — |
| 9 | 0x0aa84 | 3 | DDRCTL + (via x24) — ditto | — |
| 10 | 0x0aaf8 | 3 | abs 0xff000024 per decoder — SRAM mirror of a GRF? non-obvious |
— |
Site 10 is unusual — absolute 0xff000024 is in the SRAM_BOOT
region, not a controller or PHY block. Possibly a BL2 handoff word
the blob waits on before continuing. Worth its own trace.
Late cluster (sites 11–15): 0x0d154..0x0d378
| # | branch @ | body | register | src |
|---|---|---|---|---|
| 11 | 0x0d154 | 3 | ldr w5, [x0+0x14] where x0=PHY+0x10000 → PHY + 0x10014, test &0x7 == 1 |
PHY_STATE_014 (RE) — wait for state 1 |
| 12 | 0x0d340 | 2 | ldr w1, [x0+0x118] where x0=PHY+0x8000 → PHY + 0x8118 |
PHY_STAT_A_118 (RE) — train_phy_block |
| 13 | 0x0d34c | 2 | ldr w1, [x0+0x120] → PHY + 0x8120 |
PHY_STAT_B_120 (RE) |
| 14 | 0x0d364 | 2 | ldr w1, [x0+0x184] → PHY + 0x8184 |
PHY_HANDSHAKE_184 (RE, ack assert) |
| 15 | 0x0d378 | 2 | ldr w1, [x0+0x184] → PHY + 0x8184 |
PHY_HANDSHAKE_184 (RE, ack deassert) |
Summary by coverage
- TRM-documented (vendor-canonical names): 1 site (site 3 — DDRCTL_DFISTAT).
- TRM-documented family (0x14 offset in uMCTL2 space, exact register TBD): 4 sites (2, 4, 5, 7).
- DWC PUB / Innosilicon PHY — undocumented, RE names only: 11 sites.
Known tensions
-
Site 3 tests DFISTAT bits[2:1] (mask 0x6), not bit[0]. Generic uMCTL2 DFISTAT has only bit[0] defined (
dfi_init_complete); bits 1+ are reserved. RK3588's blob treating bits[2:1] as meaningful suggests Rockchip extended the DFISTAT register with vendor-specific bits. Worth checking TRM bit tables for DFISTAT directly. -
Sites 2/4/5/7 all poll DDRCTL + 0x10014 with different bit masks (&0x7==1, &0x7==3, &0x30==0x20, &0x7==3). At offset +0x14 in uMCTL2 is
STAT(Operating Mode Status Register) per generic DWC docs —operating_mode[2:0]field encodes: 0=Init, 1=Normal, 2=Power-down, 3=Self-refresh, 5=Deep-power-down, 6=Deep-power-down init. RK3588 probably follows this convention — these polls wait for the controller to enter specific operating modes. -
Site 10 at absolute
0xff000024is suspect. That region is SRAM_BOOT in our emulator map. Possibly a BL2 handshake word. If so, patching this site to "bounded retry" is safe — worst case we skip one BL2 handoff. Should flag this separately.
Action items
- Extract DFISTAT bit-field description from TRM Part 2 to confirm/deny the RK3588 vendor extension hypothesis for site 3.
- Extract STAT (+0x14) bit-field description from TRM to confirm/deny the "operating_mode" mapping for sites 2/4/5/7.
- Special-case site 10 in the bisection plan — it's not a normal PHY poll and may need different treatment.
Update 2026-04-15 evening — TRM §2.4.3 mined
Sibling research went back into the TRM and found §2.4.3 Registers
Summary For DDRPHY which I'd missed. That section names almost every
low-offset poll we'd labelled (RE):
| old guess | TRM actual | confidence |
|---|---|---|
PHY + 0x110 (RE) — F000F000 trigger |
DDRPHY_CAL_RD_VWML0 (Read Valid Window Margin Left Code 0) |
TRM HIGH |
PHY + 0x120 (RE) — step-complete bit |
DDRPHY_CAL_RD_VWMR0 (Read Valid Window Margin Right Code 0) |
TRM HIGH |
PHY + 0x160 (RE) — CFG_B 0x30003 |
DDRPHY_CAL_CON5 (Calibration Control 5: wrtrn_cyc_mode / wrtrn_cyc_en / wrtrn_cyc_th) |
TRM HIGH |
| PHY + 0x684 "CalBusy" | DDRPHY_PRBS_CON0 — PRBS training control (our CalBusy guess was wrong) |
TRM HIGH |
| PHY + 0xa24 "DFI ready" | DDRPHY_SCHD_TRAIN_CON0 — master training scheduler, full bit layout documented |
TRM extremely high |
| PHY + 0xb88 "shadow" | DDRPHY_DQSDUTY_CON2 — DQS rise-duty monitor (DCM/DCA debug) |
TRM HIGH |
| PHY + 0x118, 0x154, 0x184 | TRM gaps (reserved) — still RE. Likely per-slice shadow / handshake FSMs | RE |
Big picture
- SCHD_TRAIN_CON0 @ +0xa24 is the master controller. Writing to
it with the right bit combination selects a training type (CBT,
WrLvl, GT, Rd, Wr), enables per-rank bits, and sets the
phy_train_enbit. Polling[1] phy_train_donetells you when it's finished. Four of our poll sites (8, 9, possibly 11) are almost certainly polling this bit. - The
0x30003/0x30000pattern in d328 writes toCAL_CON5, notSCHD_TRAIN_CON0— so it configures write-training cycle mode, not the master scheduler. Initial sibling hypothesis of "DVFS gate training" was incorrect on second reading.
Training sequence now mapped to poll sites
Per TRM §2.6.x "Training Procedure":
- Power ramp / PLL lock / clock config
- DFI init:
DDRCTL_DFIMISC.dfi_init_start=1, pollDDRCTL_DFISTAT.dfi_init_complete— site 3 is here - ZQ calibration:
ZQ_CON0.zq_manual_str, pollZQ_CON1.zq_done - MDLL lock
- Scheduler enable (
LP_CON0.ctrl_scheduler_en) - Training loop, all via
SCHD_TRAIN_CON0:- CBT (Command Bus Training)
- Write Leveling
- Gate Training
- Read DQ Training → results land in
CAL_RD_VWMC0/VWML0/VWMR0 - Write DQ Training → results in
CAL_WR_VWMC0/VWML0/VWMR0 - PRBS Training (LPDDR5 high-speed)
- DQS Duty-cycle monitoring
Our 16 poll sites are scattered across steps 2, 5 (scheduler-en state), and the six training sub-steps in step 6.
Open items
- +0x118 / +0x154 / +0x184 remain TRM-reserved — training engine private FSMs. No way to name these from docs; dynamic tracing on real hardware (with UART or JTAG) is the remaining path.
- CSDN LPDDR5 series (DDR Study blog) turned out to cover JEDEC-layer protocol only; no register-level help. Useful background for training phase names but not for our RE.
Correction 2026-04-15 late evening — sibling over-trusted
User pointed out I was applying sibling names without verification ("weights from browsing Instagram"). Going back to verify each, found the d328 register naming was wrong:
- Sibling said
+0x110in d328 =DDRPHY_CAL_RD_VWML0(per TRM). - But the blob writes
0xF000F000to that offset, and TRM listsCAL_RD_VWML0as READ-ONLY with bits[9:0] carrying a training-result code. Writes are no-ops; 0xF000F000 as a legit VWML value makes no physical sense either. - Reason sibling was confused: d328's effective address is
ctx->b8 + 0x8000 + offset, notDDRPHY_OPB + offset. TRM's CAL_RD_VWML0 is atDDRPHY_OPB + 0x0110, notDDRPHY_OPB + 0x8110. - What's at
DDRPHY_OPB + 0x8000..0x8FFF? TRM does not say. Only chapters elsewhere in the TRM have 0x8xxx-offset registers (DPTX PHY etc. — different peripherals, not DDR).
What d328 really does: we still don't know the semantic register names for its +0x8110, +0x8118, +0x8120, +0x8154, +0x8160, +0x8184 accesses. The 0xF000F000 / 0x30003 / 0x30000 values suggest a custom trigger/config/ack protocol of the +0x8000 sub-block, which is likely a per-rank-shadow or per-slice mirror of DDRPHY's Master block — but without docs, naming it would just be re-guessing.
Still verified (hold): the early-cluster + MID-cluster offsets
that access ctx->b8 + 0x10000 + small (i.e. the DDRCTL register
space):
- Site 3 @ 0x7ca8 →
DDRCTL + 0x10514= DDRCTL_DFISTAT ✓ TRM-verified - Sites 2, 4, 5, 7 →
DDRCTL + 0x10014= DDRCTL_STAT.operating_mode ✓ TRM-verified
And at DDRPHY OPB + small (NOT the 0x8000 sub-block):
DDRPHY + 0xa24=SCHD_TRAIN_CON0— I verified the full 32-bit layout directly from TRM §2.4.3 before committing, so this one stands.
Wrong/unsafe (retracted): the +0x8XXX offsets in d328. Their
sibling-proposed names (CAL_RD_VWML0 etc.) are TRM-registered at
+0x0XXX, not +0x8XXX, and the blob's write access patterns
contradict the read-only TRM bit definitions.