skywalker-1/tools/test_i2c_isolate.py
Ryan Malloy d9f51548e0 Fix BCM4500 boot: spurious I2C STOP corrupted FX2 controller
Removed I2CS bmSTOP "bus reset" from bcm4500_boot() and debug modes.
Sending STOP with no active transaction puts the FX2 I2C controller
into an inconsistent state where subsequent START+ACK detection fails.

Root cause identified through incremental debug modes (wValue 0x80-0x85)
on live hardware: mode 0x82 (with bmSTOP) fails, mode 0x85 (identical
but without bmSTOP) succeeds. Raw I2C reads confirm BCM4500 is alive
the entire time -- only the controller state is corrupted.

BCM4500 now boots successfully in ~90ms. Three I2C devices found on
bus: 0x08 (BCM4500), 0x10 (tuner/LNB), 0x51 (EEPROM).

Also in this commit:
- Timeout-protected I2C functions replacing fx2lib bare while loops
- I2C bus scan and debug mode infrastructure
- Kernel driver blacklist for dvb_usb_gp8psk
- Test tools for incremental boot debugging
- Technical findings documented in docs/boot-debug-findings.md
2026-02-12 10:34:15 -07:00

127 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""Isolate whether bcm_direct_read is broken or if re-reset causes the failure.
Test sequence:
1. Power on BCM4500 with 0x81 (GPIO only)
2. Wait 1s for chip to settle
3. Confirm chip alive via raw read 0xB5
4. Try bcm_direct_read via debug mode 0x82 (which RE-RESETS the chip)
5. Immediately try raw read 0xB5 again (is chip alive after 0x82's reset?)
6. Wait various delays and retry raw reads
This tells us if the issue is bcm_direct_read vs insufficient post-reset delay.
"""
import usb.core
import usb.util
import sys
import time
BOOT_8PSK = 0x89
def find_device():
dev = usb.core.find(idVendor=0x09C0, idProduct=0x0203)
if not dev:
print("Device not found!")
sys.exit(1)
return dev
def setup_device(dev):
try:
if dev.is_kernel_driver_active(0):
dev.detach_kernel_driver(0)
except Exception:
pass
try:
dev.set_configuration()
except usb.core.USBError:
pass
def raw_read(dev, addr, reg, label=""):
"""Read via 0xB5 raw I2C handler."""
try:
r = dev.ctrl_transfer(0xC0, 0xB5, addr, reg, 1, timeout=1000)
val = r[0]
ok = val != 0xFF
mark = "OK" if ok else "no-resp"
print(f" {label}Raw read addr=0x{addr:02X} reg=0x{reg:02X} → 0x{val:02X} ({mark})")
return val, ok
except usb.core.USBError as e:
print(f" {label}Raw read addr=0x{addr:02X} reg=0x{reg:02X} → USB ERROR: {e}")
return None, False
def main():
dev = find_device()
setup_device(dev)
ret = dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=2000)
major, minor, patch = ret[2], ret[1], ret[0]
print(f"Firmware: v{major}.{minor:02d}.{patch}\n")
# --- Test A: Verify BCM4500 alive from cold ---
print("=" * 55)
print("TEST A: Power on BCM4500, wait, then raw read")
print("=" * 55)
print(" Sending 0x81 (GPIO power on + reset release)...")
ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x81, 0, 3, timeout=3000)
print(f" GPIO done: stage=0x{ret[1]:02X}")
print(" Waiting 1000ms for BCM4500 to settle...")
time.sleep(1.0)
raw_read(dev, 0x08, 0xA2, "After 1s: ")
# --- Test B: Now try bcm_direct_read (which re-resets) ---
print()
print("=" * 55)
print("TEST B: Run debug mode 0x82 (re-resets + probe via bcm_direct_read)")
print("=" * 55)
ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x82, 0, 3, timeout=3000)
stage = ret[1]
probe = ret[2]
if stage == 0xA2:
print(f" bcm_direct_read SUCCEEDED: status=0x{probe:02X}")
else:
print(f" bcm_direct_read FAILED: stage=0x{stage:02X} probe=0x{probe:02X}")
# --- Test C: Immediately try raw read after 0x82 (same I2C function, no reset) ---
print()
print("=" * 55)
print("TEST C: Immediately try raw read 0xB5 (same i2c_combined_read)")
print("=" * 55)
raw_read(dev, 0x08, 0xA2, "Immediate: ")
# --- Test D: Wait and retry at various intervals ---
print()
print("=" * 55)
print("TEST D: Raw reads with increasing delays after 0x82's reset")
print("=" * 55)
for delay_ms in [100, 200, 500, 1000, 2000]:
time.sleep(delay_ms / 1000.0)
raw_read(dev, 0x08, 0xA2, f"After {delay_ms}ms: ")
# --- Test E: Redo power-on without reset, then probe ---
print()
print("=" * 55)
print("TEST E: Run 0x81 again (re-power), wait 1s, then 0x82")
print("=" * 55)
ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x81, 0, 3, timeout=3000)
print(f" GPIO done: stage=0x{ret[1]:02X}")
time.sleep(1.0)
raw_read(dev, 0x08, 0xA2, "After 0x81+1s: ")
print(" Now running 0x82 (re-reset + probe)...")
ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x82, 0, 3, timeout=3000)
stage = ret[1]
probe = ret[2]
if stage == 0xA2:
print(f" bcm_direct_read SUCCEEDED: status=0x{probe:02X}")
else:
print(f" bcm_direct_read FAILED: stage=0x{stage:02X} probe=0x{probe:02X}")
print("\n" + "=" * 55)
print("Analysis complete.")
if __name__ == "__main__":
main()