301fc08890
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>
184 lines
9.6 KiB
Markdown
184 lines
9.6 KiB
Markdown
# 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
|
||
|
||
1. **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.
|
||
|
||
2. **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.
|
||
|
||
3. **Site 10 at absolute `0xff000024`** is 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_en`
|
||
bit. Polling `[1] phy_train_done` tells you when it's finished. **Four
|
||
of our poll sites (8, 9, possibly 11) are almost certainly polling
|
||
this bit.**
|
||
- The `0x30003` / `0x30000` pattern in d328 writes to `CAL_CON5`, not
|
||
`SCHD_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":
|
||
|
||
1. Power ramp / PLL lock / clock config
|
||
2. DFI init: `DDRCTL_DFIMISC.dfi_init_start=1`, poll `DDRCTL_DFISTAT.dfi_init_complete` — **site 3 is here**
|
||
3. ZQ calibration: `ZQ_CON0.zq_manual_str`, poll `ZQ_CON1.zq_done`
|
||
4. MDLL lock
|
||
5. Scheduler enable (`LP_CON0.ctrl_scheduler_en`)
|
||
6. 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 `+0x110` in d328 = `DDRPHY_CAL_RD_VWML0` (per TRM).
|
||
- But the blob **writes** `0xF000F000` to that offset, and TRM lists
|
||
`CAL_RD_VWML0` as **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`**, not `DDRPHY_OPB + offset`. TRM's
|
||
CAL_RD_VWML0 is at `DDRPHY_OPB + 0x0110`, not
|
||
`DDRPHY_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.
|