skywalker-1/tools/i2c_host_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

197 lines
6.9 KiB
Python

#!/usr/bin/env python3
"""Test I2C communication from host side while FX2LP CPU is halted.
When the CPU is halted (CPUCS=1), I2CS=0x0A (clean state). When the CPU
runs (CPUCS=0), I2CS=0xF6 (stuck). This script tests whether the I2C
controller is functional during CPU halt by attempting to read the boot
EEPROM header (which MUST contain 0xC0 or 0xC2).
Uses USB vendor command 0xA0 to read/write XDATA registers directly.
"""
import sys
import time
import usb.core
import usb.util
# XDATA register addresses
I2CS_ADDR = 0xE678
I2DAT_ADDR = 0xE679
I2CTL_ADDR = 0xE67A
CPUCS_ADDR = 0xE600
# I2CS bit masks
bmSTART = 0x80
bmSTOP = 0x40
bmLASTRD = 0x20
bmBERR = 0x04
bmACK = 0x02
bmDONE = 0x01
EEPROM_ADDR_W = 0xA2 # 0x51 << 1 | 0 (write)
EEPROM_ADDR_R = 0xA3 # 0x51 << 1 | 1 (read)
def fx2_read(dev, addr, length=1):
"""Read XDATA register(s) via USB vendor command 0xA0."""
return dev.ctrl_transfer(0xC0, 0xA0, addr, 0, length, timeout=1000)
def fx2_write(dev, addr, data):
"""Write XDATA register(s) via USB vendor command 0xA0."""
dev.ctrl_transfer(0x40, 0xA0, addr, 0, data, timeout=1000)
def i2cs_str(val):
"""Decode I2CS register value."""
flags = []
if val & 0x80: flags.append('START')
if val & 0x40: flags.append('STOP')
if val & 0x20: flags.append('LASTRD')
if val & 0x04: flags.append('BERR')
if val & 0x02: flags.append('ACK')
if val & 0x01: flags.append('DONE')
return f"0x{val:02X} ({' | '.join(flags) if flags else 'idle'})"
def wait_done(dev, timeout_ms=100):
"""Poll I2CS for DONE bit."""
deadline = time.monotonic() + timeout_ms / 1000.0
while time.monotonic() < deadline:
i2cs = fx2_read(dev, I2CS_ADDR, 1)[0]
if i2cs & bmDONE:
return True, i2cs
time.sleep(0.001)
return False, i2cs
def main():
dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203)
if not dev:
print("SkyWalker-1 not found")
sys.exit(1)
print("SkyWalker-1 I2C Host-Side Test")
print("=" * 50)
# Step 1: Halt CPU
print("\n[1] Halting CPU (CPUCS=1)...")
fx2_write(dev, CPUCS_ADDR, bytes([0x01]))
time.sleep(0.05)
# Step 2: Read I2C state
i2cs = fx2_read(dev, I2CS_ADDR, 1)[0]
i2ctl = fx2_read(dev, I2CTL_ADDR, 1)[0]
print(f" I2CS = {i2cs_str(i2cs)}")
print(f" I2CTL = 0x{i2ctl:02X}")
if i2cs == 0xF6:
print(" WARNING: I2CS stuck at 0xF6 even during CPU halt!")
print(" I2C test will likely fail.")
# Step 3: Try to write I2CTL and read back
print("\n[2] Testing register writability...")
fx2_write(dev, I2CTL_ADDR, bytes([0x01])) # 400kHz
time.sleep(0.01)
i2ctl_rb = fx2_read(dev, I2CTL_ADDR, 1)[0]
print(f" Wrote I2CTL=0x01, read back 0x{i2ctl_rb:02X}", end="")
if i2ctl_rb == 0x01:
print(" (write works!)")
else:
print(f" (write IGNORED — register is read-only during halt)")
# Step 4: Try hardware I2C — read EEPROM header byte at address 0x0000
print("\n[3] Attempting EEPROM read via hardware I2C...")
print(" Target: EEPROM 0x51, address 0x0000 (boot header)")
# Issue START
print("\n [START] Writing I2CS = 0x80...")
fx2_write(dev, I2CS_ADDR, bytes([bmSTART]))
done, i2cs = wait_done(dev, 200)
print(f" I2CS = {i2cs_str(i2cs)}, DONE={'YES' if done else 'NO'}")
if not done:
print(" START didn't complete. Trying alternative: write I2DAT first...")
# Some controllers need I2DAT written before START can proceed
# Write EEPROM address (0xA2 = 0x51 write)
fx2_write(dev, I2DAT_ADDR, bytes([EEPROM_ADDR_W]))
time.sleep(0.01)
i2cs = fx2_read(dev, I2CS_ADDR, 1)[0]
print(f" After I2DAT=0xA2: I2CS = {i2cs_str(i2cs)}")
if not done:
# Try the Cypress-documented sequence: START is issued by
# writing the slave address to I2DAT AFTER writing START to I2CS
print("\n Trying standard Cypress I2C sequence...")
# Re-issue START
fx2_write(dev, I2CS_ADDR, bytes([bmSTART]))
time.sleep(0.001)
# Write slave address — this should clock the address byte
fx2_write(dev, I2DAT_ADDR, bytes([EEPROM_ADDR_W]))
done, i2cs = wait_done(dev, 200)
print(f" After START + I2DAT=0xA2: I2CS = {i2cs_str(i2cs)}, DONE={'YES' if done else 'NO'}")
if done:
ack = 'ACK' if (i2cs & bmACK) else 'NAK'
print(f" Address phase: {ack}")
if done:
# Write EEPROM address bytes (16-bit address: 0x0000)
print("\n Writing address 0x0000...")
fx2_write(dev, I2DAT_ADDR, bytes([0x00])) # addr high
done, i2cs = wait_done(dev, 200)
ack = 'ACK' if (i2cs & bmACK) else 'NAK'
print(f" Addr high: {ack}, DONE={'YES' if done else 'NO'}")
if done:
fx2_write(dev, I2DAT_ADDR, bytes([0x00])) # addr low
done, i2cs = wait_done(dev, 200)
ack = 'ACK' if (i2cs & bmACK) else 'NAK'
print(f" Addr low: {ack}, DONE={'YES' if done else 'NO'}")
if done:
# Re-START for read phase
print("\n Re-START for read phase...")
fx2_write(dev, I2CS_ADDR, bytes([bmSTART]))
time.sleep(0.001)
fx2_write(dev, I2DAT_ADDR, bytes([EEPROM_ADDR_R]))
done, i2cs = wait_done(dev, 200)
ack = 'ACK' if (i2cs & bmACK) else 'NAK'
print(f" Read addr: {ack}, DONE={'YES' if done else 'NO'}")
if done:
# Read first byte (and only byte — set LASTRD + STOP)
print("\n Reading first byte (LASTRD + STOP)...")
fx2_write(dev, I2CS_ADDR, bytes([bmLASTRD]))
time.sleep(0.001)
# Dummy read to trigger byte transfer
dummy = fx2_read(dev, I2DAT_ADDR, 1)[0]
done, i2cs = wait_done(dev, 200)
if done:
data = fx2_read(dev, I2DAT_ADDR, 1)[0]
fx2_write(dev, I2CS_ADDR, bytes([bmSTOP]))
print(f" Boot header byte: 0x{data:02X}", end="")
if data == 0xC0:
print(" (C0 = no renumerate)")
elif data == 0xC2:
print(" (C2 = renumerate)")
else:
print(f" (unexpected!)")
print("\n *** EEPROM READ SUCCESSFUL! ***")
else:
print(f" Read DONE timeout. I2CS = {i2cs_str(i2cs)}")
# Final state
i2cs_final = fx2_read(dev, I2CS_ADDR, 1)[0]
print(f"\n[4] Final I2CS = {i2cs_str(i2cs_final)}")
# Try STOP to clean up
fx2_write(dev, I2CS_ADDR, bytes([bmSTOP]))
time.sleep(0.01)
i2cs_stop = fx2_read(dev, I2CS_ADDR, 1)[0]
print(f" After STOP: I2CS = {i2cs_str(i2cs_stop)}")
# Release CPU
print("\n[5] Releasing CPU (CPUCS=0)...")
fx2_write(dev, CPUCS_ADDR, bytes([0x00]))
time.sleep(0.5)
print(" CPU released. Device will re-enumerate.")
if __name__ == '__main__':
main()