// ============================================================================
// lpddr_dump.c — Ch319 Brick 3 — HPS reads FPGA-private LPDDR4B back THROUGH
// THE HPS BRIDGE (never /dev/mem of the framebuffer itself).
//
// mmaps ONLY the PS2 HPS-bridge register window (the same window ps2_status.sh
// uses), then drives the LPDDR4B read-probe one 32-bit word at a time:
//   write LPDDR_RDADDR (0x03C) = byte addr   -> sets address + triggers a read
//   poll  LPDDR_STATUS (0x02C) bit3 (rd_pending) until 0
//   read  LPDDR_RDATA  (0x03C)               -> the 32-bit word
//
// Output:
//   default  : raw little-endian bytes to stdout  (pipe to md5sum / save .bin)
//   --ppm W H: decode PSMCT16 (RGB5A1) -> binary PPM (P6) on stdout
//
// Disarm the writer first (the FB must be static while dumping):
//   busybox devmem 0x40000018 w 0x2
//
// Build on the HPS:  gcc -O2 -o lpddr_dump lpddr_dump.c
// Usage:
//   sudo ./lpddr_dump 0 8192 > fb.bin ; md5sum fb.bin   # acceptance (expect 3b12baff...)
//   sudo ./lpddr_dump --ppm 64 64 0 > fb.ppm            # screen-dump (64x64 PSMCT16)
// Env: PS2_BRIDGE_BASE (default 0x40000000).
// ============================================================================
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#define OFF_LPDDR_STATUS  0x02C   // bit3 = rd_pending
#define OFF_LPDDR_RDPORT  0x03C   // write = addr+trigger, read = data
#define MAP_SPAN          0x1000

static volatile uint32_t *g_reg;

static uint32_t rd_word(uint32_t byte_addr) {
    long spin = 0;
    g_reg[OFF_LPDDR_RDPORT/4] = byte_addr;           // set addr + trigger read
    while (g_reg[OFF_LPDDR_STATUS/4] & 0x8) {         // wait rd_pending -> 0
        if (++spin > 100000000L) {
            fprintf(stderr, "lpddr_dump: TIMEOUT waiting for read @0x%x\n", byte_addr);
            exit(2);
        }
    }
    return g_reg[OFF_LPDDR_RDPORT/4];
}

int main(int argc, char **argv) {
    const char *base_env = getenv("PS2_BRIDGE_BASE");
    unsigned long bridge_base = base_env ? strtoul(base_env, NULL, 0) : 0x40000000UL;

    int ppm = 0, ai = 1, w = 0, h = 0;
    if (argc > ai && strcmp(argv[ai], "--ppm") == 0) {
        ppm = 1; ai++;
        if (argc < ai + 3) { fprintf(stderr, "usage: %s --ppm W H START\n", argv[0]); return 1; }
        w = atoi(argv[ai++]); h = atoi(argv[ai++]);
    }
    if (argc <= ai) { fprintf(stderr, "usage: %s [--ppm W H] START [LEN]\n", argv[0]); return 1; }
    uint32_t start = (uint32_t)strtoul(argv[ai++], NULL, 0);
    uint32_t len   = ppm ? (uint32_t)(w * h * 2) : (argc > ai ? (uint32_t)strtoul(argv[ai], NULL, 0) : 8192);

    int fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (fd < 0) { perror("/dev/mem"); return 1; }
    void *map = mmap(NULL, MAP_SPAN, PROT_READ | PROT_WRITE, MAP_SHARED, fd, bridge_base);
    if (map == MAP_FAILED) { perror("mmap bridge"); return 1; }
    g_reg = (volatile uint32_t *)map;

    if (ppm) printf("P6\n%d %d\n255\n", w, h);

    // Read in 32-bit words; LEN is byte count (word-aligned up).
    for (uint32_t a = 0; a < len; a += 4) {
        uint32_t word = rd_word(start + a);
        uint8_t b[4] = { word & 0xff, (word >> 8) & 0xff, (word >> 16) & 0xff, (word >> 24) & 0xff };
        if (!ppm) {
            fwrite(b, 1, 4, stdout);
        } else {
            // two PSMCT16 (RGB5A1) pixels per word, little-endian halfwords.
            for (int p = 0; p < 2; p++) {
                uint16_t px = b[p*2] | (b[p*2+1] << 8);
                uint8_t r = ((px >> 0)  & 0x1f) << 3;
                uint8_t g = ((px >> 5)  & 0x1f) << 3;
                uint8_t bl= ((px >> 10) & 0x1f) << 3;
                uint8_t rgb[3] = { r, g, bl };
                fwrite(rgb, 1, 3, stdout);
            }
        }
    }
    munmap(map, MAP_SPAN);
    close(fd);
    return 0;
}
