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
123 lines
4.0 KiB
Python
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()
|