// retroDE_ps2 — ps2_lpddr_probe_test (Ch352 board bring-up: isolate the HPS->LPDDR write/read path) // // Codex debug step 1: prove the write-probe + read-probe ALONE, with a tiny known pattern, BEFORE the texture // cache or renderer is involved. Writes N known words to a scratch LPDDR region, reads them back, and reports // per-word: did the auto-increment pointer (0x04C) advance? did wr_busy (0x054 bit1) assert? does the readback // match? This pinpoints whether "word 0 ok, rest 0" is a PACING race, an AUTO-INCREMENT bug, or a read-probe // latency issue — none of which the cache/render path can disambiguate. // // Build on the board: gcc -O2 -o ps2_lpddr_probe_test ps2_lpddr_probe_test.c // Run: sudo ./ps2_lpddr_probe_test [N=64] [--base 0x40000000] [--lpddr 0x100000] // // Pattern: word i = 0x11110000 + i (non-trivial, low bytes change per word so a stuck/striped path is obvious). // Scratch LPDDR base defaults to 0x100000 (1 MiB) — clear of the texture region at 0x200000. #include #include #include #include #include #include #include #include #define OFF_LPDDR_STATUS 0x02C // R: [3] rd_pending #define OFF_LPDDR_RDADDR 0x03C // W: read byte addr + trigger ; R: result word #define OFF_LPDDR_WRADDR 0x04C // RW: write byte addr (auto-inc +4 per WRDATA write) #define OFF_LPDDR_WRDATA 0x050 // W: data word -> one 32-bit LPDDR write + addr+=4 #define OFF_TEX_FILL_CTRL 0x054 // R: [0]fill_done [1]wr_busy #define OFF_LPDDR_WR_ERRS 0x06C // R: write-probe non-OKAY (BRESP) responses #define OFF_LPDDR_STATUS2 0x02C // R: [0]wr_idle [3]rd_pending [4]scan_valid [5]scan_err #define OFF_TEX_FILL_BEATS 0x058 // R: cache-fill beats (emif_clk READ liveness) #define OFF_TEX_FILL_BYTES 0x05C // R: cache-fill bytes #define OFF_TEX_RD_ERRS 0x068 // R: cache-fill non-OKAY read responses #define WR_BUSY_BIT 0x2 #define WR_PENDING 0x4 // 0x054 bit2 — Ch352 STABLE write-done flag (poll this, not transient wr_busy) #define RD_PENDING 0x8 #define FILL_DONE 0x1 typedef struct { volatile uint8_t *base; } br_t; static void w(br_t *b,int o,uint32_t v){ *(volatile uint32_t*)(b->base+o)=v; } static uint32_t r(br_t *b,int o){ return *(volatile uint32_t*)(b->base+o); } int main(int argc,char**argv){ unsigned long base=0x40000000UL, lpddr=0x00100000UL; int N=64; char *e=getenv("PS2_BRIDGE_BASE"); if(e) base=strtoul(e,NULL,0); for(int i=1;i4096) N=64; printf("[probe] N=%d bridge=0x%lx lpddr-scratch=0x%lx pattern word[i]=0x11110000+i\n", N, base, lpddr); int fd=open("/dev/mem",O_RDWR|O_SYNC); if(fd<0){ fprintf(stderr,"open /dev/mem (root?): %s\n",strerror(errno)); return 1; } void*map=mmap(NULL,0x1000,PROT_READ|PROT_WRITE,MAP_SHARED,fd,(off_t)base); if(map==MAP_FAILED){ fprintf(stderr,"mmap: %s\n",strerror(errno)); return 1; } br_t b={(volatile uint8_t*)map}; // ---- WRITE: set WRADDR once, then stream; per word verify pointer advance + observe busy high->low ---- uint32_t err0 = r(&b,OFF_LPDDR_WR_ERRS); w(&b,OFF_LPDDR_WRADDR,(uint32_t)lpddr); int ptr_bad=0, pending_stuck=0; for(int i=0;i=2000000) pending_stuck++; uint32_t ptr=r(&b,OFF_LPDDR_WRADDR), exp=(uint32_t)lpddr+(uint32_t)(i+1)*4; if(ptr!=exp){ if(ptr_bad<6) printf(" PTR after word %d: 0x%08x exp 0x%08x\n",i,ptr,exp); ptr_bad++; } } uint32_t err1=r(&b,OFF_LPDDR_WR_ERRS); printf("[probe] wrote %d words. ptr-mismatches=%d write_pending_stuck=%d wr_bresp_errs: %u->%u\n", N, ptr_bad, pending_stuck, err0, err1); // ---- READBACK: per word set RDADDR, poll rd_pending clear, read result. Count rd_pending TIMEOUTS // explicitly (Codex): if rd_pending never clears, the value read below is STALE — not a real LPDDR read. ---- int rd_bad=0, rd_timeouts=0; for(int i=0;i=1000000) rd_timeouts++; // rd_pending stuck -> the read is stale uint32_t got=r(&b,OFF_LPDDR_RDADDR); if(got!=exp){ if(rd_bad<8) printf(" RD word %d @0x%08x: got 0x%08x exp 0x%08x\n",i,addr,got,exp); rd_bad++; } } printf("[probe] readback mismatches=%d/%d rd_pending_timeouts=%d%s\n", rd_bad, N, rd_timeouts, rd_timeouts? " (reads were STALE, not real LPDDR reads)":""); // ---- EMIF READ-domain liveness: arm the texture-cache fill (issues emif_clk reads from LPDDR and // counts beats), independent of the write path. This disambiguates the failure: // beats advance -> emif READ domain is ALIVE => the bug is WRITE-specific (probe/pulse wiring). // beats stay 0 -> the whole emif_clk domain is DEAD (EMIF didn't calibrate / clock / reset), // which by itself explains busy-never-high AND the all-zero readback. // Snapshot BEFORE arming (Codex): the fill counters reset only after the new toggle reaches the EMIF // domain, so a stale fill_done=1 / beats from an earlier fill would be a false positive. Require, in order: // fill_done seen LOW after arming (the NEW fill is in flight), then fill_done HIGH (it completed), with // final beats=8192 / bytes=262144 / rd_errs=0. uint32_t st0 = r(&b,OFF_LPDDR_STATUS2); uint32_t fd0 = (r(&b,OFF_TEX_FILL_CTRL)&FILL_DONE)?1:0; uint32_t beats0 = r(&b,OFF_TEX_FILL_BEATS); w(&b,OFF_TEX_FILL_CTRL,FILL_DONE); // arm: toggle fill_start int saw_low=0; for(int g=0;g<8000000;g++){ if(!(r(&b,OFF_TEX_FILL_CTRL)&FILL_DONE)){saw_low=1;break;} } int fdone=0; for(int g=0;g<8000000;g++){ if( r(&b,OFF_TEX_FILL_CTRL)&FILL_DONE){fdone=1;break;} } uint32_t beats=r(&b,OFF_TEX_FILL_BEATS), bytes=r(&b,OFF_TEX_FILL_BYTES), rderr=r(&b,OFF_TEX_RD_ERRS); int fill_ok = saw_low && fdone && beats==8192 && bytes==262144 && rderr==0; printf("[probe] EMIF fill liveness: pre(status=0x%08x fill_done=%u beats=%u) -> armed: saw_done_low=%d final_done=%d\n", st0, fd0, beats0, saw_low, fdone); printf("[probe] final beats=%u (exp 8192) bytes=%u (exp 262144) rd_errs=%u (exp 0)\n", beats, bytes, rderr); if(fill_ok) printf("[probe] => emif READ domain ALIVE and well — failure is WRITE-specific.\n"); else if(beats||fdone) printf("[probe] => emif read PARTIALLY working (counts off) — investigate fill path / cal margin.\n"); else printf("[probe] => emif_clk domain appears DEAD (cal/clock/reset) — explains busy-never-high + zero readback.\n"); if(rd_bad==0 && ptr_bad==0) printf("[probe] write/read path clean.\n"); else printf("[probe] FAIL — write/read path broken; see liveness line above for which domain.\n"); munmap(map,0x1000); close(fd); return (rd_bad||ptr_bad)?1:0; }