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

123 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Pinpoint which element in mode 0x82 causes bcm_direct_read to fail.
Test sequence:
1. Power on via 0x81, confirm alive with raw read
2. 0x84: bcm_direct_read ONLY (no GPIO, no reset, no bus reset)
3. 0x85: GPIO + reset + power but NO I2C bus reset (no bmSTOP)
4. 0x82: GPIO + I2C bus reset + reset + power + probe (the one that fails)
"""
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 decode_stage(stage):
names = {
0x00: "NOT_STARTED", 0xA1: "GPIO_OK", 0xA2: "PROBE_OK(0x82)",
0xA3: "BLK0_OK", 0xA4: "PROBE_OK(0x84)", 0xA5: "PROBE_OK(0x85)",
0xE3: "PROBE_FAIL", 0xE4: "BLK0_FAIL",
}
return names.get(stage, f"0x{stage:02X}")
def test_boot_mode(dev, wval, label, timeout_ms=3000):
print(f"\n{'' * 55}")
print(f" Mode 0x{wval:02X}: {label}")
print(f"{'' * 55}")
t0 = time.monotonic()
try:
ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, wval, 0, 3, timeout=timeout_ms)
except usb.core.USBError as e:
elapsed = (time.monotonic() - t0) * 1000
print(f" TIMEOUT after {elapsed:.0f}ms: {e}")
return None
elapsed = (time.monotonic() - t0) * 1000
stage = ret[1]
probe = ret[2]
ok = stage not in (0xE3, 0xE4)
status_str = "SUCCESS" if ok else "FAILED"
print(f" {status_str} in {elapsed:.0f}ms")
print(f" stage=0x{stage:02X} [{decode_stage(stage)}] probe=0x{probe:02X}")
return ret
def raw_read(dev, addr, reg):
try:
r = dev.ctrl_transfer(0xC0, 0xB5, addr, reg, 1, timeout=1000)
return r[0]
except:
return None
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}")
# Step 1: Power on via GPIO-only mode
print("\n=== STEP 1: Power on BCM4500 (mode 0x81) ===")
ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x81, 0, 3, timeout=3000)
print(f" GPIO setup done, stage=0x{ret[1]:02X}")
time.sleep(1.0)
# Confirm alive
val = raw_read(dev, 0x08, 0xA2)
print(f" Raw read 0x08:0xA2 = 0x{val:02X}" if val is not None else " Raw read FAILED")
# Step 2: Test 0x84 (I2C read ONLY, no GPIO manipulation)
test_boot_mode(dev, 0x84, "bcm_direct_read ONLY (no GPIO, chip already powered)")
# Confirm still alive
val = raw_read(dev, 0x08, 0xA2)
print(f" Raw read after 0x84: 0x{val:02X}" if val is not None else " Raw read FAILED")
# Step 3: Test 0x85 (GPIO + reset but NO I2C bus reset)
test_boot_mode(dev, 0x85, "GPIO + reset + power, NO bmSTOP (no I2C bus reset)")
# Confirm still alive
time.sleep(0.1)
val = raw_read(dev, 0x08, 0xA2)
print(f" Raw read after 0x85: 0x{val:02X}" if val is not None else " Raw read FAILED")
# Step 4: For comparison, test 0x82 (the one that fails)
test_boot_mode(dev, 0x82, "GPIO + I2C bmSTOP + reset + power + probe")
# Confirm still alive
val = raw_read(dev, 0x08, 0xA2)
print(f" Raw read after 0x82: 0x{val:02X}" if val is not None else " Raw read FAILED")
print(f"\n{'=' * 55}")
print("Analysis complete.")
print()
print("If 0x84 works → bcm_direct_read is fine, issue is in reset/GPIO sequence")
print("If 0x84 fails → bcm_direct_read itself has a bug")
print("If 0x85 works → I2CS bmSTOP (I2C bus reset) is the culprit in 0x82")
print("If 0x85 fails → re-reset of BCM4500 needs more delay")
if __name__ == "__main__":
main()