// 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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#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;i<argc;i++){
        if(!strcmp(argv[i],"--base")&&i+1<argc) base=strtoul(argv[++i],NULL,0);
        else if(!strcmp(argv[i],"--lpddr")&&i+1<argc) lpddr=strtoul(argv[++i],NULL,0);
        else if(argv[i][0]!='-') N=atoi(argv[i]);
    }
    if(N<1||N>4096) 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<N;i++){
        uint32_t data=0x11110000u+(uint32_t)i;
        w(&b,OFF_LPDDR_WRDATA,data);
        // Wait for the STABLE write_pending flag (0x054 bit2) to clear = the write-probe completed THIS write.
        // Unlike the transient wr_busy level, this can't be missed by a slow Linux poll (Codex).
        int g=0; while((r(&b,OFF_TEX_FILL_CTRL)&WR_PENDING) && g<2000000) g++;
        if(g>=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<N;i++){
        uint32_t addr=(uint32_t)lpddr+(uint32_t)i*4, exp=0x11110000u+(uint32_t)i;
        w(&b,OFF_LPDDR_RDADDR,addr);
        int g=0; while((r(&b,OFF_LPDDR_STATUS)&RD_PENDING)&&g<1000000) g++;
        if(g>=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;
}
