feat(pwr): Add blanton_multiphase_read.sh to read back the SWB VRMs

Reads the controllers blanton_multiphase_margin.sh writes (native i2c,
SWB0 = i2c-13/15, SWB1 = i2c-14/16) and prints one row per chip in the
pu_show.sh layout - VOUT/VOUT2, IOUT/IOUT2, POUT (V x I), TEMP - with the
raw PMBus word in hex after every voltage and current, plus the total
power per board. PAGE is read back after each page and the page is re-read
if something else moved it. Not run on the bench yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0131orciRKrejchsuDk6UpXk
This commit is contained in:
2026-11-12 20:35:58 +08:00
co-authored by Claude Opus 5.5
parent 957eda3ab4
commit be24eebff2
@@ -0,0 +1,202 @@
#!/usr/bin/env bash
# =============================================================================
# blanton_multiphase_read.sh -- read back the SWB multiphase VRMs (V / I / P / T)
# =============================================================================
# Version History:
# V1.0.0 2026-11-04 First version. Reads the same controllers that
# blanton_multiphase_margin.sh writes (native i2c,
# SWB0 = i2c-13/15, SWB1 = i2c-14/16) and prints one row
# per chip in the pu_show.sh layout, with the raw PMBus
# word in hex after every voltage and current.
# =============================================================================
#
# Per chip, per PAGE (page 0 -> VOUT/IOUT, page 1 -> VOUT2/IOUT2):
# PAGE 0x00 i2cset <page>
# READ_VOUT 0x8B MP29816 : bits[11:0] x LSB from MFR_VOUT_SCALE_LOOP
# (0x29) bits[12:10], read from the chip
# MP2985B : bits[11:0] x 1 mV
# READ_IOUT 0x8C SLINEAR11
# READ_TEMP1 0x8D page 0 only. MP29816 SLINEAR11, MP2985B bits[10:0]
# two's complement degC
# POUT computed V x I (not READ_POUT) -- same as pu_show.sh
# Conversions are the ones verified in blanton_pwr_data.sh V1.0.0.
#
# The kernel / platform daemons may talk to the same chips and move PAGE
# between our page select and our read. After every page's reads the PAGE
# register is read back; if it moved, that page is re-read (MP_RETRY times)
# and printed NA if it never holds still. 0xFFFF is also treated as NA.
#
# Read only -- nothing but PAGE is written. PAGE is put back to 0 afterwards.
# ⚠️ Do not run while blanton_multiphase_margin.sh is writing: both select
# PAGE on the same chips and neither one knows about the other.
#
# Usage:
# ./blanton_multiphase_read.sh # both boards
# ./blanton_multiphase_read.sh -u 0 # SWB0 only
# ./blanton_multiphase_read.sh -n # print the i2c commands only
#
# Options:
# -u|--unit <0|1|all> which switch board (default: both)
# -d|--debug print every i2c command and the word it returned
# -n|--dry-run print the commands only, do not touch the DUT
# -h|--help help
# =============================================================================
MP_UNITS="${MP_UNITS:-0 1}"
MP_RETRY="${MP_RETRY:-3}"
MP_DRY_RUN=${MP_DRY_RUN:-0}
DEBUG_MODE=${DEBUG_MODE:-0}
MP_NA="NA"
# --- Chip map ----------------------------------------------------------------
# "name:part:bus_swb0:addr:pages" SWB1 = same addr on bus + 1 (13->14, 15->16)
# PU numbers are from PWR_Data.xlsx (blanton_pwr_data.sh PWR_RAILS).
MP_CHIPS=(
"PU2:MP29816:13:0x20:0"
"PU105:MP2985B:13:0x21:0 1"
"PU102:MP2985B:13:0x22:0 1"
"PU114:MP2985B:13:0x25:0 1"
"PU122:MP2985B:13:0x27:0 1"
"PU142:MP2985B:13:0x29:0 1"
"PU108:MP2985B:15:0x23:0 1"
"PU111:MP2985B:15:0x24:0 1"
"PU118:MP2985B:15:0x26:0 1"
"PU127:MP2985B:15:0x28:0 1"
)
_mp_err() { echo -e "[\033[31mERR \033[0m] $*" >&2; }
_mp_dbg() { [ "$DEBUG_MODE" = "1" ] && echo -e "\033[90m[DEBG] $*\033[0m" >&2; return 0; }
# --- i2c ---------------------------------------------------------------------
_mp_setpage() { # <bus> <addr> <page>
if [ "$MP_DRY_RUN" = "1" ]; then echo "i2cset -f -y $1 $2 0x00 $3"; return 0; fi
_mp_dbg "i2cset -f -y $1 $2 0x00 $3"
i2cset -f -y "$1" "$2" 0x00 "$3" 2>/dev/null
}
_mp_rd() { # <bus> <addr> <reg> <b|w> -> decimal on stdout
local out
if [ "$MP_DRY_RUN" = "1" ]; then echo "i2cget -f -y $1 $2 $3 $4" >&2; echo 0; return 0; fi
out=$(i2cget -f -y "$1" "$2" "$3" "$4" 2>/dev/null) || { _mp_dbg "i2cget -f -y $1 $2 $3 $4 -> fail"; return 1; }
_mp_dbg "i2cget -f -y $1 $2 $3 $4 -> $out"
case "$out" in 0x[0-9a-fA-F]*) echo $(( out )) ;; *) return 1 ;; esac
}
# --- decoders ----------------------------------------------------------------
_mp_l11() { # SLINEAR11 word -> value
local w=$(( $1 & 0xFFFF )) e m
e=$(( (w >> 11) & 0x1F )); (( e > 15 )) && e=$(( e - 32 ))
m=$(( w & 0x7FF )); (( m > 1023 )) && m=$(( m - 2048 ))
awk -v m="$m" -v e="$e" 'BEGIN { printf "%.3f", m * (2.0 ^ e) }'
}
_mp_res() { # MP29816 Vout LSB (V) from MFR_VOUT_SCALE_LOOP bits[12:10]
case $(( ($1 >> 10) & 0x7 )) in
0) echo 0.00625 ;; 1) echo 0.005 ;; 2) echo 0.0025 ;; 3) echo 0.002 ;;
4) echo 0.001 ;; 5) echo 0.00390625 ;; 6) echo 0.001953125 ;; 7) echo 0.0009765625 ;;
esac
}
# --- one page: sets V_STR / I_STR / V_VAL / I_VAL ----------------------------
_mp_read_page() { # <part> <bus> <addr> <page> <res>
local part="$1" bus="$2" addr="$3" pg="$4" res="$5" try wv wi wp
V_STR="$MP_NA"; I_STR="$MP_NA"; V_VAL=""; I_VAL=""
for (( try=0; try<MP_RETRY; try++ )); do
_mp_setpage "$bus" "$addr" "$pg" || continue
wv=$(_mp_rd "$bus" "$addr" 0x8B w) || wv=""
wi=$(_mp_rd "$bus" "$addr" 0x8C w) || wi=""
[ "$MP_DRY_RUN" = "1" ] && { _mp_rd "$bus" "$addr" 0x00 b >/dev/null; return 0; }
wp=$(_mp_rd "$bus" "$addr" 0x00 b) || continue
(( wp == pg )) || { _mp_dbg "$bus/$addr: PAGE moved to $wp, re-reading page $pg"; continue; }
break
done
(( try == MP_RETRY )) && return 1
if [ -n "$wv" ] && (( wv != 0xFFFF )); then
if [ "$part" = "MP29816" ]; then
V_VAL=$(awk -v c=$(( wv & 0xFFF )) -v r="$res" 'BEGIN { printf "%.3f", c * r }')
else
V_VAL=$(awk -v c=$(( wv & 0xFFF )) 'BEGIN { printf "%.3f", c / 1000 }')
fi
V_STR=$(printf '%s(0x%04X)' "$V_VAL" "$wv")
fi
if [ -n "$wi" ] && (( wi != 0xFFFF )); then
I_VAL=$(_mp_l11 "$wi")
I_STR=$(printf '%s(0x%04X)' "$I_VAL" "$wi")
fi
return 0
}
_mp_pout() { [ -n "$1" ] && [ -n "$2" ] && awk -v v="$1" -v i="$2" 'BEGIN { printf "%.3f", v * i }' || echo "$MP_NA"; }
# --- main --------------------------------------------------------------------
blanton_multiphase_read() {
while [ $# -gt 0 ]; do
case "$1" in
-u|--unit)
case "$2" in
all|ALL) MP_UNITS="0 1" ;;
0|1) MP_UNITS="$2" ;;
*) _mp_err "invalid unit '$2' (use 0, 1 or all)"; return 2 ;;
esac
shift 2 ;;
-d|--debug) DEBUG_MODE=1; shift ;;
-n|--dry-run) MP_DRY_RUN=1; shift ;;
-h|--help) sed -n '/^# Usage:/,/^# =====/p' "${BASH_SOURCE[0]}" | sed '$d; s/^# \{0,1\}//'; return 0 ;;
*) _mp_err "unknown option '$1'"; return 2 ;;
esac
done
command -v i2cget >/dev/null 2>&1 || [ "$MP_DRY_RUN" = "1" ] || { _mp_err "i2cget not found"; return 1; }
local fmt='%-16s| %-17s| %-17s| %-18s| %-18s| %-9s| %-9s| %s\n'
local rule='--------------------------------------------------------------------------------------------------------------------------'
local u e name part bus0 addr pages bus res w temp tot sums=""
local V1 I1 V1v I1v V2 I2 V2v I2v P1 P2 m
printf "$fmt" NAME "VOUT" "VOUT2" "IOUT" "IOUT2" "POUT" "POUT2" "TEMP"
echo "$rule"
for u in $MP_UNITS; do
tot=0
for e in "${MP_CHIPS[@]}"; do
IFS=: read -r name part bus0 addr pages <<< "$e"
bus=$(( bus0 + u ))
res=""; temp="$MP_NA"
V1="$MP_NA"; I1="$MP_NA"; V1v=""; I1v=""
V2="$MP_NA"; I2="$MP_NA"; V2v=""; I2v=""
# page 0: scale (MP29816), temperature, VOUT/IOUT
_mp_setpage "$bus" "$addr" 0
if [ "$part" = "MP29816" ]; then
w=$(_mp_rd "$bus" "$addr" 0x29 w) && res=$(_mp_res "$w")
[ -n "$res" ] || res=0.0025
fi
if w=$(_mp_rd "$bus" "$addr" 0x8D w) && (( w != 0xFFFF )); then
if [ "$part" = "MP29816" ]; then temp=$(printf '%.2f' "$(_mp_l11 "$w")")
else m=$(( w & 0x7FF )); (( m > 1023 )) && m=$(( m - 2048 )); temp=$(printf '%.2f' "$m"); fi
fi
if _mp_read_page "$part" "$bus" "$addr" 0 "$res"; then
V1="$V_STR"; I1="$I_STR"; V1v="$V_VAL"; I1v="$I_VAL"
fi
if [[ " $pages " == *" 1 "* ]] && _mp_read_page "$part" "$bus" "$addr" 1 "$res"; then
V2="$V_STR"; I2="$I_STR"; V2v="$V_VAL"; I2v="$I_VAL"
fi
_mp_setpage "$bus" "$addr" 0 >/dev/null
P1=$(_mp_pout "$V1v" "$I1v")
if [[ " $pages " == *" 1 "* ]]; then P2=$(_mp_pout "$V2v" "$I2v"); else P2="$MP_NA"; fi
[ "$P1" != "$MP_NA" ] && tot=$(awk -v a="$tot" -v b="$P1" 'BEGIN { printf "%.3f", a + b }')
[ "$P2" != "$MP_NA" ] && tot=$(awk -v a="$tot" -v b="$P2" 'BEGIN { printf "%.3f", a + b }')
[ "$MP_DRY_RUN" = "1" ] && continue
printf "$fmt" "SWB${u}-MP_${name}" "$V1" "$V2" "$I1" "$I2" "$P1" "$P2" "$temp"
done
sums="${sums}SWB${u} Total Power: ${tot} W"$'\n'
done
[ "$MP_DRY_RUN" = "1" ] && return 0
echo "$rule"
printf '%s' "$sums"
echo "(hex = raw PMBus word; POUT = VOUT x IOUT; an NA row means the chip did not answer or PAGE kept moving)"
}
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
blanton_multiphase_read "$@"
fi