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
This commit is contained in:
parent
890a38bfa0
commit
d9f51548e0
7 changed files with 1296 additions and 51 deletions
171
tools/test_boot.py
Normal file
171
tools/test_boot.py
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test BOOT_8PSK on SkyWalker-1 with custom firmware v3.01.0"""
|
||||
|
||||
import usb.core
|
||||
import usb.util
|
||||
import sys
|
||||
import time
|
||||
|
||||
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):
|
||||
"""Detach kernel driver and set configuration."""
|
||||
try:
|
||||
if dev.is_kernel_driver_active(0):
|
||||
dev.detach_kernel_driver(0)
|
||||
print("Detached kernel driver from interface 0")
|
||||
except Exception as e:
|
||||
print(f"Driver detach note: {e}")
|
||||
|
||||
try:
|
||||
dev.set_configuration()
|
||||
except usb.core.USBError:
|
||||
# Already configured, that's fine
|
||||
pass
|
||||
|
||||
def main():
|
||||
dev = find_device()
|
||||
setup_device(dev)
|
||||
|
||||
# GET_FW_VERS (0x92)
|
||||
print("=" * 50)
|
||||
ret = dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6)
|
||||
major, minor, patch = ret[2], ret[1], ret[0]
|
||||
day, month, year = ret[3], ret[4], ret[5] + 2000
|
||||
print(f"Firmware: v{major}.{minor:02d}.{patch} ({year}-{month:02d}-{day:02d})")
|
||||
|
||||
# GET_8PSK_CONFIG (0x80)
|
||||
ret = dev.ctrl_transfer(0xC0, 0x80, 0, 0, 1)
|
||||
print(f"Config before boot: 0x{ret[0]:02X}")
|
||||
|
||||
# BOOT_8PSK (0x89) with wValue=1
|
||||
print()
|
||||
print("=" * 50)
|
||||
print("Sending BOOT_8PSK(1)...")
|
||||
print(" (This triggers: P0.5 reset, power on, 3-block register init)")
|
||||
print()
|
||||
|
||||
try:
|
||||
ret = dev.ctrl_transfer(0xC0, 0x89, 1, 0, 3, timeout=10000)
|
||||
except usb.core.USBError as e:
|
||||
print(f"BOOT_8PSK USB error: {e}")
|
||||
print("The device may have timed out during init.")
|
||||
print("Trying to read config status anyway...")
|
||||
try:
|
||||
ret = dev.ctrl_transfer(0xC0, 0x80, 0, 0, 1)
|
||||
print(f"Config after attempted boot: 0x{ret[0]:02X}")
|
||||
except:
|
||||
print("Device not responding. May need power cycle.")
|
||||
sys.exit(1)
|
||||
|
||||
status = ret[0]
|
||||
stage = ret[1] if len(ret) > 1 else 0
|
||||
stage_names = {
|
||||
0: "NOT_STARTED", 1: "GPIO_SETUP", 2: "PWR_SETTLED",
|
||||
3: "I2C_PROBE", 4: "INIT_BLK0", 5: "INIT_BLK1",
|
||||
6: "INIT_BLK2", 0xFF: "COMPLETE"
|
||||
}
|
||||
flags = []
|
||||
if status & 0x01: flags.append("STARTED")
|
||||
if status & 0x02: flags.append("FW_LOADED")
|
||||
if status & 0x04: flags.append("INTERSIL")
|
||||
if status & 0x08: flags.append("DVB_MODE")
|
||||
if status & 0x10: flags.append("22KHZ")
|
||||
if status & 0x20: flags.append("SEL18V")
|
||||
if status & 0x40: flags.append("DC_TUNED")
|
||||
if status & 0x80: flags.append("ARMED")
|
||||
print(f"BOOT_8PSK response: 0x{status:02X} [{' | '.join(flags) if flags else 'none'}]")
|
||||
print(f"Boot stage: 0x{stage:02X} [{stage_names.get(stage, 'UNKNOWN')}]")
|
||||
|
||||
if status & 0x03 == 0x03:
|
||||
print()
|
||||
print("*** BCM4500 BOOT SUCCESS! ***")
|
||||
print()
|
||||
|
||||
# Read direct I2C registers
|
||||
print("BCM4500 direct registers (via I2C_RAW_READ 0xB5):")
|
||||
for reg in [0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8]:
|
||||
try:
|
||||
r = dev.ctrl_transfer(0xC0, 0xB5, 0x08, reg, 1)
|
||||
print(f" Reg 0x{reg:02X} = 0x{r[0]:02X}")
|
||||
except Exception as e:
|
||||
print(f" Reg 0x{reg:02X}: ERROR {e}")
|
||||
|
||||
# Read indirect registers through our protocol
|
||||
print()
|
||||
print("BCM4500 indirect registers (via RAW_DEMOD_READ 0xB1):")
|
||||
for page in range(16):
|
||||
try:
|
||||
r = dev.ctrl_transfer(0xC0, 0xB1, page, 0, 1)
|
||||
print(f" Page 0x{page:02X} = 0x{r[0]:02X}")
|
||||
except Exception as e:
|
||||
print(f" Page 0x{page:02X}: ERROR {e}")
|
||||
|
||||
# I2C diagnostic
|
||||
print()
|
||||
print("I2C diagnostic (0xB6) for page 0x00:")
|
||||
try:
|
||||
r = dev.ctrl_transfer(0xC0, 0xB6, 0x00, 0, 8)
|
||||
labels = ["wr_A6", "rb_A6", "wr_A8", "rb_A8",
|
||||
"rb_A7", "fin_A6", "fin_A7", "fin_A8"]
|
||||
for i, lab in enumerate(labels):
|
||||
print(f" {lab}: 0x{r[i]:02X}")
|
||||
except Exception as e:
|
||||
print(f" ERROR: {e}")
|
||||
|
||||
# Signal strength
|
||||
print()
|
||||
try:
|
||||
r = dev.ctrl_transfer(0xC0, 0x87, 0, 0, 6)
|
||||
print(f"Signal strength: {' '.join(f'{b:02X}' for b in r)}")
|
||||
except Exception as e:
|
||||
print(f"Signal strength ERROR: {e}")
|
||||
|
||||
# Signal lock
|
||||
try:
|
||||
r = dev.ctrl_transfer(0xC0, 0x90, 0, 0, 1)
|
||||
print(f"Signal lock: 0x{r[0]:02X}")
|
||||
except Exception as e:
|
||||
print(f"Signal lock ERROR: {e}")
|
||||
|
||||
else:
|
||||
print()
|
||||
print("*** BOOT FAILED ***")
|
||||
print()
|
||||
|
||||
# I2C bus scan
|
||||
print("I2C bus scan:")
|
||||
try:
|
||||
r = dev.ctrl_transfer(0xC0, 0xB4, 0, 0, 16)
|
||||
addrs = []
|
||||
for bi in range(16):
|
||||
for bit in range(8):
|
||||
if r[bi] & (1 << bit):
|
||||
addrs.append(bi * 8 + bit)
|
||||
if addrs:
|
||||
print(f" Found devices at: {[f'0x{a:02X}' for a in addrs]}")
|
||||
else:
|
||||
print(" No I2C devices found!")
|
||||
except Exception as e:
|
||||
print(f" Scan error: {e}")
|
||||
|
||||
# Try raw I2C reads anyway
|
||||
print()
|
||||
print("Raw I2C reads to BCM4500 (0x08):")
|
||||
for reg in [0xA2, 0xA4, 0xA6, 0xA7, 0xA8]:
|
||||
try:
|
||||
r = dev.ctrl_transfer(0xC0, 0xB5, 0x08, reg, 1)
|
||||
print(f" Reg 0x{reg:02X} = 0x{r[0]:02X}")
|
||||
except Exception as e:
|
||||
print(f" Reg 0x{reg:02X}: ERROR {e}")
|
||||
|
||||
print()
|
||||
print("=" * 50)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
127
tools/test_boot_debug.py
Normal file
127
tools/test_boot_debug.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Incremental BOOT_8PSK debug tester for SkyWalker-1.
|
||||
|
||||
Sends debug boot modes (wValue=0x80..0x83) one at a time to isolate
|
||||
which stage of the BCM4500 boot sequence hangs the FX2 firmware.
|
||||
|
||||
Usage:
|
||||
sudo python3 test_boot_debug.py # run all debug stages
|
||||
sudo python3 test_boot_debug.py 0x82 # run only stage 0x82
|
||||
"""
|
||||
|
||||
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",
|
||||
0x01: "GPIO_SETUP",
|
||||
0x02: "PWR_SETTLED",
|
||||
0x03: "I2C_PROBE",
|
||||
0x04: "INIT_BLK0",
|
||||
0x05: "INIT_BLK1",
|
||||
0x06: "INIT_BLK2",
|
||||
0xA1: "DEBUG_GPIO_OK",
|
||||
0xA2: "DEBUG_PROBE_OK",
|
||||
0xA3: "DEBUG_BLK0_OK",
|
||||
0xE3: "DEBUG_PROBE_FAIL",
|
||||
0xE4: "DEBUG_BLK0_FAIL",
|
||||
0xFF: "COMPLETE",
|
||||
}
|
||||
return names.get(stage, f"UNKNOWN(0x{stage:02X})")
|
||||
|
||||
def test_mode(dev, wval, label, timeout_ms=3000):
|
||||
"""Send a debug boot mode and read 3-byte response."""
|
||||
print(f"\n{'─' * 50}")
|
||||
print(f" Testing wValue=0x{wval:02X}: {label}")
|
||||
print(f"{'─' * 50}")
|
||||
|
||||
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" FAILED after {elapsed:.0f}ms: {e}")
|
||||
# Try to see if device is still alive
|
||||
try:
|
||||
dev.ctrl_transfer(0xC0, 0x92, 0, 0, 6, timeout=1000)
|
||||
print(" Device still responds to GET_FW_VERS")
|
||||
except:
|
||||
print(" Device is HUNG (no response to GET_FW_VERS)")
|
||||
return None
|
||||
elapsed = (time.monotonic() - t0) * 1000
|
||||
|
||||
status = ret[0]
|
||||
stage = ret[1] if len(ret) > 1 else 0
|
||||
probe = ret[2] if len(ret) > 2 else 0
|
||||
|
||||
print(f" Response in {elapsed:.0f}ms:")
|
||||
print(f" config_status: 0x{status:02X}")
|
||||
print(f" boot_stage: 0x{stage:02X} [{decode_stage(stage)}]")
|
||||
print(f" probe_byte: 0x{probe:02X}")
|
||||
return ret
|
||||
|
||||
def main():
|
||||
dev = find_device()
|
||||
setup_device(dev)
|
||||
|
||||
# Verify firmware is responding
|
||||
try:
|
||||
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}")
|
||||
except usb.core.USBError as e:
|
||||
print(f"GET_FW_VERS failed: {e}")
|
||||
print("Device may be hung. Try reloading firmware with fw_load.py.")
|
||||
sys.exit(1)
|
||||
|
||||
ret = dev.ctrl_transfer(0xC0, 0x80, 0, 0, 1)
|
||||
print(f"Config: 0x{ret[0]:02X}")
|
||||
|
||||
# Parse optional argument for single-stage testing
|
||||
single_stage = None
|
||||
if len(sys.argv) > 1:
|
||||
single_stage = int(sys.argv[1], 0)
|
||||
|
||||
stages = [
|
||||
(0x80, "No-op: return current state only"),
|
||||
(0x81, "GPIO setup + power + delays (no I2C)"),
|
||||
(0x82, "GPIO + I2C bus reset + BCM4500 probe read"),
|
||||
(0x83, "GPIO + I2C probe + write init block 0"),
|
||||
]
|
||||
|
||||
for wval, label in stages:
|
||||
if single_stage is not None and wval != single_stage:
|
||||
continue
|
||||
result = test_mode(dev, wval, label)
|
||||
if result is None:
|
||||
print("\n*** STOPPING: device not responding ***")
|
||||
break
|
||||
|
||||
print(f"\n{'=' * 50}")
|
||||
print("Debug complete.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
118
tools/test_i2c_debug.py
Normal file
118
tools/test_i2c_debug.py
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#!/usr/bin/env python3
|
||||
"""I2C debug tool for SkyWalker-1.
|
||||
|
||||
First powers on the BCM4500 via GPIO debug mode (0x81), then:
|
||||
1. Runs I2C bus scan (0xB4) to find any devices
|
||||
2. Tries raw I2C reads (0xB5) to common BCM4500 addresses
|
||||
3. Tests different post-reset delays
|
||||
"""
|
||||
|
||||
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 main():
|
||||
dev = find_device()
|
||||
setup_device(dev)
|
||||
|
||||
# Verify firmware
|
||||
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 BCM4500 via GPIO-only debug mode
|
||||
print("\n--- Step 1: Power on BCM4500 (GPIO mode 0x81) ---")
|
||||
ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x81, 0, 3, timeout=3000)
|
||||
print(f" GPIO setup: stage=0x{ret[1]:02X}")
|
||||
|
||||
# Step 2: I2C bus scan immediately
|
||||
print("\n--- Step 2: I2C bus scan (immediately after power-on) ---")
|
||||
try:
|
||||
ret = dev.ctrl_transfer(0xC0, 0xB4, 0, 0, 16, timeout=5000)
|
||||
addrs = []
|
||||
for bi in range(16):
|
||||
for bit in range(8):
|
||||
if ret[bi] & (1 << bit):
|
||||
addrs.append(bi * 8 + bit)
|
||||
if addrs:
|
||||
print(f" Found devices at: {[f'0x{a:02X}' for a in addrs]}")
|
||||
else:
|
||||
print(" No I2C devices found!")
|
||||
except usb.core.USBError as e:
|
||||
print(f" Bus scan error: {e}")
|
||||
|
||||
# Step 3: Wait longer and scan again
|
||||
print("\n--- Step 3: Wait 500ms and scan again ---")
|
||||
time.sleep(0.5)
|
||||
try:
|
||||
ret = dev.ctrl_transfer(0xC0, 0xB4, 0, 0, 16, timeout=5000)
|
||||
addrs = []
|
||||
for bi in range(16):
|
||||
for bit in range(8):
|
||||
if ret[bi] & (1 << bit):
|
||||
addrs.append(bi * 8 + bit)
|
||||
if addrs:
|
||||
print(f" Found devices at: {[f'0x{a:02X}' for a in addrs]}")
|
||||
else:
|
||||
print(" No I2C devices found!")
|
||||
except usb.core.USBError as e:
|
||||
print(f" Bus scan error: {e}")
|
||||
|
||||
# Step 4: Try raw I2C reads to various addresses
|
||||
print("\n--- Step 4: Raw I2C reads (0xB5) to likely BCM4500 addresses ---")
|
||||
# BCM4500 could be at different addresses depending on pin strapping
|
||||
# Common: 0x08 (AD=low), 0x0A (AD=high), or even other addresses
|
||||
candidates = [0x08, 0x09, 0x0A, 0x0B, 0x10, 0x11, 0x68, 0x69, 0x60, 0x61]
|
||||
for addr in candidates:
|
||||
for reg in [0xA2, 0x00]:
|
||||
try:
|
||||
r = dev.ctrl_transfer(0xC0, 0xB5, addr, reg, 1, timeout=1000)
|
||||
print(f" Addr 0x{addr:02X} Reg 0x{reg:02X} = 0x{r[0]:02X} <--- RESPONDS!")
|
||||
except usb.core.USBError:
|
||||
print(f" Addr 0x{addr:02X} Reg 0x{reg:02X} = (no response)")
|
||||
|
||||
# Step 5: Check I2C bus state
|
||||
print("\n--- Step 5: I2C controller state ---")
|
||||
try:
|
||||
# Read I2CTL and I2CS by inspecting them through a known-working address
|
||||
# Actually, we can just observe what happens when we try reads
|
||||
print(" (Bus scan and raw reads above show bus health)")
|
||||
except Exception as e:
|
||||
print(f" Error: {e}")
|
||||
|
||||
# Step 6: Try I2C probe via debug mode 0x82 again with a delay
|
||||
print("\n--- Step 6: Debug probe (0x82) after additional 1s delay ---")
|
||||
time.sleep(1.0)
|
||||
ret = dev.ctrl_transfer(0xC0, BOOT_8PSK, 0x82, 0, 3, timeout=3000)
|
||||
stage = ret[1]
|
||||
probe = ret[2]
|
||||
if stage == 0xA2:
|
||||
print(f" PROBE SUCCESS! BCM4500 status = 0x{probe:02X}")
|
||||
else:
|
||||
print(f" Probe failed: stage=0x{stage:02X} probe=0x{probe:02X}")
|
||||
|
||||
print("\nDone.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
126
tools/test_i2c_isolate.py
Normal file
126
tools/test_i2c_isolate.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#!/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()
|
||||
122
tools/test_i2c_pinpoint.py
Normal file
122
tools/test_i2c_pinpoint.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#!/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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue