skywalker-1/tools/indirect_loopback_test.py
Ryan Malloy 0d6facb321 Add experimental I2C debugging and EEPROM analysis tools
One-off diagnostic scripts from experiments 0xD7-0xDB investigating
the I2C BERR deadlock. Documents the systematic elimination of
software-only recovery approaches:

- i2c_host_test.py: Proved 0xA0 register writes cannot drive I2C bus
- i2c_register_test.py: Tested I2C register writability from host
- i2c_recovery_boot.py: Attempted I2C state machine recovery via boot
- eeprom_flash_a0.py: Host-side EEPROM flash attempt (failed)
- boot_ab_test.py / boot_test.py: EEPROM boot reliability testing
- a8_autoclear_test.py: BCM4500 command register auto-clear behavior
- addr_gateway_test.py: BCM3440 gateway address routing analysis
- stock_fw_compare.py / stock_fw_test.py: Stock vs custom fw analysis
2026-02-20 10:57:10 -07:00

177 lines
6.8 KiB
Python

#!/usr/bin/env python3
"""BCM4500 indirect register loopback test.
Write a known value via indirect write, then read it back.
Tests multiple approaches:
1. Multi-byte A6+A7+A8 in one transaction (0xB2 uses bcm_indirect_write)
2. Separate writes via 0xB6 diagnostic
3. Read via 0xB6 with various delays
4. Read using 0xB1 (bcm_indirect_read wrapper)
If we can write-then-read a value back, the DSP command processor works.
If not, we need to look at the I2C transaction structure more carefully.
"""
import sys
import time
sys.path.insert(0, 'tools')
from skywalker_lib import SkyWalker1
CMD_RAW_DEMOD_READ = 0xB1
CMD_RAW_DEMOD_WRITE = 0xB2
CMD_I2C_RAW_READ = 0xB5
CMD_I2C_DIAG = 0xB6
BCM4500_ADDR = 0x08
sw = SkyWalker1()
sw.open()
print('=== BCM4500 Indirect Register Loopback Test ===')
print(f'Firmware: {sw.get_fw_version()}')
# Boot with full sequence
print('\n--- Booting BCM4500 (full boot) ---')
result = sw._vendor_in(0x89, value=1, index=0, length=3)
cfg, stage = result[0], result[1]
print(f' Config: 0x{cfg:02X}, Stage: 0x{stage:02X}')
time.sleep(0.1)
# ============================================================
# Test 1: Read default indirect register values
# ============================================================
print('\n=== Test 1: Default indirect register values (0xB1) ===')
for page in [0x00, 0x01, 0x06, 0x07, 0x0A, 0x0F]:
try:
data = sw._vendor_in(CMD_RAW_DEMOD_READ, value=page, index=0, length=1)
print(f' Page 0x{page:02X}: 0x{data[0]:02X}')
except Exception as e:
print(f' Page 0x{page:02X}: FAILED ({e})')
# ============================================================
# Test 2: Write via 0xB2 (multi-byte A6+A7+A8), then read via 0xB1
# ============================================================
print('\n=== Test 2: Write 0x42 to page 0x00, read back (0xB2 write, 0xB1 read) ===')
try:
# 0xB2: bcm_indirect_write(page=0x00, val=0x42)
# Writes A6=0x00, A7=0x42, A8=0x03 in one 3-byte I2C transaction
sw._vendor_in(CMD_RAW_DEMOD_WRITE, value=0x00, index=0x42, length=0)
except Exception:
pass # May not return data
time.sleep(0.05)
# Read back via 0xB1 (bcm_indirect_read with 1ms delay)
try:
data = sw._vendor_in(CMD_RAW_DEMOD_READ, value=0x00, index=0, length=1)
print(f' Read back page 0x00: 0x{data[0]:02X}')
if data[0] == 0x42:
print(' >> LOOPBACK SUCCESS! DSP is processing commands!')
elif data[0] == 0x00:
print(' >> Got 0x00 — either DSP not running or write/read protocol broken')
else:
print(f' >> Unexpected: 0x{data[0]:02X}')
except Exception as e:
print(f' Read failed: {e}')
# ============================================================
# Test 3: Direct register reads before/after indirect write
# ============================================================
print('\n=== Test 3: Direct register state before/after indirect commands ===')
print(' Before indirect write:')
for reg in [0xA6, 0xA7, 0xA8]:
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
index=reg, length=1)
print(f' 0x{reg:02X}: 0x{data[0]:02X}')
# Write via 0xB2
try:
sw._vendor_in(CMD_RAW_DEMOD_WRITE, value=0x06, index=0xAB, length=0)
except Exception:
pass
time.sleep(0.01)
print(' After indirect write (page=0x06, data=0xAB):')
for reg in [0xA6, 0xA7, 0xA8]:
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
index=reg, length=1)
print(f' 0x{reg:02X}: 0x{data[0]:02X}')
# ============================================================
# Test 4: Manual step-by-step with individual I2C writes
# ============================================================
print('\n=== Test 4: Manual indirect read using individual raw I2C writes ===')
print(' Writing A6=0x06 via direct I2C write...')
# We don't have a raw I2C write command, but we can use the 0xB6 diagnostic
# which does individual writes and reads for us.
# Test 4a: 0xB6 with READ command (wIndex=0x01)
print('\n 4a: 0xB6 diagnostic — READ page 0x06:')
diag = sw._vendor_in(CMD_I2C_DIAG, value=0x06, index=0x01, length=8)
print(f' A6 write ok: {diag[0]}')
print(f' A6 readback: 0x{diag[1]:02X}')
print(f' A8 write ok: {diag[2]}')
print(f' A8 immediate: 0x{diag[3]:02X}')
print(f' A8 after 2ms: 0x{diag[4]:02X}')
print(f' A7 data: 0x{diag[5]:02X}')
print(f' A6 final: 0x{diag[6]:02X}')
# ============================================================
# Test 5: Try reading A7 with longer delays
# ============================================================
print('\n=== Test 5: Indirect read with longer delays ===')
print(' Maybe the DSP needs more time to process the command...')
# Use 0xB6 to write A6=0x06 and A8=0x01
sw._vendor_in(CMD_I2C_DIAG, value=0x06, index=0x01, length=8)
for delay_ms in [10, 50, 100, 500]:
time.sleep(delay_ms / 1000.0)
# Read A7 via raw I2C
try:
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
index=0xA7, length=1)
print(f' After {delay_ms:4d}ms: A7=0x{data[0]:02X}')
except Exception as e:
print(f' After {delay_ms:4d}ms: FAILED ({e})')
# ============================================================
# Test 6: Direct register dump after all tests
# ============================================================
print('\n=== Test 6: Register state after all tests ===')
print(' A0-AF:')
for reg in range(0xA0, 0xB0):
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
index=reg, length=1)
print(f' 0x{reg:02X}=0x{data[0]:02X}', end='')
if (reg % 8) == 7:
print()
print()
# ============================================================
# Test 7: Power cycle BCM4500 and check pre-command register state
# ============================================================
print('\n=== Test 7: Reboot and check BEFORE any indirect commands ===')
sw._vendor_in(0x89, value=0, index=0, length=3) # shutdown
time.sleep(0.5)
sw._vendor_in(0x89, value=1, index=0, length=3) # full boot
time.sleep(0.1)
print(' Fresh boot — direct reg reads (no indirect commands issued):')
for reg in [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB]:
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
index=reg, length=1)
print(f' 0x{reg:02X}: 0x{data[0]:02X}')
# Now issue ONE indirect read command and check if registers change
print('\n After ONE indirect read (page 0x06):')
diag = sw._vendor_in(CMD_I2C_DIAG, value=0x06, index=0x01, length=8)
print(f' A7 data: 0x{diag[5]:02X} (this is the indirect read result)')
# Check if direct registers changed
print(' Direct register check after indirect command:')
for reg in [0xA0, 0xA2, 0xA4, 0xA6, 0xA7, 0xA8]:
data = sw._vendor_in(CMD_I2C_RAW_READ, value=BCM4500_ADDR,
index=reg, length=1)
print(f' 0x{reg:02X}: 0x{data[0]:02X}')
sw.close()
print('\n=== Done ===')