#!/bin/sh
# retroDE_ps2 LPDDR framebuffer write/readback test — Ch318 operator helper.
#
# Drives the runtime LPDDR test controls in `ps2_hps_bridge` and verifies the
# tile-flush writer reached real LPDDR. ONE bitstream (GS_TILE_PSMCT16FB_DEMO +
# GS_LPDDR_FB); all control is at runtime — no rebuild between stages.
#
# Same style/contract as ps2_status.sh: PS2 HPS-bridge base + busybox devmem
# (busybox avoids the devmem2 "Bus error" quirk on 0x?4-suffixed offsets — and
# LPDDR_BURSTS sits at 0x...34). See rtl/platform/ps2_hps_bridge.sv and
# docs/ch318-lpddr-fb-bringup.md.
#
# Usage (run from HPS Linux after loading the .core.rbf):
#   ./ps2_lpddr_test.sh            # read-only LPDDR status (safe; no arming)
#   ./ps2_lpddr_test.sh --canary   # arm canary (1 line), re-render, prove via counters
#   ./ps2_lpddr_test.sh --full     # arm full frame, re-render, prove via counters
#   ./ps2_lpddr_test.sh --disarm   # write LPDDR_CTRL = 0x2 (disarmed, canary)
#
# PROOF METHOD = bridge counters, NOT /dev/mem. The Ch318 writer targets the HPS
# LPDDR (f2sdram) at 0x80000000, which is a firmware-RESERVED region: reading it
# with `dd /dev/mem` HARD-CRASHES the fabric (needs a power cycle). So this script
# NEVER touches /dev/mem. It proves the write reached LPDDR by reading LPDDR_BYTES/
# LPDDR_BURSTS/LPDDR_STATUS over the HPS bridge (safe register reads). Byte-level
# CONTENT verification needs the Ch318b bridge-register readback path (ported from
# ao486 lpddr4b_loader.sv) — until that lands, content is not checked here.
#
# ONE-SHOT FIX: the EE bootlet renders once at boot (before you can arm), then
# halts -> BYTES stays 0. So --canary/--full arm FIRST, then pulse the core reset
# (CORE_CTRL[0]) to re-run the bootlet and flush a frame WHILE ARMED.
#
# Defaults are SAFE: the bitstream boots disarmed; this script only writes when
# you pass --canary/--full, and always leaves the writer disarmed on exit.

set -u

BASE="${PS2_BRIDGE_BASE:-0x40000000}"
DEVMEM="${DEVMEM:-busybox devmem}"
MODE="${1:-status}"

# Register offsets (see ps2_hps_bridge.sv).
OFF_CORE_CTRL=0x010       # RW [0]=core reset (pulse 1->0 re-runs the EE bootlet)
OFF_LPDDR_CTRL=0x018      # RW [0]=arm [1]=canary  (reset 0x2)
OFF_LPDDR_FB_BASE=0x01C   # RW LPDDR byte base     (reset 0x80000000)
OFF_LPDDR_STATUS=0x02C    # R  [0]=idle [1]=bresp_err [2]=fifo_ovf
OFF_LPDDR_BYTES=0x030     # R  total bytes written
OFF_LPDDR_BURSTS=0x034    # R  total 32-byte bursts
OFF_LPDDR_BRESP_ERRS=0x038 # R count of bursts with non-OKAY response (1=reset-race phantom; 256=all refused)

# Expected byte counts (canary = 1 top line = 32 B; full = 64x64 PSMCT16 = 8 KiB).
EXP_CANARY_BYTES=32
EXP_FULL_BYTES=8192

read_reg()  { $DEVMEM "$(printf '0x%08x' $(( BASE + $1 )) )" w; }
write_reg() { $DEVMEM "$(printf '0x%08x' $(( BASE + $1 )) )" w "$2"; }
bit_set()   { if [ $(( ($1 >> $2) & 1 )) -eq 1 ]; then echo 1; else echo 0; fi; }

show_status() {
    local ctrl base st by bu
    ctrl=$(read_reg $OFF_LPDDR_CTRL); base=$(read_reg $OFF_LPDDR_FB_BASE)
    st=$(read_reg $OFF_LPDDR_STATUS); by=$(read_reg $OFF_LPDDR_BYTES); bu=$(read_reg $OFF_LPDDR_BURSTS)
    printf "LPDDR writer status\n"
    printf "  LPDDR_CTRL   : %s  (arm=%d canary=%d)\n" "$ctrl" "$(bit_set $((ctrl)) 0)" "$(bit_set $((ctrl)) 1)"
    printf "  LPDDR_FB_BASE: %s\n" "$base"
    printf "  LPDDR_STATUS : %s  (idle=%d bresp_err=%d fifo_ovf=%d)\n" \
        "$st" "$(bit_set $((st)) 0)" "$(bit_set $((st)) 1)" "$(bit_set $((st)) 2)"
    printf "  LPDDR_BYTES  : %s\n  LPDDR_BURSTS : %s\n" "$by" "$bu"
    printf "  LPDDR_BRESP_ERRS: %s\n" "$(read_reg $OFF_LPDDR_BRESP_ERRS)"
}

err_bits_clear() {  # 1 if bresp_err and fifo_ovf both 0
    local st=$(( $(read_reg $OFF_LPDDR_STATUS) ))
    [ "$(bit_set $st 1)" = "0" ] && [ "$(bit_set $st 2)" = "0" ]
}

# Re-run the EE bootlet so it renders a frame WHILE the writer is armed.
# (The bootlet is one-shot; it renders once at boot, before you can arm.)
rerender_pulse() {
    write_reg $OFF_CORE_CTRL 0x1   # assert core reset
    sleep 1
    write_reg $OFF_CORE_CTRL 0x0   # release -> bootlet re-runs, flushes a frame
    sleep 3                         # ~2 s DMAC-drain render cadence + margin
}

# Arm, re-render, and prove the write reached LPDDR via the bridge counters.
# $1 = LPDDR_CTRL arm value (0x3 canary / 0x1 full), $2 = expected byte count, $3 = label
prove_via_counters() {
    local armval=$1 expbytes=$2 label=$3 by bu
    write_reg $OFF_LPDDR_CTRL "$armval"   # arm
    rerender_pulse                         # render a frame while armed
    by=$(( $(read_reg $OFF_LPDDR_BYTES) ))
    bu=$(( $(read_reg $OFF_LPDDR_BURSTS) ))
    be=$(( $(read_reg $OFF_LPDDR_BRESP_ERRS) ))
    write_reg $OFF_LPDDR_CTRL 0x2          # DISARM
    printf "after re-render: LPDDR_BYTES=%d (expect %d)  LPDDR_BURSTS=%d  BRESP_ERRS=%d\n" "$by" "$expbytes" "$bu" "$be"
    if [ "$by" -ge "$expbytes" ] && err_bits_clear; then
        printf "%s: PASS (fabric delivered %d B to LPDDR; no AXI/FIFO errors)\n" "$label" "$by"
        return 0
    else
        printf "%s: FAIL (BYTES=%d < %d, or error bits set)\n" "$label" "$by" "$expbytes"
        show_status; return 1
    fi
}

case "$MODE" in
  status)
    show_status
    ;;

  --canary)
    printf "== LPDDR CANARY (32-byte top-line write, counter proof) ==\n"
    printf "defaults: LPDDR_CTRL=%s (expect 0x00000002)  LPDDR_FB_BASE=%s (expect 0x80000000)\n" \
        "$(read_reg $OFF_LPDDR_CTRL)" "$(read_reg $OFF_LPDDR_FB_BASE)"
    prove_via_counters 0x3 "$EXP_CANARY_BYTES" CANARY; exit $?
    ;;

  --full)
    printf "== LPDDR FULL FRAME (%d B, counter proof) ==\n" "$EXP_FULL_BYTES"
    prove_via_counters 0x1 "$EXP_FULL_BYTES" FULL; exit $?
    ;;

  --disarm)
    write_reg $OFF_LPDDR_CTRL 0x2
    printf "disarmed (LPDDR_CTRL=0x2)\n"
    ;;

  *)
    printf "usage: %s [--canary|--full|--disarm]   (no arg = status)\n" "$0"; exit 2
    ;;
esac
